确定一个列表是不是由 Java 8 中的字谜元素组成
Posted
技术标签:
【中文标题】确定一个列表是不是由 Java 8 中的字谜元素组成【英文标题】:Determine if a list composed of anagram elements in Java 8确定一个列表是否由 Java 8 中的字谜元素组成 【发布时间】:2019-04-02 12:51:33 【问题描述】:我想使用 Java 8 确定一个列表是否是字谜。
示例输入:
"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()
方法对字符数组进行排序,所以这就是我使用第二个地图运算符的原因。如果有某种方法可以直接对 char 流而不是 char 数组的 Stream 进行操作,那也会有所帮助。
【问题讨论】:
【参考方案1】:您可以在字符串中获得chars
的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()))
代替四个maps
的系列。不确定速度是否存在(显着)差异,这可能主要是口味问题。
此外,您可以使用IntBuffer.wrap
使数组具有可比性,这应该比Arrays.toString
快得多(感谢cmets 中的Holger)。
boolean anagrams = Stream.of(words)
.map(s -> IntBuffer.wrap(s.chars().sorted().toArray()))
.distinct().count() == 1;
【讨论】:
不错的答案,来自我的 +1。虽然我个人会拆分单词的映射并列出本身,而不是使用四个映射。 It still does exactly the same and outputs the same results, though. @Michael 你在包装数组时免费获得它,CharBuffer.wrap(array)
在char[]
的情况下,IntBuffer.wrap(array)
在int[]
的情况下。无需昂贵的转换为String
...
一般来说,这是一个典型的方法引用过度使用的例子。单个.map(s -> Arrays.toString(s.chars().sorted().toArray())
或.map(s -> IntBuffer.wrap(s.chars().sorted().toArray())
(请参阅我之前的评论),阅读效果要好得多……
@Holger 是的,你提到了。我在最后一段中添加了这一点,但我想这有点模棱两可。 “不确定是否更快”部分只是关于地图与 lambda 位。改写了最后一部分。
是的,这是一个更好的措辞。我还假设带有方法引用的多个map
步骤和单个map
步骤之间没有显着的速度差异(与this Q&A 相比)。我只是认为这里的单个 lambda 表达式更具可读性。【参考方案2】:
我不会处理不同值的计数,因为这不是您感兴趣的。您想知道的是,根据特殊的相等规则,所有元素是否相等。
因此,当我们创建一个将String
转换为规范键(即所有字符排序)的方法时
private CharBuffer canonical(String s)
char[] array = s.toCharArray();
Arrays.sort(array);
return CharBuffer.wrap(array);
我们可以简单地检查所有后续元素是否等于第一个:
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
。
当然,您也可以使用 Stream API 创建规范密钥:
private IntBuffer canonical(String s)
return IntBuffer.wrap(s.chars().sorted().toArray());
(isAnagram
方法不需要做任何改动)
请注意,CharBuffer
和 IntBuffer
可以用作数组周围的轻量级包装器,就像在这个答案中一样,并根据实际数组内容适当地实现 equals
和 hashCode
。
【讨论】:
看起来不错。但是,我确实认为如果列表为空则返回 false 很奇怪,但如果列表有一个元素则返回 true。 @Michael 这是一个选择,作者必须做出。您可以考虑返回true
或故意抛出异常,因为空集合是否由字谜组成的问题毫无意义,但是,我决定模仿 OP 原始代码的行为,因为检查不同计数是否为一个将零元素导致false
。【参考方案3】:
我不会对 char 数组进行排序,因为排序是 O(NlogN)
,这里没有必要。
对于列表中的每个单词,我们只需要计算每个字符的出现次数。为此,我们将每个单词的字符收集到 Map<Integer, Long>
,其中键是每个字符,值是它的计数。
然后,我们检查,对于数组参数中的所有单词,我们有相同的字符数,即相同的映射:
return Arrays.stream(list)
.map(word -> word.chars()
.boxed().collect(Collectors.grouping(c -> c, Collectors.counting()))
.distinct()
.count() == 1;
【讨论】:
虽然这是真的,但我想知道这是否更快,甚至更慢,前提是这种方法可能会带来更大的开销(?)并且我认为所有字符串都相对较小(比如说最多不超过几十个字符)。 如果我不使用 Streams,我会使用 HashMaps 实现类似的东西。 @tobias_k 在许多语言中,大多数单词由少量字母组成,这会将O(NlogN)
减少到您提到的几乎可以忽略不计。现在我想知道这两种方法的记忆印记是什么,以及一种在速度方面是否比另一种更有优势。
@tobias_k 对于短字符串,你是对的,但对于这些,它可能无论如何都无关紧要。
@Limonkufu 我认为 Tobias 和 Peter 的 cmets 是有效的。也许Map
的开销比就地排序小数组要多。无论如何,如果你追求性能,你根本不应该使用流,即传统的迭代方法会更快,我相信......
@tobias_k 同意,你有一个有效的观点。我认为最终,这一切都取决于输入。也许我的解决方案更适合较长的字符串,而您的解决方案更适合一般情况......【参考方案4】:
或者,可以工作的实施的更新版本是:
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;
【讨论】:
【参考方案5】:或者可能是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);
【讨论】:
以上是关于确定一个列表是不是由 Java 8 中的字谜元素组成的主要内容,如果未能解决你的问题,请参考以下文章