确定在Java8中是否存在由字谜元素组成的列表

确定在Java8中是否存在由字谜元素组成的列表,java,java-8,java-stream,Java,Java 8,Java Stream,我想确定一个列表是否是字谜,是否使用Java8 输入示例: "cat", "cta", "act", "atc", "tac", "tca" 我已经编写了以下函数来完成这项工作,但我想知道是否有更好、更优雅的方法来完成这项工作 boolean isAnagram(String[] list) { long count = Stream.of(list) .map(String::toCharArray) .map(arr -> {

我想确定一个列表是否是字谜,是否使用Java8

输入示例:

"cat", "cta", "act", "atc", "tac", "tca"
我已经编写了以下函数来完成这项工作,但我想知道是否有更好、更优雅的方法来完成这项工作

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;

}

似乎我不能用
Stream.sorted()
方法对字符数组进行排序,所以我使用了第二个map操作符。如果有某种方法可以直接对char stream而不是char数组的stream进行操作,那也会很有帮助。

而不是创建和排序
char[]
int[]
,后者无法内联完成,因此会“中断”流,您可以获得字符串中
字符的
,并在将其转换为数组之前对其进行排序。请注意,这是一个
IntSteam
,而
String.valueOf(int[])
将包括数组的内存地址,这在这里不是很有用,因此在这种情况下最好使用
Arrays.toString

boolean anagrams = Stream.of(words)
        .map(String::chars).map(IntStream::sorted)
        .map(IntStream::toArray).map(Arrays::toString)
        .distinct().count() == 1;
当然,您也可以使用
map(s->Arrays.toString(s.chars().sorted().toArray())
而不是四个
映射的序列。不确定速度是否有(显著)差异,这可能主要是口味的问题

此外,还可以使用
IntBuffer.wrap
使数组具有可比性,这应该比
arrays.toString
快得多(感谢注释中的)


我不会对字符数组进行排序,因为排序是
O(NlogN)
,这里不需要排序

我们所需要的是,对于列表中的每个单词,计算每个字符的出现次数。为此,我们将每个单词的字符收集到一个
映射
,其中键为每个字符,值为其计数

然后,我们检查数组参数中的所有单词是否具有相同的字符数,即相同的映射:

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

或者可以是带有
位集的

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

或者,可以工作的实现的更新版本是:

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;
}
布尔isAnagram(字符串[]列表){
返回Stream.of(list)//Stream
.map(字符串::toCharArray)//流
.peek(数组::排序)//排序
.map(字符串::valueOf)//流
.distinct()//distinct
.count()==1;
}

我不会处理计算不同值的问题,因为这不是您感兴趣的。您想知道的是,根据特殊的相等规则,所有元素是否相等

因此,当我们创建一个方法将
字符串
转换为规范键(即所有已排序的字符)时

我们只需检查所有后续元素是否等于第一个元素:

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);
}
请注意,对于形式为
expression::name
的方法引用,表达式计算一次并捕获结果,因此
canonical(list[0])
对于整个流操作只计算一次,对于每个元素只调用
equals

当然,您也可以使用流API创建规范密钥:

private IntBuffer canonical(String s) {
    return IntBuffer.wrap(s.chars().sorted().toArray());
}
(无需对
isAnagram
方法进行任何更改)


请注意,
CharBuffer
IntBuffer
可以用作数组的轻量级包装器,就像在这个答案中一样,并根据实际数组内容适当地实现
equals
hashCode

虽然这是真的,但我想知道这是更快还是更慢,假设这个方法可能会带来更大的开销(?),并且我认为所有的字符串都相对较小(比如最多不超过几十个字符)。如果我不使用Streams,我会使用HashMaps实现类似的东西@tobias_k在许多语言中,大多数单词都由少量字母组成,这使得
O(NlogN)
几乎可以忽略不计,正如您所提到的。现在我想知道这两种方法的记忆印记是什么,以及一种方法在速度上是否对另一种方法有利。@tobias_k对于短字符串,你是对的,但对于这些,这可能并不重要。@Limonkufu我认为tobias和Peter的评论是正确的。也许一个
映射
比在适当位置排序小数组的开销更大。无论如何,如果你追求性能,你根本不应该使用流,也就是说,传统的迭代方法会更快,我相信…@tobias_k同意,你的观点是正确的。我认为最终,这将取决于投入。也许我的解决方案对更长的字符串更好,而你的解决方案对一般情况更有效……回答不错,+1来自我。虽然我个人会分割单词的映射并列出它本身,而不是使用四个映射@Michael当您包装数组时,您可以免费获得它,
CharBuffer.wrap(array)
如果是
char[]
IntBuffer.wrap(array)
如果是
int[]
。无需昂贵地转换为
字符串
…通常,这是方法引用过度使用的典型示例。单个
.map(s->Arrays.toString(s.chars().sorted().toArray())
.map(s->IntBuffer.wrap(s.chars().sorted().toArray())
(参见我之前的评论),读起来要好得多…@Holger是的,你提到过。我在最后一段中添加了这一点,但我想这是一个模棱两可的问题。“不确定是否更快”这一部分是关于map和lambda位的。重写了最后一部分。是的,这是一个更好的措辞。我还假设,有方法引用的多个
map
步骤和单个
map
步骤之间没有明显的速度差异(与比较)我认为,单个lambda表达式在这里更可读。看起来,如果列表是空的,返回false是很奇怪的,但是如果列表有一个元素,则返回true。@米迦勒,这是作者必须做出的选择。您可以考虑返回<代码> true <代码>,或者故意抛出异常,如E
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);
}
private IntBuffer canonical(String s) {
    return IntBuffer.wrap(s.chars().sorted().toArray());
}