Determine if a list composed of anagram elements in Java 8

和自甴很熟 提交于 2019-12-05 00:46:05

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.

I wouldn't sort the char array, as sorting is O(NlogN), which is not necessary here.

All we need is, for each word of the list, to count the occurrences of each character. For this, we're collecting each word's characters to a Map<Integer, Long>, with the keys being each character and the value being its count.

Then, we check that, for all the words in the array argument, we have the same count of characters, i.e. the same map:

return Arrays.stream(list)
    .map(word -> word.chars()
            .boxed().collect(Collectors.grouping(c -> c, Collectors.counting()))
    .distinct()
    .count() == 1;

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;
}

Or may be with a BitSet:

  System.out.println(stream.map(String::chars)
        .map(x -> {
            BitSet bitSet = new BitSet();
            x.forEach(bitSet::set);
            return bitSet;
        })
        .collect(Collector.of(
            BitSet::new,
            BitSet::xor,
            (left, right) -> {
                left.xor(right);
                return left;
            }
        ))
        .cardinality() == 0);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!