I want to determine if a list is anagram or not using Java 8.
Example input:
"cat", "cta", "act", "atc", "tac", "tca"
I have written the following function that does the job but I am wondering if there is a better and elegant way to do this.
boolean isAnagram(String[] list) {
long count = Stream.of(list)
.map(String::toCharArray)
.map(arr -> {
Arrays.sort(arr);
return arr;
})
.map(String::valueOf)
.distinct()
.count();
return count == 1;
}
It seems I can't sort char array with Stream.sorted()
method so that's why I used a second map operator. If there is some way that I can operate directly on char stream instead of Stream of char array, that would also help.
Alternatively an updated version of your implementation that could work would be:
boolean isAnagram(String[] list) {
return Stream.of(list) // Stream<String>
.map(String::toCharArray) // Stream<char[]>
.peek(Arrays::sort) // sort
.map(String::valueOf) // Stream<String>
.distinct() //distinct
.count() == 1;
}
Instead of creating and sorting a char[]
or int[]
, which can not be done inline and thus "breaks" the stream, you could get a Stream
of the chars
in the Strings and sort those before converting them to arrays. Note that this is an IntSteam
, though, and String.valueOf(int[])
will include the array's memory address, which is not very useful here, so better use Arrays.toString
in this case.
boolean anagrams = Stream.of(words)
.map(String::chars).map(IntStream::sorted)
.map(IntStream::toArray).map(Arrays::toString)
.distinct().count() == 1;
Of course, you can also use map(s -> Arrays.toString(s.chars().sorted().toArray()))
instead of the series of four maps
. Not sure if there's a (significant) difference in speed, it's probably mainly a matter of taste.
Also, you could use IntBuffer.wrap
to make the arrays comparable, which should be considerably faster than Arrays.toString
(thanks to Holger in comments).
boolean anagrams = Stream.of(words)
.map(s -> IntBuffer.wrap(s.chars().sorted().toArray()))
.distinct().count() == 1;
I would not deal with counting distinct values, as that’s not what you are interested in. What you want to know, is whether all elements are equal according to a special equality rule.
So when we create a method to convert a String
to a canonical key (i.e. all characters sorted)
private CharBuffer canonical(String s) {
char[] array = s.toCharArray();
Arrays.sort(array);
return CharBuffer.wrap(array);
}
we can simply check whether all subsequent elements are equal to the first one:
boolean isAnagram(String[] list) {
if(list.length == 0) return false;
return Arrays.stream(list, 1, list.length)
.map(this::canonical)
.allMatch(canonical(list[0])::equals);
}
Note that for method references of the form expression::name
, the expression is evaluate once and the result captured, so canonical(list[0])
is evaluated only once for the entire stream operation and only equals
invoked for every element.
Of course, you can also use the Stream API to create the canonical keys:
private IntBuffer canonical(String s) {
return IntBuffer.wrap(s.chars().sorted().toArray());
}
(the isAnagram
method does not need any change)
Note that CharBuffer
and IntBuffer
can be used as lightweight wrappers around arrays, like in this answer, and implement equals
and hashCode
appropriately, based on the actual array contents.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With