Java: как преобразовать List‹T› в Map‹f1(T), List(f2(T))› без повторения

У меня есть список объектов, которые мне нужно преобразовать в карту, где ключи — это функции каждого элемента, а значения — это списки другой функции каждого элемента. По сути, это группировка элементов по их функциям.

Например, предположим простой класс элемента:

class Element {
    int f1() { ... }
    String f2() { ... }
}

и их список:

[
    { f1=100, f2="Alice" },
    { f1=200, f2="Bob" },
    { f1=100, f2="Charles" },
    { f1=300, f2="Dave" }
]

то я хотел бы карту следующим образом:

{
    {key=100, value=[ "Alice", "Charles" ]},
    {key=200, value=[ "Bob" ]},
    {key=300, value=[ "Dave" ]}
}

Может ли кто-нибудь предложить краткий способ сделать это на Java без итерации? Комбинация метода LambdaJ group с Maps.transform Guava почти дает результат, но group не генерирует карту.


person Kkkev    schedule 02.12.2011    source источник
comment
Каждый подход нуждается в некоторой форме итерации, он может сделать это за вас. Однако в чем проблема с вызовом функций для каждого элемента в foreach?   -  person Thomas    schedule 02.12.2011
comment
Поскольку вы хотите иметь возможность группировать элементы вместе, вам нужно повторять. Вам просто нужно найти предметы, которые принадлежат друг другу.   -  person Hiery Nomus    schedule 02.12.2011
comment
В чем причина избегать повторения? Если вы боитесь его использовать, вы используете неправильный язык. ;)   -  person Peter Lawrey    schedule 02.12.2011
comment
Питер, вы правы: работая с Ruby в прошлом, я предпочитаю писать код без циклов везде, где это возможно. Коллекции Guava и LambdaJ имеют большое значение, но недостаточно далеко в таких случаях.   -  person Kkkev    schedule 02.12.2011
comment
Некоторый код выглядит просто уродливее на Java, когда он написан с помощью многословной функции преобразования + Гуавы, а не с традиционным циклом... P.S. Я предпочитаю методы функционального программирования, но на других языках, кроме Java (пока у него нет лямбды).   -  person Xaerxess    schedule 02.12.2011
comment
@Xaerxess - да, я склонен согласиться. Добавьте правильные лямбда-функции в Java!   -  person Kkkev    schedule 03.12.2011


Ответы (4)


Гуава имеет Maps.uniqueIndex(Итерируемые значения, Function keyFunction) и Multimaps.index(Итерируемые значения, Function keyFunction), но они не преобразуют значения. Есть некоторые запросы на добавление служебных методов, которые делают то, что вы хотите, но пока вам придется выполнять это самостоятельно, используя Multimaps.index() и Multimaps.transformValues():

static class Person {
    private final Integer age;
    private final String name;

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}

private enum GetAgeFunction implements Function<Person, Integer> {
    INSTANCE;

    @Override
    public Integer apply(Person person) {
        return person.getAge();
    }
}

private enum GetNameFunction implements Function<Person, String> {
    INSTANCE;

    @Override
    public String apply(Person person) {
        return person.getName();
    }
}

public void example() {
    List<Person> persons = ImmutableList.of(
            new Person(100, "Alice"),
            new Person(200, "Bob"),
            new Person(100, "Charles"),
            new Person(300, "Dave")
    );

    ListMultimap<Integer, String> ageToNames = getAgeToNamesMultimap(persons);

    System.out.println(ageToNames);

    // prints {100=[Alice, Charles], 200=[Bob], 300=[Dave]}
}

private ListMultimap<Integer, String> getAgeToNamesMultimap(List<Person> persons) {
    ImmutableListMultimap<Integer, Person> ageToPersons = Multimaps.index(persons, GetAgeFunction.INSTANCE);
    ListMultimap<Integer, String> ageToNames = Multimaps.transformValues(ageToPersons, GetNameFunction.INSTANCE);

    // Multimaps.transformValues() returns a *lazily* transformed view of "ageToPersons"
    // If we want to iterate multiple times over it, it's better to create a copy
    return ImmutableListMultimap.copyOf(ageToNames);
}

Повторно используемый служебный метод может быть:

public static <E, K, V> ImmutableListMultimap<K, V> keyToValuesMultimap(Iterable<E> elements, Function<E, K> keyFunction, Function<E, V> valueFunction) {
    ImmutableListMultimap<K, E> keysToElements = Multimaps.index(elements, keyFunction);
    ListMultimap<K, V> keysToValuesLazy = Multimaps.transformValues(keysToElements, valueFunction);
    return ImmutableListMultimap.copyOf(keysToValuesLazy);
}

Я думаю, мы могли бы улучшить дженерики в подписи, используя Function<? extends E, K> или что-то в этом роде, но у меня нет времени копаться дальше...

person Etienne Neveu    schedule 02.12.2011
comment
Спасибо, @eneveu, это прекрасно. - person Kkkev; 03.12.2011

Теперь с Java8 вы можете сделать это так:

static class Element {
    final int f1;
    final String f2;

    Element(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }

    int f1() { return f1;}
    String f2() { return f2; }
}

public static void main(String[] args) {
    List<Element> elements = new ArrayList<>();
    elements.add(new Element(100, "Alice"));
    elements.add(new Element(200, "Bob"));
    elements.add(new Element(100, "Charles"));
    elements.add(new Element(300, "Dave"));

    elements.stream()
            .collect(Collectors.groupingBy(
                    Element::f1,
                    Collectors.mapping(Element::f2, Collectors.toList())
                    ))
            .forEach((f1, f2) -> System.out.println("{"+f1.toString() + ", value="+f2+"}"));
}
person enterbios    schedule 06.04.2014

Было некоторое обсуждение добавления одного API в Apache CollectionUtils для преобразования списка в карту, но тогда я не вижу причин не использовать конструкцию foreach. Есть ли какая-либо проблема, с которой вы столкнулись? Преобразование будет делать то же самое, что вы можете легко получить с помощью foreach, избежать зацикливания невозможно.

РЕДАКТИРОВАТЬ:

Вот ссылка на обсуждение на форуме Apache http://apache-commons.680414.n4.nabble.com/Convert-List-to-Map-td747218.html

person mprabhat    schedule 02.12.2011

Я не знаю, почему вы не хотите повторять. JDK не поддерживает преобразование, но вы можете реализовать его самостоятельно.

Если вы беспокоитесь о производительности, даже если бы JDK ее поддерживал, он также повторил бы ее.

person xie    schedule 02.12.2011