3

I want to create a HashMap<String,Integer> from an existing HashMap<String,Integer> by applying some operations on the key of the Map. Say suppose I have a String->

String sampleString= "SOSSQRSOP";` 

then created a hashmap by taking only 3 characters from string like below(putting 0 as value):

Map<String, Integer> messages= new HashMap<>();
messages.put("SOS",0); 
messages.put("SQR",0);
messages.put("SOP",0);

The Actual task is to find total no of different characters from given string "SOS" with each key in the map and assign the no to value of each key. Like below (End result):

Map<String, Integer> messages= new HashMap<>();
messages.put("SOS",0);
messages.put("SQR",2);
messages.put("SOP",1);

so I wrote code in java8 using stream given below:

    Map<String,Integer>  result= messages
            .entrySet().stream()
            .collect(Collectors.toMap(e-> e.getKey(),
                    e-> e.getKey().stream()
                         .forEach(x-> {
                                if(!"SOS".equals(x)){
                                    char[] characters= {'S','O','S'};
                                    char[] message= x.toCharArray();
                                    for(int i=0; i< characters.length;i++){
                                        int index=0;
                                        if(characters[i] != message[i]){
                                            messages.put(e.getKey(),++index);
                                        }
                                    }
                                }
                            });
                    ));

I am getting compile error. Can anybody help me write the code using stream.

Edited: Also please describe other approaches to do this. BTW creating first hashmap from given string was required in my case.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
Nipun
  • 73
  • 1
  • 9

5 Answers5

7

There is no need to premake the HashMap. Stream collector toMap will make it for you:

import static java.util.stream.Collectors.toMap;

Map<String, Integer> result = Stream.of("SOS", "SQR", "SOP")
        .collect(toMap(
                s -> s,
                s -> (int) IntStream.range(0, 3)
                        .filter(i -> "SOS".charAt(i) != s.charAt(i))  // assume all words are 3-letters
                        .count()
         ));

However, if you already have the map and want to modify it, use replaceAll:

messages.replaceAll(
        (s, unused) -> (int) IntStream.range(0, 3)
                .filter(i -> "SOS".charAt(i) != s.charAt(i))
                .count()
);

If your task is to split the source message and compare each triplet against the first 3 chars, you can combine it all in one stream expression:

String message = "SOSSQRSOP";
int n = 3;

assert message.length() % n == 0;

Map<String, Integer> messages = IntStream.range(0, message.length() / n)
        .map(i -> i * n) // starting points of the n-grams
        .mapToObj(idx -> message.substring(idx, idx + n))
        .collect(toMap(
                group -> group,
                group -> (int) IntStream.range(0, n)
                        .filter(i -> message.charAt(i) != group.charAt(i))
                        .count()
        ));
Misha
  • 27,433
  • 6
  • 62
  • 78
  • Perhaps, replacing `3` with `"SOS".length()` would make it cleaner. – Holger Jun 12 '18 at 07:21
  • The first one is really nice. Also, one might assume that the distances are to be calculated from the first String that is found, which would make this `Stream.of(array)... .filter(i -> array[0].charAt(i) != s.charAt(i))`. What's the difference between `Stream.of(...)` and ´Arrays.stream(...)` anyway? – daniu Jun 12 '18 at 07:23
  • @Holger It also assumes that all other words are also at least 3 characters long. I was just trying to keep out anything that does not directly pertain to the issue the OP is having to avoid drowning out the important parts – Misha Jun 12 '18 at 07:24
  • 1
    @daniu `Stream.of` is a *varargs* method, hence, allows to write `Stream.of(element1, element2, ...)` – Holger Jun 12 '18 at 07:25
  • @daniu We don't know all the details of the task that OP is trying to solve. Hopefully, this is enough to address his immediate issue and he can modify it as needed for his actual requirements – Misha Jun 12 '18 at 07:26
  • @Misha well, yes, the OP said that `"SOSSQRSOP"` has been split into three-character string, so that's given point. Now, the question is whether to treat `3` or `"SOS"` as starting point... – Holger Jun 12 '18 at 07:26
  • @Holger Here, based on the best guess of what OP is trying to do, I made it nice and clean – Misha Jun 12 '18 at 07:44
  • You could replace `.boxed()` with `.mapToObj(idx -> message.substring(idx, idx + n)`, that will not only avoid boxing to `Integer`, it allows simplifying *both* `toMap` functions. – Holger Jun 12 '18 at 08:29
  • @Misha I like the way you implemented the count. As a suggestion, you could shorten the split by using `Pattern.compile("(?<=\\G.{" + n + "})").splitAsStream("SOSSQRSOP")` (see [this question](https://stackoverflow.com/questions/3760152/split-string-to-equal-length-substrings-in-java)). – Malte Hartwig Jun 12 '18 at 09:39
4

You can't use forEach in the value mapper, since it doesn't return a value.

String x = "SOS";
Map<String,Integer> result = messages
        .entrySet().stream()
        .collect(Collectors.toMap(e-> e.getKey(),
                                  e-> {
                                          int count = 0;
                                          for (int i = 0; i < characters.length; i++){
                                              if (e.getKey().charAt(i) != x.charAt(i)) {
                                                  count++;
                                              }
                                          }
                                          return count;
                                      }));
Eran
  • 387,369
  • 54
  • 702
  • 768
3

The best you can do IMO is not using Streams at all, but replaceAll:

Map<String, Integer> messages = new HashMap<>();
// sample entries
messages.put("SOS", 0);
messages.put("SQR", 0);
messages.put("SOP", 0);

messages.replaceAll((k, v) -> {
    // calculate new value for each entry
    int diff = 0;
    for (int i = 0; i < "SOS".length(); i++) {
        if ("SOS".charAt(i) != k.charAt(i)) {
            diff++;
        }
    }
    return diff;
});
System.out.println(messages);

Output

{SQR=2, SOP=1, SOS=0}

daniu
  • 14,137
  • 4
  • 32
  • 53
1

I assume, the input you have is a String containing all the characters. So you could skip the creation of the initial map and split the string into chunks of 3, mapping each resulting String to a key, and using some formula for calculating the diff for the value.

    String sampleString= "SOSSQRSOP";
    final char[] SOS = "SOS".toCharArray();
    Map<String, Integer> result = IntStream.rangeClosed(0, sampleString.length() / 3)
                                           .mapToObj(i -> sampleString.substring(3*i, Math.min(3*i+3, sampleString.length())))
                                           .filter(s -> !s.isEmpty())
                                           .collect(Collectors.toMap(s -> s,
                                                                     s -> (int) IntStream.range(0, s.length())
                                                                                         .filter(i -> SOS[i] != s.charAt(i))  
                                                                                         .count()));

    result.forEach((k,v) -> System.out.println(k + " -> " + v));

Regarding your own implementation:

for(int i=0; i< characters.length;i++){
   int index=0; //<-- this will always set the index to 0
   if(characters[i] != message[i]){
     messages.put(e.getKey(),++index); //<-- this sets the index to one before assigning it to the map entry value, resulting in being 1, always
   }
}
Gerald Mücke
  • 10,724
  • 2
  • 50
  • 67
0

You are doing this: e.getKey().stream(), which means you are trying to stream over a single value, because your key is a String, and this is not possible.

You can do e.getKey().chars().forEach(...); and you will get the expected result or you can do something like e.codePoints().forEach(...).

If you use one of the above methods you will also need to cast the entries to chars because codePoints() and chars() return IntStreams.

As @Holger mentioned in the comment, my first answer contained the following snippet Stream.of(e.getKey().toCharArray()) which will give you a stream with a single char[].

Definitely, there are better ways of doing what you want to do, but your question was about the compilation error.

LoolKovsky
  • 1,018
  • 1
  • 12
  • 29
  • 1
    `Stream.of(e.getKey().toCharArray())` would create a stream with a single `char[]` element. – Holger Jun 12 '18 at 07:23