1

So I have a list of integer that is insIds, from this I can get a list of ABCs, I want to map them to an entry in map (notice that we can not getInsId from Abc.getIns)

This is what I want it to be: (but since we do not have getInsId we are not able to write this way)

Map<Integer, Integer> insIdToAbcId = abcController
          .findAbcsByInsIds(insIds)
          .stream()
          .collect(Collectors.toMap(Abc::getInsId,
            Abc::getAbcId));

I am not sure how to write it in order to have the mapping relationship I want.

Known list of integer: insIds

Known function that will take insIds and return list:

abcController.findAbcsByInsIds(insIds)

And then is what I am not sure about: how to map insId to AbcId

(expect output: Map<Integer, Integer> insIdToAbcId )

shmosel
  • 49,289
  • 6
  • 73
  • 138
KKlalala
  • 965
  • 1
  • 13
  • 23
  • full code, please – TheTechGuy Feb 06 '18 at 23:39
  • 2
    @TheTechGuy not sure what is missing? I think everything needed is here already? Known list of integer: insIds, known function that will take insIds and return list: abcController.findAbcsByInsIds(insIds). And then is what I am not sure about: how to map – KKlalala Feb 06 '18 at 23:45
  • how your expected output should look like? – TheTechGuy Feb 06 '18 at 23:59
  • @TheTechGuy expect output: Map insIdToAbcId – KKlalala Feb 07 '18 at 00:00
  • I guess you have that `Abc ` class already – TheTechGuy Feb 07 '18 at 00:00
  • @TheTechGuy but can't map them.. – KKlalala Feb 07 '18 at 00:02
  • 2
    Unless there's an ordered one-to-one relationship between `insIds` and the result of `findAbcsByInsIds()`, I don't think it's possible. Is there a variant of the method that takes a single ID? – shmosel Feb 07 '18 at 00:08
  • @shmosel yes it's an ordered one-to-one relationship between insIds and the result of findAbcsByInsIds(). The number of insIds can be really big so I want to use one sql to find them all.(findAbcsByInsIds will just have one sql) – KKlalala Feb 07 '18 at 00:11

3 Answers3

4

If there's an ordered one-to-one relationship between the items in insIds and the result of findAbcsByInsIds(), you can join the lists by index:

List<Abc> abcs = abcController.findAbcsByInsIds(insIds);
Map<Integer, Integer> insIdToAbcId = IntStream.range(0, insIds.size())
        .boxed()
        .collect(Collectors.toMap(insIds::get, i -> abcs.get(i).getAbcId()));
shmosel
  • 49,289
  • 6
  • 73
  • 138
1

This is a prime example of a problem where Stream is not the right solution.

After the findAbcsByInsIds() call, you have two lists of equal size, and you want to match them, by position, i.e. you need to parallel iterate them.

So do that:

List<Integer> insIds = /*...*/
List<Abc> abcList = abcController.findAbcsByInsIds(insIds);

Map<Integer, Integer> insIdToAbcId = new HashMap<>(insIds.size() * 4 / 3 + 1);
Iterator<Integer> insIter = insIds.iterator();
Iterator<Abc> abcIter = abcList.iterator();
while (insIter.hasNext())
    insIdToAbcId.put(insIter.next(), abcIter.next().getAbcId());

This code will perform well, even if lists are LinkedList.
The HashMap was preallocated to appropriate size, so resizing is eliminated, preventing need to rehash, i.e. for better performance.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Would you mind explaining the rationale behind `list.size() * 4 / 3 + 1`? Doesn't `HashMap` take care of that internally? – fps Feb 07 '18 at 14:47
  • 1
    @FedericoPeraltaSchaffner The [`HashMap​(int initialCapacity)`](https://docs.oracle.com/javase/9/docs/api/java/util/HashMap.html#HashMap-int-) constructor *"constructs an empty `HashMap` with the specified initial capacity and the default **load factor (0.75)**"*. Once you've added `initialCapacity * 3/4` elements, the `HashMap` is resized. To prevent resizing / rehashing, we need the `initialCapacity` to be *at least* `size * 4/3`, and since the division operator may round down, simply adding 1 will ensure that `initialCapacity >= size * 4/3`. – Andreas Feb 07 '18 at 17:06
0
Map< Integer, Integer > insIdToAbcId = findAbcsByInsIds( insIds )
            .stream( )
            .collect( HashMap::new, ( map, abc ) -> map.put( insIds.get( map.size( ) ), abc.getAbcId( ) ), ( a, b ) -> {} );

For each version to address the contract concerns about the third parameter

    Map< Integer, Integer > map = new HashMap<>(  );
    findAbcsByInsIds( insIds )
            .forEach( (abc) -> map.put( insIds.get( map.size( ) ), abc.getAbcId( ) ));

This is actually simpler and there in no need for boxing, auxiliary streams of even a collector. But about the collect method one could use an unsupported exception to make sure it is never called, should the implementation details of the underline jre change, it would not change without one's knowledge.

Victor
  • 3,520
  • 3
  • 38
  • 58
  • What's with the combiner? – shmosel Feb 07 '18 at 01:10
  • it is never called – Victor Feb 07 '18 at 01:11
  • So what? It's a required argument. – shmosel Feb 07 '18 at 01:12
  • that is why it is there – Victor Feb 07 '18 at 01:13
  • Well, no. You're violating the [requirement](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction) that *a partially accumulated result `p`... must be equivalent to `combiner.apply(p, supplier.get())`.* – shmosel Feb 07 '18 at 01:15
  • but there is no partial result. – Victor Feb 07 '18 at 01:17
  • Actually I don't think that requirement is broken. But the one in the next paragraph is. – shmosel Feb 07 '18 at 01:25
  • I see what you mean, but first, if it was a full collector implementation this could well be an unsupported exception. there is no violation it there is no entry for your violation test. – Victor Feb 07 '18 at 01:51
  • That's not how it works. Just because the code works under certain circumstances doesn't mean it's correct. – shmosel Feb 07 '18 at 01:54
  • the exactly what the semantics "for all" means in a set. an empty set is an empty set. But this is nice: https://stackoverflow.com/questions/29210176/can-a-collectors-combiner-function-ever-be-used-on-sequential-streams – Victor Feb 07 '18 at 02:01
  • also, there is the exception -> which is actually recommended by Raul-Gabriel Urma, Mario Fusco and Alan Mycroft – Victor Feb 07 '18 at 02:02
  • I don't know what "for all" you're referencing. And what part of the book are you referring to? – shmosel Feb 07 '18 at 02:07
  • The "for all" I was referering to the javadocs for stream. For the book "Note that in reality this collector can’t be used in parallel, because the algorithm is inherently sequential. This means the combiner method won’t ever be invoked, and you could leave its implementation empty (or better, throw an UnsupportedOperation- Exception)." – Victor Feb 07 '18 at 02:15
  • Where are both of those references? – shmosel Feb 07 '18 at 02:18
  • but point being, I would fell pretty safe with an exception there. otherwise a custom collector would simplify this problem too. in any case, if the op is uncomfortable with the combine, I doubt they could not rewrite this with a forEach and an auxiliary variable. – Victor Feb 07 '18 at 02:19
  • the book is on pag 154, and the java doc https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.htm – Victor Feb 07 '18 at 02:20
  • Ok, I see the quote in the book, but I disagree with the authors. The specification doesn't differentiate between parallel and sequential streams, as pointed out in the answer you linked (by a JDK developer, I should add), and to assume otherwise is dangerous. I'm still not seeing any "for all" on the Stream page that's relevant to our discussion. – shmosel Feb 07 '18 at 02:25
  • This means that for all u, combiner(identity, u) is equal to u. – Victor Feb 07 '18 at 02:27
  • That's from the `reduce()` method. – shmosel Feb 07 '18 at 02:28
  • next to (The combiner is necessary in parallel reductions, where the input is partitioned, a partial accumulation computed for each partition, and then the partial results are combined to produce a final result.) – Victor Feb 07 '18 at 02:31
  • That's an implementation note, not the specification. Anyway, we're discussing `collect()`, not `reduce()`. – shmosel Feb 07 '18 at 02:33
  • As I see those note are for both: The streams classes have multiple forms of general reduction operations, called reduce() and collect() – Victor Feb 07 '18 at 02:35