确定在Java8中是否存在由字谜元素组成的列表
我想确定一个列表是否是字谜,是否使用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 -> {
"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());
}