24

Using Google Guava (Google Commons), is there a way to merge two equally sized lists into one list, with the new list containing composite objects of the two input lists?

Example:

public class Person {
    public final String name;
    public final int age;

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

    public String toString() {
        return "(" + name + ", " + age + ")";
    }
}

and

List<String> names = Lists.newArrayList("Alice", "Bob", "Charles");
List<Integer> ages = Lists.newArrayList(42, 27, 31);

List<Person> persons =
    transform with a function that converts (String, Integer) to Person
System.out.println(persons);

Would output:

[(Alice, 42), (Bob, 27), (Charles, 31)]
Steve Kuo
  • 61,876
  • 75
  • 195
  • 257
  • Why do you need a specialized function for this, why not just write a simple one yourself? – arshajii Sep 27 '13 at 00:22
  • 5
    Anything Guava could provide would be more complicated to use than the straightforward `for` loop, which is short and simple anyway. – Louis Wasserman Sep 27 '13 at 01:59
  • Is there something you're not telling us? The example you're citing, it's kind of ridiculous to expect an external library to know anything about your `Person` class... the amount of reflection needed to do what you want would slow it down much more than the fairly obvious answer given below. – dcsohl Sep 27 '13 at 15:57
  • 2
    @dcsohl See https://code.google.com/p/guava-libraries/issues/detail?id=677 as to how this might be useful – Steve Kuo Sep 27 '13 at 16:08
  • 2
    @arshajii because it's a standard functional thing, and Guava supports some functional constructs. – orbfish Apr 03 '15 at 19:39

6 Answers6

33

As of Guava 21, this is possible via Streams.zip():

List<Person> persons = Streams.zip(names.stream(), ages.stream(), Person::new)
                              .collect(Collectors.toList());
Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
shmosel
  • 49,289
  • 6
  • 73
  • 138
16

Looks like this is not currently in Guava, but is a desired feature. See this github issue, in particular Iterators.zip().

Matthew Read
  • 1,365
  • 1
  • 30
  • 50
Steve Kuo
  • 61,876
  • 75
  • 195
  • 257
10

Just pretend this is a Guava method:

for (int i = 0; i < names.size(); i++) {
    persons.add(new Person(names.get(i), ages.get(i)));
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
stepanian
  • 11,373
  • 8
  • 43
  • 63
  • 22
    This answer misses the point. Of course I can write a for loop. I'm looking for generic way to apply the "zip" pattern to two collections. If tomorrow I need to merge `X`s and `Y`s into a List of `Z`s, I'd have to copy the above code and modify it. – Steve Kuo Sep 27 '13 at 16:10
  • 8
    @SteveKuo: What API could Guava possibly provide that would result in a shorter solution than this? Even if Guava added a `BiFunction` interface or something, and a `zipWith` method, the resulting anonymous class would be longer than this simple `for` loop. – Louis Wasserman Sep 27 '13 at 20:30
  • 3
    See "Functional idioms in Guava, explained" for the concept of using a transform https://code.google.com/p/guava-libraries/wiki/FunctionalExplained – Steve Kuo Sep 27 '13 at 23:11
  • 4
    @SteveKuo What if elements in your collections do not have an index? Sets come to mind. – Max Mar 26 '14 at 16:26
  • 4
    @Max Or `Iterable`s. (You normally don't want to zip sets, as they don't have an order. Cross product would be useful though) – Thomas Ahle Sep 22 '14 at 11:10
  • To all the zip haters, this is a valid concept that even Swift has adopted https://developer.apple.com/reference/swift/1541125-zip – Steve Kuo Jan 26 '17 at 19:30
  • and Scala http://alvinalexander.com/scala/how-to-merge-sequential-collection-pairs-zip-unzip-scala-cookbook – Steve Kuo May 03 '17 at 16:58
2

You can refer to underscore-java library.

Underscore-java is a port of Underscore.js for Java, and the zip method can achieve the goal.

Following is a sample code & output :

$.zip(Arrays.asList("moe", "larry", "curly"), Arrays.asList("30", "40", "50"));

=> [[moe, 30], [larry, 40], [curly, 50]]

LuFFy
  • 8,799
  • 10
  • 41
  • 59
1

Here's a generic way to zip lists with vanilla Java. Lacking tuples, I opted to use a list of map entries (If you don't like to use map entries, introduce an additional class ZipEntry or something).

public static <T1,T2> List<Map.Entry<T1,T2>> zip(List<T1> zipLeft, List<T2> zipRight) {
    List<Map.Entry<T1,T2>> zipped = new ArrayList<>();
    for (int i = 0; i < zipLeft.size(); i++) {
        zipped.add(new AbstractMap.SimpleEntry<>(zipLeft.get(i), zipRight.get(i)));
    }
    return zipped;
}

To support arrays as well:

@SuppressWarnings("unchecked")
public static <T1,T2> Map.Entry<T1,T2>[] zip(T1[] zipLeft, T2[] zipRight) {
    return zip(asList(zipLeft), asList(zipRight)).toArray(new Map.Entry[] {});
}

To make it more robust add precondition checks on list sizes etc, or introduce left join / right join semantics similar to SQL queries.

Benny Bottema
  • 11,111
  • 10
  • 71
  • 96
0

Here's a version with no explicit iteration, but it's getting pretty ugly.

List<Person> persons = ImmutableList.copyOf(Iterables.transform(
    ContiguousSet.create(Range.closedOpen(0, names.size()),
        DiscreteDomain.integers()),
    new Function<Integer, Person>() {
      @Override
      public Person(Integer index) {
        return new Person(names.get(index), ages.get(index));
      }
    }));

It's really not much better than having explicit iteration, and you probably want some level of bounds checking to ensure that the two inputs are indeed of the same size.

Zhe
  • 396
  • 1
  • 13