从两个数组/切片中获取交集和差集的最有效方法是什么?

4

给定两个数组或切片,例如:

a := []int{1, 2, 3, 4, 5}
b := []int{3, 4, 5, 6, 7, 8, 9}

切片可能未排序,顺序不重要。

计算值的最有效方法是什么,以便您最终获得两个切片的公共元素和一个中存在但另一个中不存在的元素,即对于上面给出的两个数组,返回值将是:

common := []int{3, 4, 5}
inAButNotB := []int{1, 2}
inBButNotA := []int{6, 7, 8, 9}

将一个切片转换为映射并遍历该映射以查看值是否存在,计算交集非常容易。是否有一种方法可以在同一个循环中计算另外两个值?


你的数组(实际上是切片!)总是排序好的吗(就像示例中一样)?你需要保留原始切片还是可以更改它们? - ain
将两个切片的数据插入到一个映射中,然后检查元素出现的次数,这将告诉您哪个元素是共同的。 - Himanshu
1
数组切片可能未排序,且顺序无关紧要。 - ziyadparekh
这取决于大小和值的分布。在图书馆花些时间。所提出的问题与Go无关。 - Volker
这个回答解决了你的问题吗?如何在Go中获取两个切片的交集? - dikkini
1个回答

9

O(len(a) + len(b)) 是高效的。例如,

package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3, 4, 5}
    b := []int{3, 4, 5, 6, 7, 8, 9}
    fmt.Println(a)
    fmt.Println(b)

    m := make(map[int]uint8)
    for _, k := range a {
        m[k] |= (1 << 0)
    }
    for _, k := range b {
        m[k] |= (1 << 1)
    }

    var inAAndB, inAButNotB, inBButNotA []int
    for k, v := range m {
        a := v&(1<<0) != 0
        b := v&(1<<1) != 0
        switch {
        case a && b:
            inAAndB = append(inAAndB, k)
        case a && !b:
            inAButNotB = append(inAButNotB, k)
        case !a && b:
            inBButNotA = append(inBButNotA, k)
        }
    }
    fmt.Println(inAAndB)
    fmt.Println(inAButNotB)
    fmt.Println(inBButNotA)
}

演示代码:https://play.golang.org/p/RvGaC9Wfjiv

输出结果:

[1 2 3 4 5]
[3 4 5 6 7 8 9]
[3 4 5]
[1 2]
[8 6 7 9]

The Go Programming Language Specification

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer
我们对于uint8有8个比特位。比特0(1 << 0,1向左移动0位)代表a,比特1(1 << 1,1向左移动1位)代表b。对于uint8的比特位,00000001代表a00000010代表b00000011代表ab00000000代表既不是a也不是b|运算符用于设置某个比特位,&运算符用于读取某个比特位。

Go编程语言规范

映射类型

映射是一组无序的元素,元素类型为一种类型,称为元素类型,由另一种类型的一组唯一键索引,称为键类型。

对于键类型,必须完全定义比较操作符==和!=;因此,键类型不能是函数、映射或切片。如果键类型是接口类型,则必须为动态键值定义这些比较操作符;否则将引发运行时恐慌。

该算法适用于任何元素类型可以作为映射键的切片类型。对于键类型,必须完全定义比较操作符==和!=。

太棒了!我对位运算符不是很熟悉。你能解释一下这段代码的作用吗?for _, k := range a { m[k] |= (1 << 0) } for _, k := range b { m[k] |= (1 << 1) }以及这段代码:a := v&(1<<0) != 0 b := v&(1<<1) != 0 - ziyadparekh
我喜欢这个答案,但如果切片是[]struct{},它还能用吗? - ziyadparekh
@ziyadparekh 如果结构体中的所有内容都可以作为映射键,则结构体可以作为映射键。否则,您可能需要采用从排序数组开始的方法。 - twotwotwo
(更正之前的评论,如果您不能进行哈希,则可以根据需要按某种方式对一个数组进行排序。) - twotwotwo
1
这是一段代码,可以对a进行哈希或排序,从而得到a&b(交集)和a-b(差集)。(对于b-a,您可以使用相同的代码并交换ab来运行。)https://play.golang.org/p/FcX8OIAGVH4 - twotwotwo
1
@ziyadparekh:请查看我修改后的答案,以回答你在评论中提出的问题。 - peterSO

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