5

I have something like the below :

public class MyClass {
private Long stackId
private Long questionId
}

A collection of say 100, where the stackid could be duplicate with different questionIds. Its a one to many relationship between stackId and questionId

Is there a streamy, java 8 way to convert to the below strcuture :

public class MyOtherClass {
private Long stackId
private Collection<Long> questionIds
}

Which would be a collection of 25, with each instance having a nested collection of 4 questionIds.

Input :

[{1,100},{1,101},{1,102},{1,103},{2,200},{2,201},{2,202},{1,203}]

Output

[{1, [100,101,102,103]},{2,[200,201,202,203]}]
5
  • 2
    markhneedham.com/blog/2014/02/23/… - convert to a Map<Long, Collection<Long>> first, then convert the entries of that map to (the second kind of) MyClass instances. Commented Apr 6, 2016 at 7:58
  • 1
    I still don't understand the Which would be a collection of 25, part. Could you expand? Do you want the collection to contain at most 25 elements? What shall we do with the rest? Commented Apr 6, 2016 at 8:05
  • @Tunaki the output has less root elements than the input, but then each root element has a one to many relationship with sub elements (questionIds) Commented Apr 6, 2016 at 8:05
  • 1
    But why 25? Where does this number come from? And why A collection of say 100? Commented Apr 6, 2016 at 8:06
  • that doesn't matter, the numbers are just chosen to highlight the reduciton. It could be 1million to 1. Commented Apr 6, 2016 at 8:07

3 Answers 3

9

The straight-forward way with the Stream API involves 2 Stream pipelines:

  • The first one creates a temporary Map<Long, List<Long>> of stackId to questionIds. This is done with the groupingBy(classifier, downstream) collectors where we classify per the stackId and values having the same stackId are mapped to their questionId (with mapping) and collected into a list with toList().
  • The second one converts each entry of that map into a MyOtherClass instance and collects that into a list.

Assuming you have a constructor MyOtherClass(Long stackId, Collection<Long> questionIds), a sample code would be:

Map<Long, List<Long>> map = 
    list.stream()
        .collect(Collectors.groupingBy(
            MyClass::getStackId,
            Collectors.mapping(MyClass::getQuestionId, Collectors.toList())
        ));

List<MyOtherClass> result = 
    map.entrySet()
       .stream()
       .map(e -> new MyOtherClass(e.getKey(), e.getValue()))
       .collect(Collectors.toList());

Using StreamEx library, you could do that in a single Stream pipeline. This library offers a pairing and first collectors. This enables to pair two collectors and perform a finisher operation on the two collected results:

  • The first one only keeps the first stackId of the grouped elements (they will all be the same, by construction)
  • The second one mapping each element into their questionId and collecting into a list.
  • The finisher operation just returns a new instance of MyOtherClass.

Sample code:

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static one.util.streamex.MoreCollectors.first;
import static one.util.streamex.MoreCollectors.pairing;

// ...

Collection<MyOtherClass> result = 
    StreamEx.of(list)
            .groupingBy(
                MyClass::getStackId,
                pairing(
                    collectingAndThen(mapping(MyClass::getStackId, first()), Optional::get),
                    mapping(MyClass::getQuestionId, toList()),
                    MyOtherClass::new
                )
            ).values();
Sign up to request clarification or add additional context in comments.

2 Comments

Wow thanks for the in depth answer . Have you used any of the other functinal libs ? github.com/akullpp/awesome-java#functional-programming . This javaslang.io looks particualy active/polished.
@NimChimpsky No, I've never used those (a bit jOOL).
2
List<MyClass> inputs = Arrays.asList(
    new MyClass(1L, 100L),
    new MyClass(1L, 101L),
    new MyClass(1L, 102L),
    new MyClass(1L, 103L),
    new MyClass(2L, 200L),
    new MyClass(2L, 201L),
    new MyClass(2L, 202L),
    new MyClass(2L, 203L)
);

Map<Long, List<Long>> result = inputs
    .stream()
    .collect(
      Collectors.groupingBy(MyClass::getStackId,
        Collectors.mapping(
          MyClass::getQuestionId, 
          Collectors.toList()
        )
      )
    );

Comments

0

You can use the java8 groupingBy collector. Like this:

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class RandomTest {

    class MyClass {
        private Long stackId;
        private Long questionId;

        public MyClass(Long stackId, Long questionId) {
            this.stackId = stackId;
            this.questionId = questionId;
        }

        public Long getStackId() {
            return stackId;
        }

        public Long getQuestionId() {
            return questionId;
        }
    }

    public class MyOtherClass {
        private Long stackId;
        private Set<Long> questionIds;

        public MyOtherClass(Long stackId, Set<Long> questionIds) {
            this.stackId = stackId;
            this.questionIds = questionIds;
        }

        public Long getStackId() {
            return stackId;
        }

        public Set<Long> getQuestionIds() {
            return questionIds;
        }
    }

    @Test
    public void test() {
        List<MyClass> classes = new ArrayList<>();
        List<MyOtherClass> otherClasses = new ArrayList<>();

        //populate the classes list
        for (int j = 1; j <= 25; j++) {
            for (int i = 0; i < 4; i++) {
                classes.add(new MyClass(0L + j, (100L*j) + i));
            }
        }

        //populate the otherClasses List
        classes.stream().collect(Collectors
                .groupingBy(MyClass::getStackId, Collectors.mapping(MyClass::getQuestionId, Collectors.toSet())))
                .entrySet().stream().forEach(
                longSetEntry -> otherClasses.add(new MyOtherClass(longSetEntry.getKey(), longSetEntry.getValue())));

        //print the otherClasses list
        otherClasses.forEach(myOtherClass -> {
            System.out.print(myOtherClass.getStackId() + ": [");
            myOtherClass.getQuestionIds().forEach(questionId-> System.out.print(questionId + ","));
            System.out.println("]");
        });
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.