Definition
- **string.chars()** returns an `IntStream` of character codes. - **mapToObj(c -> (char) c)** boxes each into a `Character`. - **groupingBy(c -> c, counting())** groups identical characters and counts each group. - The result is `Map<Character, Long>` with the frequency of each character. - Use **LinkedHashMap::new** for first-seen order or **TreeMap::new** for sorted characters.
Priya at the National Bank of India goes through a reference code letter by letter and keeps a tally sheet: each letter gets a tick every time it appears. At the end, the sheet shows the count for each letter.
Understanding the Concepts
How do you count character frequencies?
Stream the characters, group by identity, and count each group.
package com.bank.core;
import java.util.*;
import java.util.stream.Collectors;
public class CharFrequency {
public static void main(String[] args) {
String input = "banana";
Map<Character, Long> freq = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c, Collectors.counting()));
freq.forEach((ch, count) ->
System.out.println("'" + ch + "': " + count));
}
}
'b': 1
'a': 3
'n': 2
How do you choose the map type?
| Map type | Order | Use case |
|---|---|---|
| HashMap (default) | None | When order does not matter |
| LinkedHashMap | First-seen order | For first non-repeating char detection |
| TreeMap | Sorted by character | For alphabetical histograms |
// Sorted alphabetically
Map<Character, Long> sorted = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c, TreeMap::new, Collectors.counting()));
sorted.forEach((ch, count) ->
System.out.println("'" + ch + "': " + count));
'a': 3
'b': 1
'n': 2
How do you use this for anagram detection?
Two strings are anagrams if they have the same character frequency map.
public static boolean areAnagrams(String a, String b) {
return charFrequency(a.toLowerCase())
.equals(charFrequency(b.toLowerCase()));
}
private static Map<Character, Long> charFrequency(String s) {
return s.chars()
.mapToObj(c -> (char) c)
.filter(c -> !Character.isWhitespace(c))
.collect(Collectors.groupingBy(
c -> c, Collectors.counting()));
}
System.out.println(areAnagrams("listen", "silent")); // true
System.out.println(areAnagrams("hello", "world")); // false
How do you handle case sensitivity and whitespace?
// Case-insensitive, no whitespace
Map<Character, Long> clean = input.toLowerCase().chars()
.mapToObj(c -> (char) c)
.filter(c -> !Character.isWhitespace(c))
.collect(Collectors.groupingBy(c -> c, Collectors.counting()));
What about emoji and supplementary characters?
chars() iterates individual char values. For text containing emoji, use codePoints().
// For emoji and supplementary characters:
Map<String, Long> emojiFreq = input.codePoints()
.mapToObj(cp -> new String(Character.toChars(cp)))
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
What are common mistakes?
Mistake: expecting Integer instead of Long from counting().
// WRONG: counting() returns Long
Map<Character, Integer> wrong = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c, Collectors.counting())); // compile error
// CORRECT: use Long
Map<Character, Long> correct = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c, Collectors.counting()));
What should a senior engineer know?
- This is the canonical frequency-count idiom and underpins anagram detection, duplicate detection, and histograms.
- counting() returns Long to avoid overflow on very large inputs.
- Decide what 'character' means: include/exclude whitespace, case-sensitive or not, BMP vs supplementary.
- For performance on very large strings, a simple int[] array indexed by character value avoids Map overhead.
What are common interview follow-up questions?
- "How do you find the most frequent character?" max(comparingByValue()) on the frequency map. - "How do you find characters that appear exactly once?" filter(count == 1). - "How do you count word frequencies instead?" Split by whitespace and groupBy(word -> word, counting()).
Visual Overview
String: "banana"
│
│ chars() → mapToObj
▼
Stream<Character> ['b', 'a', 'n', 'a', 'n', 'a']
│
│ groupingBy(c, counting())
▼
Map<Character, Long>
┌──────────────┐
│ 'b' → 1 │
│ 'a' → 3 │
│ 'n' → 2 │
└──────────────┘
Key Takeaway
To count character frequencies, stream the string with chars(), box to Character, and collect with groupingBy(c -> c, counting()). The result is a Map
This is the canonical frequency-count idiom that underpins anagram checks, duplicate detection, and histograms. At the National Bank of India, it was used to build character histograms of account references to flag anomalous patterns.
Choose the map type based on your needs: LinkedHashMap for first-seen order, TreeMap for alphabetical order, or the default HashMap when order does not matter. Decide explicitly about case, whitespace, and surrogate pairs.
Remember one sentence: groupingBy(c -> c, counting()) is the canonical character-frequency idiom, and counting() returns Long to stay safe for large inputs.
---