@Udf(description = "When reducing a map, " 
      + "the reduce function must have three arguments. "
      + "The three arguments for the reduce function are in order: " 
      + "the state, the key, and the value. "
      + "The final state is returned."
  )
  public <K,V,S> S reduceMap(
      @UdfParameter(description = "The map.") final Map<K, V> map,
      @UdfParameter(description = "The initial state.") final S initialState,
      @UdfParameter(description = "The reduce function.") final TriFunction<S, K, V, S> triFunction
  ) {
    if (initialState == null || triFunction == null) {
      return null;
    }

    if (map == null) {
      return initialState;
    }

    S state = initialState;
    for (Entry<K, V> entry : map.entrySet()) {
      state = triFunction.apply(state, entry.getKey(), entry.getValue());
    }
    return state;
  }
@Test
  public void shouldReduceMap() {
    final Map<Integer, Integer> map1 = new HashMap<>();
    assertThat(udf.reduceMap(map1, 3, triFunction1()), is(3));
    map1.put(4, 3);
    map1.put(6, 2);
    assertThat(udf.reduceMap(map1, 42,triFunction1()), is(57));
    assertThat(udf.reduceMap(map1, -4, triFunction1()), is(11));
    map1.put(0,0);
    assertThat(udf.reduceMap(map1, 0, triFunction1()), is(15));

    final Map<String, Integer> map2 = new HashMap<>();
    assertThat(udf.reduceMap(map2, "", triFunction2()), is(""));
    map2.put("a", 42);
    map2.put("b", 11);
    assertThat(udf.reduceMap(map2, "", triFunction2()), is("ba"));
    assertThat(udf.reduceMap(map2, "string", triFunction2()), is("bastring"));
    map2.put("c",0);
    map2.put("d",15);
    map2.put("e",-5);
    assertThat(udf.reduceMap(map2, "q", triFunction2()), is("dbaq"));
  }