Groovy: a.intersect( b ) 和 b.intersect( a ) 的区别

10

为什么在Groovy中,当我创建两个列表时,执行a.intersect(b)和b.intersect(a)会有差异?

def list1 = ["hello", "world", "world"];
def list2 = ["world", "world", "world"];

println( "Intersect list1 with list2: " + list1.intersect( list2 ) );
println( "Intersect list2 with list1: " + list2.intersect( list1) );

跟踪:

Intersect list1 with list2: [world, world, world]
Intersect list2 with list1: [world, world]

如果所有的数组都包含唯一的元素,那么它将像正常情况一样工作。一旦你开始添加重复的元素,它就会变得奇怪:

(你可以在http://groovyconsole.appspot.com/上复制它以进行测试)

def list1 = ["hello", "world", "test", "test"];
def list2 = ["world", "world", "world", "test"];

println( "Intersect list1 with list2: " + list1.intersect( list2 ) );
println( "Intersect list2 with list1: " + list2.intersect( list1 ) );

追踪:

Intersect list1 with list2: [world, world, world, test]
Intersect list2 with list1: [world, test, test]
我认为使用 intersect() 方法的整个意图就是为了给你公共元素,所以无论你将它们放在哪个顺序中都没有关系? 如果不是这种情况,我怎样才能仅获取共同的元素(数组中的重复项除外)?例如,示例一应该返回["world", "world"],示例二应该返回["world", "test"] 编辑 稍作澄清,此代码应该测试用户数据是否仍然相同(假设他们在某些事情中断开连接,并且我们想确保数据没有被篡改,或者处于与之前相同的状态)。 列表的顺序不能保证(用户可能重新排序它,但在技术上仍然是“相同的”),并且重复项是可能的。 因此,像["one", "one", "two"]这样的内容应该与["two", "one", "one"]匹配,而列表中任何添加或数据更改都不应该匹配。

2
注意:在Groovy中,def array1 = ["hello"、"world"、"world"];创建一个数组。它是一个List(具体来说是一个ArrayList)! - Joachim Sauer
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
16

如果您查看Collection.intersect的源代码, 您可以看到该方法的逻辑遵循以下流程:

对于两个集合leftright

  1. 如果left小于right,则交换leftright
  2. 将所有left添加到一个Set中(删除重复项)
  3. 对于right中的每个元素,如果存在于leftSet中,则将其添加到结果中

因此,对于您最后的两个示例;

def array1 = ["hello", "world", "test", "test"]
def array2 = ["world", "world", "world", "test"]

array1.intersect( array2 )会给出以下结果(如果我们用Groovy编写相同的算法):

leftSet = new TreeSet( array1 ) // both same size, so no swap
// leftSet = [ 'hello', 'world', 'test' ]
right   = array2
result = right.findAll { e -> leftSet.contains( e ) }

如果你运行它,你会发现结果的值为[world, world, world, test](正如你所发现的)。这是因为right中的每个元素都可以在leftSet中找到。

不确定为什么第一个例子应该返回["world","world"]...

稍后...

所以,我认为你正在寻找的东西可能是这样的:

def array1 = ["hello", "world", "test", "test"]
def array2 = ["world", "world", "world", "test"]
def intersect1 = array1.intersect( array2 ) as TreeSet
def intersect2 = array2.intersect( array1 ) as TreeSet
assert intersect1 == intersect2
为了解决集合中的重复项,因此intersect1intersect2将相等。
[test, world]

稍后仍然

我相信这是你想要的:

[array1,array2]*.groupBy{it}.with { a, b -> assert a == b }

不确定为什么第一个例子应该返回["world","world"]-基本上我想看看列表在两个不同的时间点是否发生了变化(服务器代码-我想确保用户回来时拥有相同的数据,并且没有被篡改)。在第一个示例中,每个列表中都有2个“world”的出现,这就是为什么我正在寻找像["world", "world"]这样的结果。 - divillysausages
谢谢提供的代码示例,但是它不能处理像array1中这样的内容 ["hello", "world", "world"]; 而array2["hello", "hello", "world"]; - 它们都包含重复项,但使用TreeSet将假定两个列表相同(因为重复项被删除)。 - divillysausages
@divilly 啊...没错...你能详细解释一下你想做什么吗?为什么 assert ["hello", "world", "world"] != ["hello", "hello", "world"] 不够用呢?我不确定 intersection 能帮到你...但是凭借我们的联合力量,我们一定能想出一个可行的算法... - tim_yates
好的,使用以下方法解决了问题: 1)先检查array1和array2的大小是否相同,如果不同,则操作失败。 2)def array3 = new ArrayList(array1); 3)array2.each {array3.remove(it);} 4)最终检查array3的大小是否为0,若为0,则表示它们相同。 - divillysausages
@tim_yates 对于最后一个问题,你能不能只写成 assert array1.sort() == array2.sort()?这样更容易理解发生了什么! - OverZealous
@Over 是的...虽然你可能想要 Groovy 1.8.1+ 和 array1.sort(false),否则 sort 实际上会改变 array1 的顺序。 - tim_yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,