Pergunta

A partir de um java.util.function.BiFunction que mapeia um par de Enumé um valor, quero construir um EnumMap que reflete esse mapeamento.

Por exemplo, deixe E1 e E2 ser enum tipos e T qualquer tipo:

 BiFunction<E1,E2, T> theBiFunction = //...anything

 EnumMap<E1,EnumMap<E2,T>> theMap = 
    buildTheMap(                     // <--  this is where the magic happens
                E1.values(), 
                E2.values(),
                theBiFunction);

Dado qualquer par de valores do tipo E1 e E2

E1 e1 = //any valid value...
E2 e2 = //any valid value....

ambos os valores abaixo devem ser iguais:

T valueFromTheMaps = theMap.get(e1).get(e2);
T valueFromTheFunction = theBiFunction.apply(e1,e2);

boolean alwaysTrue = valueFromTheMaps.equals(valueFromTheFunction);

Qual é a melhor implementação (mais elegante, eficiente, etc...) para o método onde o "Magia" acontece em?

Foi útil?

Solução

Você obtém uma solução elegante se optar por uma solução genérica e decompô-la.Primeiro, implemente uma função genérica que crie um EnumMap fora de um Function, então implemente o mapeamento aninhado de um BiFunction usando a primeira função combinada consigo mesma:

static <T,E extends Enum<E>>
  EnumMap<E,T> funcToMap(Function<E,T> f, Class<E> t, E... values) {
    return Stream.of(values)
      .collect(Collectors.toMap(Function.identity(), f, (x,y)->x, ()-> new EnumMap<>(t)));
}
static <T,E1 extends Enum<E1>,E2 extends Enum<E2>>
  EnumMap<E1,EnumMap<E2,T>> biFuncToMap(
  BiFunction<E1,E2,T> f, Class<E1> t1, Class<E2> t2, E1[] values1, E2[] values2){

  return funcToMap(e1->funcToMap(e2->f.apply(e1, e2), t2, values2), t1, values1);
}

Aqui está um pequeno caso de teste:

enum Fruit {
    APPLE, PEAR
}
enum Color {
    RED, GREED, YELLOW
}

EnumMap<Fruit, EnumMap<Color, String>> result
  =biFuncToMap((a,b)->b+" "+a,
     Fruit.class, Color.class, Fruit.values(), Color.values());
System.out.println(result);

{APPLE={RED=RED APPLE, GREED=GREED APPLE, YELLOW=YELLOW APPLE}, PEAR={RED=RED PEAR, GREED=GREED PEAR, YELLOW=YELLOW PEAR}}

Claro, usando a solução genérica você pode construir métodos para concreto enum tipos que não exigem Class parâmetro(s)…


Isso deve funcionar sem problemas com um fluxo paralelo se o fornecido (Bi)Function é thread-safe.

Outras dicas

Como base de comparação, aqui está a versão convencional:

<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    EnumMap<E1,EnumMap<E2,T>> outer = new EnumMap<>(E1.class);
    for (E1 e1 : e1values) {
        EnumMap<E2,T> inner = new EnumMap<>(E2.class);
        for (E2 e2 : e2values) {
            inner.put(e2, f.apply(e1, e2));
        }
        outer.put(e1, inner);
    }
    return outer;
}

Agora, aqui está uma versão que usa formas aninhadas de três argumentos do collect() operação do terminal de fluxo:

<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    return
        Stream.of(e1values)
              .collect(() -> new EnumMap<>(E1.class),
                       (map, e1) -> map.put(e1, Stream.of(e2values)
                                                      .collect(() -> new EnumMap<>(E2.class),
                                                               (m, e2) -> m.put(e2, f.apply(e1, e2)),
                                                               Map::putAll)),
                       Map::putAll);
}

O que torna isso complicado é que a função acumuladora do coletor externo precisa executar um fluxo com seu próprio coletor de três argumentos para produzir o mapa interno.Isso é realmente difícil de recuar bem.Em vez do espaçamento padrão, alinhei os três argumentos para cada collect() chamar.Isso o torna bastante amplo, mas se eu não fizesse isso, seria difícil ver o que acontece, já que o aninhamento é muito profundo.Por mais fã de streams que eu seja, é difícil para mim dizer que esta é melhor do que a versão convencional.

Você pode dizer: "Por que não usar toMap() em vez do três argumentos collect() função?" O problema é que precisamos criar EnumMap casos, e a sobrecarga de toMap() que leva um fornecedor de mapas tem quatro argumentos:

toMap(keyFunc, valueFunc, mergeFunc, mapSupplier)

Pior ainda, a função de mesclagem (terceiro argumento) não é usada, então teríamos que fornecer uma função que nunca é usada.Aqui está o que parece:

<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    return
        Stream.of(e1values)
              .collect(toMap(e1 -> e1,
                             e1 -> Stream.of(e2values)
                                         .collect(toMap(e2 -> e2,
                                                        e2 -> f.apply(e1, e2),
                                                        (x, y) -> x,
                                                        () -> new EnumMap<>(E2.class))),
                             (x, y) -> x,
                             () -> new EnumMap<>(E1.class)));
}

Não me parece melhor.Meu dinheiro ainda está na versão convencional.

Existem várias abordagens alternativas que podemos tentar.Veremos o que uma boa noite de sono traz.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top