Java: how to transform from List<T> to Map<f1(T), List(f2(T))> without iterating
Asked Answered
I

4

15

I have a list of objects that I need to transform to a map where the keys are a function of each element, and the values are lists of another function of each element. Effectively this is grouping the elements by a function of them.

For example, suppose a simple element class:

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

and a list of these:

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

then I would like a map as follows:

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

Can anyone suggest a succinct way of doing this in Java without iterating? A combination of LambdaJ's group method with Guava's Maps.transform nearly gets there, but group doesn't generate a map.

Irradiation answered 2/12, 2011 at 9:25 Comment(6)
Every approach needs some form of iteration, it might just do it for you. However, wheres the problem with calling the functions on each element in a foreach?Lavern
Because you want to be able to group items together, you need to iterate. You just need to find the items that belong together.Noncombatant
What is the reason to avoid iterating? If you afraid of using it, you have using the wrong language. ;)Stefanysteffane
Peter- you're right: having worked with Ruby in the past, I prefer to write code without loops wherever possible. Guava collections and LambdaJ go a long way, but not far enough in cases like this.Irradiation
Some code looks just uglier in Java when written with verbose Guava's transform + function rather than with traditional loop... P.S. I prefer Functional Programming techniques but in languages other than Java (until it has lambda).Namecalling
@Xaerxess - yes, I'm inclined to agree. Bring on proper lambda functions in Java!Irradiation
I
22

Guava has Maps.uniqueIndex(Iterable values, Function keyFunction) and Multimaps.index(Iterable values, Function keyFunction), but they don't transform the values. There are some requests to add utility methods that do what you want, but for now, you'll have to roll it yourself using Multimaps.index() and 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);
}

A re-usable utility method could be:

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);
}

I guess we could improve the generics in the signature by using Function<? extends E, K> or something, but I don't have the time to delve further...

Instauration answered 2/12, 2011 at 10:48 Comment(1)
Thank you, @eneveu, that does it nicely.Irradiation
C
11

Now with Java8 you can do it like:

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+"}"));
}
Crescendo answered 6/4, 2014 at 12:34 Comment(0)
C
1

There has been some discussion in adding one API in Apache's CollectionUtils to transform a List to Map, but then I dont see any reason for not using a foreach contruct, Is there any problem that you are facing ? Transform will do the same thing which you can get easily by foreach, looping cannot be avoided.

EDIT:

Here is the link to discussion in Apache's forum http://apache-commons.680414.n4.nabble.com/Convert-List-to-Map-td747218.html

Calmative answered 2/12, 2011 at 9:38 Comment(0)
B
0

I don't know why you don't want to iterate. JDK does not support transform, but you can implement it yourself.

If you are worried about the performance, even if JDK had supported it, it would have also iterated it.

Beleaguer answered 2/12, 2011 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.