0

How to use group-1 result in group-2 by Java Lambda.

This question is update from my a solved problem: How to covert List to Map<T, <K, List<Person>>> use java lambda?

you can see the last code. the function key2 want to use function key1's result, how to do it?

class Person{
   int age;
   int cityCode;
   String name;
}

method:

// Map<age, Map<cityCode, List<Person>>>
public Map<Integer, Map<Integer, List<Person>>> covertListToMap(List<Person> list){

      // TODO: How to make List<Person> to Map<age, Map<cityCode, List<Person>>>

}



Function<Person, Integer> key1 = (Person p) -> {
            // do many sth then get a key1Result:
            int key1Result = p.getAge() * new Random(10).nextInt();
            return key1Result;
        };


Function<Person, Integer> key2 = (Person p) -> {
            //  Question: how to get and use Function key1 key1Result:

            int key1Result = 0;

            int result = p.getCityCode() + key1Result;
            return result;
        };

Map<Integer, Map<Integer, List<Person>>> collect = list.stream().collect(groupingBy(key1, groupingBy(key2)));

I write a wrong sample to show my meaning:

Map<Integer, Map<Integer, List<Person>>> collect = list.stream().collect(groupingBy((Person p) -> {
            int key1Result = p.getAge() * new Random(10).nextInt();
            return key1Result;  // perhaps, now, key1Result is 10
        }, groupingBy((Person p) -> {

            // now, I want to get the key1Result which is 10, How to do it?  
            int key1Result = 0;

            int result = p.getCityCode() + key1Result;
            return result;
        })));
kingdelee
  • 15
  • 4

2 Answers2

1

You can use the function apply(T t). According to the javadoc:

Applies this function to the given argument.

In your case, it would mean:

int key1Result = key1.apply(p)

Edit: As you modified your question, I add an answer, should be right if I understood what you wanted to do:

AtomicInteger atomicInteger = new AtomicInteger(0);
Map<Integer, Map<Integer, List<Person>>> collect = personList.stream().collect(Collectors
        .groupingBy(p -> {
                int age = p.getAge() * new Random(10).nextInt();
                atomicInteger.set(age);
                return age;
            }, Collectors.groupingBy(p2 -> p2.getCityCode() + atomicInteger.get())));

If you want to read more about AtomicInteger, see atomic. Basically it is a wrapper that will keep its variable between threads.

Paul Lemarchand
  • 2,068
  • 1
  • 15
  • 27
  • sorry , I have not explain my problem. I have update my question to explain my meaning in the third code area. – kingdelee May 08 '19 at 12:08
  • @PaulLemarchand, Why have you used AtomicInteger? Can you elaborate please? Just curious :) – Master Chief May 08 '19 at 12:40
  • @MasterChief Because it's a good way to modify a local variable inside a lambda. How would you have done it ? – Paul Lemarchand May 08 '19 at 12:41
  • @MasterChief Nevermind, saw your answer. I think this way is simpler though. – Paul Lemarchand May 08 '19 at 12:46
  • @PaulLemarchand Because the scenario does not have extra threads, I do not think there is any need for using AtomicInteger. That is why I was asking the reason behind using that. Does it have any other uses besides supporting atomic operations. – Master Chief May 08 '19 at 12:50
  • @MasterChief Oh right. Well I hope it answered you well. Good way to modify a local variable. – Paul Lemarchand May 08 '19 at 12:58
  • Thanks @PaulLemarchand MasterChief, Paul's way is easy to read, if I have many params want to use in group2, maybe is this only way? is MasterChied 's meaning if this is not in mutil-threads, it'is not the best way? what's the best way? – kingdelee May 09 '19 at 01:48
  • @kingdelee He didn't know you could use it that way. If you want to learn more, read this answer: https://stackoverflow.com/a/30026897/6252134. – Paul Lemarchand May 09 '19 at 02:10
  • 1
    +1 @PaulLemarchand The way to use `AtomicInteger (AI)` to use as a container is good Idea. That said this implementation will not work in case some one uses `parallelStream` than `stream`. Because in that case multiple threads may access the same `AI` (albiet one after the other). For e.g. 1st threads write value x in `AI` and assumes that when creating the value side, the `AI` will have the value it wrote. But when a second thread writes value y to `AI` this implemenation will go hay wire. But then again it doesn't seems like OP requires parallelStream. So a +1. – Master Chief May 09 '19 at 04:48
  • 1
    Just to add a different perspective, streams are there to enable functional programming. And its against functional paradigm philosophy to mutate some common variable. But real world uses cases sometimes do require going against the philosophy. https://medium.com/infocentric/functional-programming-concepts-explained-in-javascript-16133b441936 – Master Chief May 09 '19 at 04:52
  • Wow @MasterChief, you really thought about this ! I upvoted your answer because you make a good point, yours works in all cases whereas mine might fail if one uses `parallelStream`, and therefore, if someone that uses those finds this question, your answer will match him better. I know that streams shouldn't access non-final local variables outside of their scopes, as it is against Java 8's promise in general: Readability & Testability. Although in this case it should pose no problem, you are right once again. – Paul Lemarchand May 09 '19 at 05:09
  • 1
    Master Chief , Paul, you make me learn much about lambda, I must use much time to digest knowledge,Thank you very much.I think Master way is fantastic , Paul way is more proper this case. I am become your fans. – kingdelee May 11 '19 at 16:15
1

1st you collect the required Map with your modified key

Map<Integer, Map<Integer, List<Person>>> collect2 = personList.stream().collect(Collectors
        .groupingBy(p -> p.getAge() * new Random(10).nextInt(),
                Collectors.groupingBy(Person::getCityCode)));

Then change the map. The problem in your this case is you are using nested Maps. And using the key of the outer Map you want to change the key of inner Map. So you there is not other way than to create a new inner Map for each outer Key.

Here I am iterating over each entry of outer map and creating a new proper (required) inner Map using the inner Map already present.

collect2.entrySet().stream().forEach(e -> {
    Integer key = e.getKey();
    Map<Integer, List<Person>> oldValueMap = e.getValue();
    Map<Integer, List<Person>> newValueMap = new HashMap<>();
    oldValueMap.keySet().forEach(k -> {
        newValueMap.put(k + key, oldValueMap.get(k));
    });
    e.setValue(newValueMap);
});
Master Chief
  • 2,520
  • 19
  • 28
  • WOW,you make me open eye, it's professional, and some hard to read. It's work.But I want to ask a question, If this way is more loop than @Paul Lemarchand's way? Actually, get the key1result is not my ultimate purpose, my puzzled is, if some params have build in the first group, and I want to use it in second group, is the only way like Paul Lemarchand's way? Paul Lemarchand's way is easy to read, is it safety and good performance? – kingdelee May 09 '19 at 01:39