4

I am sorting a populated set of MyObject (the object has a getName() getter) in a stream using a predefined myComparator.

Then once sorted, is there a way to collect into a map the name of the MyObject and the order/position of the object from the sort?

Here is what I think it should look like:

Set<MyObject> mySet;  // Already populated mySet 

Map<String, Integer> nameMap = mySet.stream()
        .sorted(myComparator)
        .collect(Collectors.toMap(MyObject::getName, //HowToGetThePositionOfTheObjectInTheStream));

For example, if the set contain three objects (object1 with name name1, object2 with name name2, object3 with name name3) and during the stream they get sorted, how do I get a resulting map that looks like this:

name1, 1
name2, 2
name3, 3

Thanks.

shmosel
  • 49,289
  • 6
  • 73
  • 138
000000000000000000000
  • 780
  • 4
  • 15
  • 47

4 Answers4

4

A Java Stream doesn't expose any index or positioning of elements, so I know no way of replacing /*HowToGetThePositionOfTheObjectInTheStream*/ with streams magic to obtain the desired number.

Instead, one simple way is to collect to a List instead, which gives every element an index. It's zero-based, so when converting to a map, add 1.

List<String> inOrder = mySet.stream()
     .sorted(myComparator)
     .map(MyObject::getName)
     .collect(Collectors.toList());
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < inOrder.size(); i++) {
    nameMap.put(inOrder.get(i), i + 1);
}
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • I think I'm with you on this one. I originally thought that there might be a Function that would expose positioning, but a search online says otherwise. Thanks for this answer. – 000000000000000000000 May 25 '18 at 20:36
1

Try this one. you could use AtomicInteger for value of each entry of map. and also to guarantee order of map use LinkedHashMap.

AtomicInteger index = new AtomicInteger(1);
Map<String, Integer> nameMap =  mySet.stream()
    .sorted(myComparator)
    .collect(Collectors
            .toMap(MyObject::getName, value -> index.getAndIncrement(),
                  (e1, e2) -> e1, LinkedHashMap::new));
Hadi J
  • 16,989
  • 4
  • 36
  • 62
1

The simplest solution would be a loop, as a formally correct stream solution that would also work in parallel requires a nontrivial (compared to the rest) merge functions:

Map<String,Integer> nameMap = mySet.stream()
    .sorted(myComparator)
    .collect(HashMap::new, (m, s) -> m.put(s.getName(), m.size()),
        (m1, m2) -> {
            int offset = m1.size();
            m2.forEach((k, v) -> m1.put(k, v + offset));
    });

Compare with a loop/collection operations:

List<MyObject> ordered = new ArrayList<>(mySet);
ordered.sort(myComparator);
Map<String, Integer> result = new HashMap<>();
for(MyObject o: ordered) result.put(o.getName(), result.size());

Both solutions assume unique elements (as there can be only one position). It’s easy to change the loop to detect violations:

for(MyObject o: ordered)
    if(result.putIfAbsent(o.getName(), result.size()) != null)
        throw new IllegalStateException("duplicate " + o.getName());
Holger
  • 285,553
  • 42
  • 434
  • 765
0

Dont use a stream:

List<MyObject> list = new ArrayList<>(mySet);
list.sort(myComparator);
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
    nameMap.put(list.get(i).getName(), i);
}

Not only will this execute faster than a stream based approach, everyone knows what's going on.

Streams have their place, but pre-Java 8 code does too.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    The loop can be even simpler: `for(MyObject o: list) nameMap.put(o.getName(), nameMap.size());` – Holger May 26 '18 at 11:32