5
\$\begingroup\$

Input is a string, and the method should return repeated characters in that string and the position of these characters.

I'm currently using Java 8 Stream. The problem is I have to do it by calling 2 times collectors. I am trying to find a way to combine all these operations in 1 single collector operations if possible.

Furthermore, the operation of mapping of characters and their position in a string is still performed in another method since I have not found a way to perform that.

Please give suggestion of improvement for the code:

public static void main(String args[]) {
    String str = "acbdaghfb";

    Map<Character,List<Integer>> map=charMaps(str);
    map.forEach((k,v)->System.out.println(k+"_"+v));
}

public static Map<Character,List<Integer>> charMaps(String str) {

    Map<Character, List<Integer>> maps = encodeString(str)
            .stream()
            .collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList())));

    maps = maps.entrySet()
                .stream()
                .filter(e -> e.getValue().size() > 1)
                .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

    return maps;
}

public static List<Pair<Character, Integer>> encodeString(String str) {
    List<Pair<Character, Integer>> pairs = new LinkedList<Pair<Character, Integer>>();
    char chars[] = str.toCharArray();

    for (int a = 0; a < chars.length; a++) {
        pairs.add(new Pair<Character, Integer>(chars[a], a));
    }
    return pairs;
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You do not need encodeString and you do not even need a Pair class. You only use this method to build a list of each index with the character of the string at that index. Instead, you can directly use an IntStream that will go over the indexes of each character of the String.

Map<Character, List<Integer>> map =
    IntStream.range(0, str.length())
             .boxed()
             .collect(Collectors.groupingBy(str::charAt, HashMap::new, Collectors.toList()));

This creates an IntStream going from 0 to the length of the String (excluded). We turn this primitive Stream into a Stream<Integer> using boxed: we will need to box each index to store them in a list anyway, and having a Stream of objects enable us to use the built-in groupingBy(classifier, mapFactory, downstream) collector.

This collector groups each element of the Stream by the classifier given. In this case, the classifier is a function returning the character at the index considered. It is using the method-reference str::charAt (which would be equivalent to the lambda expression i -> str.charAt(i)). The downstream collector is used to collect all elements that are classified to the same key; in this case, it is simply toList() which will collect the indexes into a list. By default, groupingBy isn't specified to return a specific Map implementation (the current Oracle JDK returns a HashMap but that cannot be relied upon). Since we're going to post-process that map, giving HashMap::new as the map supplier makes sure that we end up with a HashMap.

The Stream API doesn't offer a way to do a GROUP BY HAVING, like we would like here (in SQL jargon), so 2 passes are needed. The second one can be simplified though: instead of creating a new Stream pipeline, we can use

map.values().removeIf(l -> l.size() <= 1);

to remove every entry from the map where the value is a list that has a size less than 1. This works because values() returns a view of the map, so changes to it reflect through the map. And then removeIf gives us the ability to remove the elements matching the given predicate.

So all in all, you can have:

public static Map<Character,List<Integer>> charMaps(String str) {
    Map<Character, List<Integer>> map =
        IntStream.range(0, str.length())
                 .boxed()
                 .collect(Collectors.groupingBy(str::charAt, HashMap::new, Collectors.toList()));
    map.values().removeIf(l -> l.size() <= 1);
    return map;
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Since the List values will definitely contain at least one element (for unique characters), I'll suggest making the removeIf() predicate l -> l.size() == 1. +1 anyways. :) \$\endgroup\$
    – h.j.k.
    Aug 13, 2016 at 14:32

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.