【问题标题】:Compare two slices比较两个切片
【发布时间】:2014-07-15 05:25:28
【问题描述】:

Go 中有没有办法比较两个切片并获取切片 X 中不在切片 Y 中的元素,反之亦然?

    X := []int{10, 12, 12, 12, 13}
    Y := []int{12, 14, 15}

func compare(X, Y []int)  

calling compare(X, Y)   
    result1 := []int{10, 12, 12, 13} // if you're looking for elements in slice X that are not in slice Y

calling compare(Y, X)
    result2 := []int{14, 15} // if you're looking for elements in slice Y that are not in slice X

【问题讨论】:

    标签: go comparison slice


    【解决方案1】:

    如果顺序不重要,而且集合很大,你应该使用集合实现,并使用它的 diff 函数来比较它们。

    集合不是标准库的一部分,但您可以使用这个库,例如,您可以使用它从切片中自动初始化集合。 https://github.com/deckarep/golang-set

    类似这样的:

    import (
        set "github.com/deckarep/golang-set"
        "fmt"
        )
    
    func main() {
        //note that the set accepts []interface{}
        X := []interface{}{10, 12, 12, 12, 13}
        Y := []interface{}{12, 14, 15}
    
        Sx := set.NewSetFromSlice(X)
        Sy := set.NewSetFromSlice(Y)
        result1 := Sx.Difference(Sy)
        result2 := Sy.Difference(Sx)
    
        fmt.Println(result1)
        fmt.Println(result2)
    }
    

    【讨论】:

    • 我正在寻找一个独立于任何外部库的实现,排序并不重要..
    • @jwesonga 你可以把这个库放在你的源代码树中,为什么还要实现另一个集合?
    • 正如 miku 指出的,我正在寻找一个更简单的实现,一个自定义函数
    • @jwesonga 恕我直言,在这种情况下,您不应该大汗淋漓——使用全套实现并不会降低效率或其他任何事情,而且您可以将花在实现它上的时间用于更重要的事情上——比如其他特性。或者就 SO 进行辩论;)
    • @jwesonga,有人把你的问题发到/r/golang,好像也有代码……
    【解决方案2】:

    提供的所有解决方案都无法准确回答所提出的问题。解决方案不是切片中的差异,而是提供切片中元素集的差异。

    具体来说,而不是预期的示例:

        X := []int{10, 12, 12, 12, 13}
        Y := []int{12, 14, 15}
    
    func compare(X, Y []int)  
    
    calling compare(X, Y)   
        result1 := []int{10, 12, 12, 13} // if you're looking for elements in slice X that are not in slice Y
    
    calling compare(Y, X)
        result2 := []int{14, 15}
    

    提供的解决方案将导致:

    result1 := []int{10,13}
    result2 := []int{14,15}
    

    要严格生成示例结果,需要使用不同的方法。这里有两个解决方案:

    如果切片已经排序:

    如果您对切片进行排序,然后调用 compare,此解决方案可能会更快。如果您的切片已经排序,肯定会更快。

    func compare(X, Y []int) []int {
        difference := make([]int, 0)
        var i, j int
        for i < len(X) && j < len(Y) {
            if X[i] < Y[j] {
                difference = append(difference, X[i])
                i++
            } else if X[i] > Y[j] {
                j++
            } else { //X[i] == Y[j]
                j++
                i++
            }
        }
        if i < len(X) { //All remaining in X are greater than Y, just copy over
            finalLength := len(X) - i + len(difference)
            if finalLength > cap(difference) {
                newDifference := make([]int, finalLength)
                copy(newDifference, difference)
                copy(newDifference[len(difference):], X[i:])
                difference = newDifference
            } else {
                differenceLen := len(difference)
                difference = difference[:finalLength]
                copy(difference[differenceLen:], X[i:])
            }
        }
        return difference
    }
    

    Go Playground version

    使用地图的未排序版本

    func compareMapAlternate(X, Y []int) []int {
        counts := make(map[int]int)
        var total int
        for _, val := range X {
            counts[val] += 1
            total += 1
        }
        for _, val := range Y {
            if count := counts[val]; count > 0 {
                counts[val] -= 1
                total -= 1
            }
        }
        difference := make([]int, total)
        i := 0
        for val, count := range counts {
            for j := 0; j < count; j++ {
                difference[i] = val
                i++
            }
        }
        return difference
    }
    

    Go Playground version

    编辑:我创建了一个基准来测试我的两个版本(地图稍作修改,从地图中删除零值)。它无法在 Go Playground 上运行,因为 Time 无法在其上正常运行,所以我在自己的计算机上运行它。

    compareSort 对切片进行排序并调用 compare 的迭代版本,compareSorted 在 compareSort 之后运行,但依赖于已排序的切片。

    Case: len(X)== 10000 && len(Y)== 10000
    --compareMap time: 4.0024ms
    --compareMapAlternate time: 3.0225ms
    --compareSort time: 4.9846ms
    --compareSorted time: 1ms
    --Result length == 6754 6754 6754 6754
    Case: len(X)== 1000000 && len(Y)== 1000000
    --compareMap time: 378.2492ms
    --compareMapAlternate time: 387.2955ms
    --compareSort time: 816.5619ms
    --compareSorted time: 28.0432ms
    --Result length == 673505 673505 673505 673505
    Case: len(X)== 10000 && len(Y)== 1000000
    --compareMap time: 35.0269ms
    --compareMapAlternate time: 43.0492ms
    --compareSort time: 385.2629ms
    --compareSorted time: 3.0242ms
    --Result length == 3747 3747 3747 3747
    Case: len(X)== 1000000 && len(Y)== 10000
    --compareMap time: 247.1561ms
    --compareMapAlternate time: 240.1727ms
    --compareSort time: 400.2875ms
    --compareSorted time: 17.0311ms
    --Result length == 993778 993778 993778 993778
    

    如您所见,如果不使用地图对数组进行排序,则速度要快得多,但使用地图比对其进行排序然后使用迭代方法要快得多。对于小型案例,排序可能足够快,应该使用它,但基准测试会很快完成以进行计时。

    【讨论】:

    • 请注意,在排序版本中,// All remaining in X are greater than Y, just copy overif 块可以显着简化为使用子切片 remaining := X[i:]difference = append(difference, remaining...),Go 将自动处理检查容量和复制.
    【解决方案3】:

    这样的事情应该可以工作:

    package main
    
    import "fmt"
    
    func main() {
        X := []int{10, 12, 12, 12, 13}
        Y := []int{12, 14, 15}
    
        fmt.Println(compare(X, Y))
        fmt.Println(compare(Y, X))
    }
    
    func compare(X, Y []int) []int {
        m := make(map[int]int)
    
        for _, y := range Y {
            m[y]++
        }
    
        var ret []int
        for _, x := range X {
            if m[x] > 0 {
                m[x]--
                continue
            }
            ret = append(ret, x)
        }
    
        return ret
    }
    

    http://play.golang.org/p/4DujR2staI

    【讨论】:

    • 看看相同的代码是否可以重构为在 O(n) 而不是 O(n^2) 上运行会很有趣
    • 这段代码的复杂度实际上是 O(n*log(n)) (如果地图复杂度是 O(log(n)),我认为它实际上更低,因为它是作为一个 (multi-级别)哈希图)。
    • 我不确定如果切片不排序是否可以提高性能。如果它们是类似 Laremere 的方法,则可以使用。
    【解决方案4】:

    拖入一个集合实现可能是矫枉过正。 This should be enough:

    func diff(X, Y []int) []int {
    
      diff := []int{}
      vals := map[int]struct{}{}
    
      for _, x := range X {
        vals[x] = struct{}{}
      }
    
      for _, x := range Y {
        if _, ok := vals[x]; !ok {
          diff = append(diff, x)
        }
      }
    
      return diff
    }
    

    如果切片变得非常大,可以添加布隆过滤器。

    【讨论】:

    • 您的实施不会产生任何结果
    • 恕我直言,拖入一个复制粘贴的集合的一半实现比导入一个功能相同的集合库更糟糕,前提是它写得很好并且不臃肿。
    • @Not_a_Golfer,我认为在某些用例中,使用小型自定义函数而不是外部库不会让您的情况更糟。此外,即使您大部分时间都在使用该库,您也可能希望了解一个简单的版本。
    • @iliacholy Go 以如此美妙的方式促进了代码重用,在这种情况下重新发明轮子似乎是多余的。但这值得商榷。依赖关系确实有其隐藏的成本。但话又说回来,为什么要重新发明轮子?
    • “Go 以如此美妙的方式促进代码重用”缺乏依赖版本控制?此外,它是 17 位置......并不是完全重新发明轮子。
    【解决方案5】:

    如果您不想使用自定义算法重新发明轮子(然后维护它),您可以使用github.com/r3labs/diff/v2

    差异返回一个更改日志,您可以检查它以准确找到更改的内容,格式为 create/update/delete 条目。更改日志也可以编组为 JSON:

    package main
    
    import (
        "encoding/json"
        "github.com/r3labs/diff/v2"
        "os"
    )
    
    func main() {
        x := []int{10, 12, 12, 12, 13}
        y := []int{12, 14, 15}
        ch, _ := diff.Diff(x, y)
        json.NewEncoder(os.Stdout).Encode(ch)
    }
    

    它打印更新日志:

    [{"type":"delete","path":["0"],"from":10,"to":null},{"type":"update","path":["2"],"from":12,"to":15},{"type":"delete","path":["3"],"from":12,"to":null},{"type":"delete","path":["4"],"from":13,"to":null},{"type":"create","path":["1"],"from":null,"to":14}]
    

    变更日志主要描述了从第一个切片获取第二个切片所需的操作。

    该库还提供类似补丁的功能。您可以将更改日志直接传递到 diff.Patch 并将更改应用于目标值:

        diff.Patch(ch, &x)
        fmt.Println(x) // [12 14 15] just like the 'y' slice
    

    请注意,此差异的默认行为是忽略切片元素顺序。因此,如果您区分 []int{12, 14, 15}[]int{12, 15, 14},它将产生一个空的变更日志。

    要考虑商品订购,请使用选项diff.SliceOrdering(true)

    func main() {
        x := []int{12, 15, 14}
        y := []int{12, 14, 15}
        ch, _ := diff.Diff(x, y, diff.SliceOrdering(true))
        json.NewEncoder(os.Stdout).Encode(ch)
        // [{"type":"update","path":["1"],"from":15,"to":14},{"type":"update","path":["2"],"from":14,"to":15}]
    
    }
    

    【讨论】:

      【解决方案6】:

      在我回答这个问题的那一刻,go版本是1.17.3

      我们可以使用内置包:reflect

      该包提供了一个非常有用的函数DeepEqual,符合您的要求

      reflect.DeepEqual(X, Y)
      

      【讨论】:

        猜你喜欢
        • 2016-07-06
        • 2013-08-26
        • 2018-10-31
        • 1970-01-01
        • 2016-03-22
        • 2017-07-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多