Java: как преобразовать из списка <T>в картубез итерации - PullRequest
14 голосов
/ 02 декабря 2011

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

Например, предположим, что класс простых элементов:

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 без итерации ?Комбинация group метода LambdaJ с Maps.transform Гуавы почти достигает цели, но group не генерирует карту.

Ответы [ 4 ]

21 голосов
/ 02 декабря 2011

Гуава имеет 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> или что-то в этом роде, но у меня нет времени углубляться ...

8 голосов
/ 06 апреля 2014

Теперь с 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+"}"));
}
1 голос
/ 02 декабря 2011

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

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

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

0 голосов
/ 02 декабря 2011

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

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

...