1

I have a Map<String, Boolean> with the following entries:

{‘John’:false,
 'Mike':false,
 'Tom':true,
 'Harry':false,
 'Bob': false}

I need to find the first entry and its index which has the value true. Here I should get Tom and 3 (say, index starts with 1).

I can iterate the map and get these values:

int itr=0;
for(Iterator<Map.Entry<String, Boolean>> entries = map.entrySet().iterator; entries.hasNext(); ) {
    itr++;
    Map.Entry<String, Boolean> entry = entries.next();
    if(entry.getValue()) {
       name = entry.getKey();
       index = itr;
    }
}

However, I am looking at a lambda expression for the same.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
user1639485
  • 808
  • 3
  • 14
  • 26
  • 5
    `HashMap` does not maintain the order of its elements! – user1803551 Feb 15 '17 at 11:55
  • 2
    @user1639485 by index you probably mean how many entries you have *seen* before you got your value? If so, this is un-reliable as when a certain re-size of the map happens, that entry can move. Unless your Map preserves order, like `LinkedHashMap`. – Eugene Feb 15 '17 at 12:15
  • @Eugene I think you pingged the wrong user and you basically said what I said. – user1803551 Feb 15 '17 at 12:18
  • 1
    See [here](http://stackoverflow.com/questions/20240408/how-to-get-element-position-from-java-map) and [here](http://stackoverflow.com/questions/18552005/is-there-a-concise-way-to-iterate-over-a-stream-with-indices-in-java-8). – user1803551 Feb 15 '17 at 12:29
  • 2
    Your loop returns the *last* occurrence, not the first one. – Holger Feb 15 '17 at 13:49

2 Answers2

3

Well if you can guarantee that Map is actually a LinkedHashMap (so that it preserves insertion order), you can do something like this:

List<Map.Entry<String, Boolean>> l = map.entrySet().stream().collect(Collectors.toList());
    IntStream.range(0, l.size())
            .mapToObj(i -> new AbstractMap.SimpleEntry<>(i, l.get(i)))
            .filter(e -> e.getValue().getValue())
            .map(e -> new AbstractMap.SimpleEntry<>(e.getValue().getKey(), e.getKey()))
            .findFirst();
Eugene
  • 117,005
  • 15
  • 201
  • 306
3

I think it's not possible to fulfill all the conditions below:

  1. Solution should be lazy (stop iterating map once the answer is found)
  2. Solution should not use explicitly iterator() or spliterator()
  3. Solution should conform the Stream API spec (in particular intermediate lambdas should not have side effects)
  4. Solution should not use third-party Stream extensions.

If you ok with violating #1, check the @Eugene's answer. If you ok with violating #2, then your code in the question is nice. If you ok with violating #3, you can do something like this:

AtomicInteger idx = new AtomicInteger();

String name = map.entrySet().stream()
   .peek(e -> idx.incrementAndGet())
   .filter(Map.Entry::getValue)
   .map(Map.Entry::getKey)
   .findFirst().orElse(null);
int itr = idx.get();

If you're ok to violate #4, you may consider using my free StreamEx library:

Map.Entry<String, Integer> entry = StreamEx.of(map.entrySet()) // (name, bool)
       .zipWith(IntStreamEx.ints().boxed()) // ((name, bool), index)
       .filterKeys(Map.Entry::getValue) // filter by bool
       .mapKeys(Map.Entry::getKey) // (name, index)
       .findFirst()
       .orElse(null);
if(entry != null) {
    String name = entry.getKey();
    int itr = entry.getValue();
}
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • 1
    both options are really nice. one plus. – Eugene Feb 15 '17 at 14:11
  • 1
    Doesn’t StreamEx have `takeWhile`? `int index=StreamEx.of(map.values()).takeWhile(b->!b) .count(); String name=map.keySet().stream().skip(index).findFirst().orElse(null);`… – Holger Feb 15 '17 at 14:13
  • 1
    @Holger, yes, such solution is also possible, though it's two-pass. Btw, it's a good version as it's available in Java 9. Also possible with StreamEx `long idx = StreamEx.ofValues(map).indexOf(b->b).orElse(-1)`. Other single-pass StreamEx solutions also possible like `EntryStream.of(map).takeWhileInclusive(e -> !e.getValue()).keys().zipWith(IntStreamEx.ints().boxed()).reduce((a,b) -> b)`. – Tagir Valeev Feb 15 '17 at 14:19