Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use Java Stream to find the average of all values that share a key?

I'm having a lot of trouble with trying to average the values of a map in java. My method takes in a text file and sees the average length of each word starting with a certain letter (case insensitive and goes through all words in the text file.

For example, let's say I have a text file that contains the following::

"Apple arrow are very common Because bees behave Cant you come home"

My method currently returns:

{A=5, a=8, B=7, b=10, c=10, C=5, v=4, h=4, y=3}

Because it is looking at the letters and finding the average length of the word, but it is still case sensitive.

It should return:

{A=5, a=8, B=7, b=10, c=10, C=5, v=4, h=4, y=3}

{a=4.3, b=5.5, c=5.0, v=4.0, h=4.0, y=3}

This is what I have so far.

public static Map<String, Integer> findAverageLength(String filename) {
    
     Map<String, Integer> wordcount = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
       
        try 
        {
            Scanner in = new Scanner(new File(filename));
            List<String> wordList = new ArrayList<>();
            while (in.hasNext()) 
            {
                wordList.add(in.next());
            }

            wordcount = wordList.stream().collect(Collectors.toConcurrentMap(w->w.substring(0,1), w -> w.length(), Integer::sum));
            System.out.println(wordcount);
            
        }
        
        catch (IOException e)
        {
            System.out.println("File: " + filename + " not found");
        }
                    
  return wordcount; 
}
like image 216
timtommy Avatar asked Dec 14 '20 10:12

timtommy


People also ask

How can we get an average of values of elements in stream operation?

IntStream average() method in Java The average() method of the IntStream class in Java returns an OptionalDouble describing the arithmetic mean of elements of this stream, or an empty optional if this stream is empty. It gets the average of the elements of the stream.

How will you get the average of all numbers present in a list using Java 8?

To calculate the arithmetic mean of the list, you can use the static Stats. meanOf() method.

What does stream of () method in Java?

Stream of(T t) returns a sequential Stream containing a single element. Syntax : static Stream of(T t) Parameters: This method accepts a mandatory parameter t which is the single element in the Stream. Return Value: Stream of(T t) returns a sequential Stream containing the single specified element.

How do you use an ArrayList for a stream?

All you need to do is first get the stream from List by calling stream() method, then call the filter() method to create a new Stream of filtered values and finally call the Collectors. toCollection(ArrayList::new) to collect those elements into an ArrayList.


Video Answer


3 Answers

You are almost there.

You could try the following.

  • We group by the first character of the word, converted to lowercase. This lets us collect into a Map<Character, …>, where the key is the first letter of each word. A typical map entry would then look like

    a = [ Apple, arrow, are ]
    
  • Then, the average of each group of word lengths is calculated, using the averagingDouble method. A typical map entry would then look like

    a = 4.33333333
    

Here is the code:

// groupingBy and averagingDouble are static imports from
// java.util.stream.Collectors
Map<Character, Double> map = Arrays.stream(str.split(" "))
    .collect(groupingBy(word -> Character.toLowerCase(word.charAt(0)),
        averagingDouble(String::length)));

Note that, for brevity, I left out additional things like null checks, empty strings and Locales.

Also note that this code was heavily improved responding to the comments of Olivier Grégoire and Holger below.

like image 117
MC Emperor Avatar answered Oct 10 '22 03:10

MC Emperor


You can try with the following:

String str = "Apple arrow are very common Because bees behave Cant you come home";
Map<String, Double> map = Arrays.stream(str.split(" "))
            .collect(Collectors.groupingBy(s -> String.valueOf(Character.toLowerCase(s.charAt(0))),
                    Collectors.averagingDouble(String::length)));

The split method will split the string into an array of strings using the delimiter " ". Then, you want to group by the average of the string length. Hence, the use the of Collectors.groupingBy method and the downstream parameter Collectors.averagingDouble(String::length). Finally, given the constraints that you have described we need to group by lower case (or up case) of the first char in the String (i.e., Character.toLowerCase(s.charAt(0)))).

and then print the map:

 map.entrySet().forEach(System.out::println);

If you do not need to keep the map structure you can do it in one go:

Arrays.stream(str.split(" "))
      .collect(Collectors.groupingBy(s -> String.valueOf(Character.toLowerCase(s.charAt(0))), Collectors.averagingDouble(String::length)))
      .entrySet().forEach(System.out::println);
like image 38
dreamcrash Avatar answered Oct 10 '22 01:10

dreamcrash


Just convert the first letter, which you obtain using substring, to the same case. Upper or lower, doesn't matter.

w.substring(0,1).toLowercase()
like image 43
Michael Avatar answered Oct 10 '22 02:10

Michael