检查没有订单的切片上的相等性

时间:2016-03-15 00:14:48

标签: go slice equality

我正在尝试找到一个解决方案来检查2个切片中的相等性。不幸的是,我发现的答案要求切片中的值处于相同的顺序。例如,http://play.golang.org/p/yV0q1_u3xR将相等性评估为false  我想要一个让[]string{"a","b","c"} == []string{"b","a","c"}评估为true的解决方案 更多示例
[]string{"a","a","c"} == []string{"c","a","c"}>>> false
[]string{"z","z","x"} == []string{"x","z","z"}>>> true

8 个答案:

答案 0 :(得分:6)

这是一个替代解决方案,虽然可能有点冗长:

func sameStringSlice(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    // create a map of string -> int
    diff := make(map[string]int, len(x))
    for _, _x := range x {
        // 0 value for int is 0, so just increment a counter for the string
        diff[_x]++
    }
    for _, _y := range y {
        // If the string _y is not in diff bail out early
        if _, ok := diff[_y]; !ok {
            return false
        }
        diff[_y] -= 1
        if diff[_y] == 0 {
            delete(diff, _y)
        }
    }
    if len(diff) == 0 {
        return true
    }
    return false
}

Go Playground

上试试

答案 1 :(得分:5)

我认为最简单的方法是将每个数组/切片中的元素映射到它们的出现次数,然后比较这些地图:

func main() {
    x := []string{"a","b","c"}
    y := []string{"c","b","a"}

    xMap := make(map[string]int)
    yMap := make(map[string]int)

    for _, xElem := range x {
        xMap[xElem]++
    }
    for _, yElem := range y {
        yMap[yElem]++
    }

    for xMapKey, xMapVal := range xMap {
        if yMap[xMapKey] != xMapVal {
            return false
        }
    }
    return true
}

如果您的数组/切片包含不同类型或不同长度的元素,您需要添加一些额外的尽职调查,例如短路。

答案 2 :(得分:1)

其他答案有更好的时间复杂度O(N) vs (O(N log(N)),这在我的回答中,如果切片中的元素经常重复,我的解决方案也将占用更多内存,但我想添加因为我认为这是最直接的方式:

package main

import (
    "fmt"
    "sort"
    "reflect"
)

func array_sorted_equal(a, b []string) bool {
    if len(a) != len(b) {return false }

    a_copy := make([]string, len(a))
    b_copy := make([]string, len(b))

    copy(a_copy, a)
    copy(b_copy, b)

    sort.Strings(a_copy)
    sort.Strings(b_copy)

    return reflect.DeepEqual(a_copy, b_copy)
}

func main() {
    a := []string {"a", "a", "c"}
    b := []string {"c", "a", "c"}
    c := []string {"z","z","x"} 
    d := []string {"x","z","z"}


    fmt.Println( array_sorted_equal(a, b))
    fmt.Println( array_sorted_equal(c, d))

}

结果:

false
true

答案 3 :(得分:0)

我知道已经回答了,但是我仍然想补充一下我的答案。通过在此处stretchr/testify遵循以下代码,我们可以得到类似的

  func Elementsmatch(listA, listB []string) (string, bool) {
    aLen := len(listA)
    bLen := len(listB)

    if aLen != bLen {
        return fmt.Sprintf("Len of the lists don't match , len listA %v, len listB %v", aLen, bLen), false
    }

    visited := make([]bool, bLen)

    for i := 0; i < aLen; i++ {
        found := false
        element := listA[i]
        for j := 0; j < bLen; j++ {
            if visited[j] {
                continue
            }
            if element == listB[j] {
                visited[j] = true
                found = true
                break
            }
        }
        if !found {
            return fmt.Sprintf("element %s appears more times in %s than in %s", element, listA, listB), false
        }
    }
    return "", true
}

现在让我们来讨论一下与基于地图的解决方案相比,该解决方案的性能。好吧,这实际上取决于要比较的列表的大小。如果列表的大小很大(我想说大于20),则使用map方法更好,否则就足够了。

Go PlayGround上它总是显示0,但是在本地系统上运行它,您会看到随着列表大小的增加所花费的时间差异

所以我建议的解决方案是在solution

上方添加基于地图的比较
func Elementsmatch(listA, listB []string) (string, bool) {
    aLen := len(listA)
    bLen := len(listB)

    if aLen != bLen {
        return fmt.Sprintf("Len of the lists don't match , len listA %v, len listB %v", aLen, bLen), false
    }

    if aLen > 20 {
        return elementsMatchByMap(listA, listB)
    }else{
        return elementsMatchByLoop(listA, listB)
    }

}

func elementsMatchByLoop(listA, listB []string) (string, bool) {
    aLen := len(listA)
    bLen := len(listB)

    visited := make([]bool, bLen)

    for i := 0; i < aLen; i++ {
        found := false
        element := listA[i]
        for j := 0; j < bLen; j++ {
            if visited[j] {
                continue
            }
            if element == listB[j] {
                visited[j] = true
                found = true
                break
            }
        }
        if !found {
            return fmt.Sprintf("element %s appears more times in %s than in %s", element, listA, listB), false
        }
    }
    return "", true
}

func elementsMatchByMap(x, y []string) (string, bool) {
    // create a map of string -> int
    diff := make(map[string]int, len(x))
    for _, _x := range x {
        // 0 value for int is 0, so just increment a counter for the string
        diff[_x]++
    }
    for _, _y := range y {
        // If the string _y is not in diff bail out early
        if _, ok := diff[_y]; !ok {
            return fmt.Sprintf(" %v is not present in list b", _y), false
        }
        diff[_y] -= 1
        if diff[_y] == 0 {
            delete(diff, _y)
        }
    }
    if len(diff) == 0 {
        return "", true
    }
    return "", false
}

答案 4 :(得分:0)

概括testify ElementsMatch的代码,比较任何种类的对象的解决方案(在示例[]map[string]string中):

https://play.golang.org/p/xUS2ngrUWUl

答案 5 :(得分:0)

由于我没有足够的声誉来发表评论,因此我不得不发布另一个答案,其代码可读性更好:

func AssertSameStringSlice(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }

    itemAppearsTimes := make(map[string]int, len(x))
    for _, i := range x {
        itemAppearsTimes[i]++
    }

    for _, i := range y {
        if _, ok := itemAppearsTimes[i]; !ok {
            return false
        }

        itemAppearsTimes[i]--

        if itemAppearsTimes[i] == 0 {
            delete(itemAppearsTimes, i)
        }
    }

    if len(itemAppearsTimes) == 0 {
        return true
    }

    return false
}

逻辑与此answer

中的逻辑相同

答案 6 :(得分:0)

就像 adrianlzt 在他的 answer 中所写的那样,可以使用 testify 中的 install.packages("survival") install.packages("lattice") install.packages("ggplot2") install.packages("Hmisc") 实现来实现这一点。但是,当您只需要比较的布尔结果时,如何重用实际的 testify 模块而不是复制该代码? testify 中的实现用于测试代码,通常采用 assert.ElementsMatch 参数。

事实证明 ElementsMatch 可以很容易地在测试代码之外使用。所需要的只是一个带有 testing.T 方法的接口的虚拟实现:

ErrorF

或者在 The Go Playground 上进行测试,这是我改编自 adrianlzt 的示例。

答案 7 :(得分:0)

您可以将 cmp.Diffcmpopts.SortSlices 一起使用:

less := func(a, b string) bool { return a < b }
equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == ""

以下是在 Go Playground 上运行的完整示例:

package main

import (
    "fmt"

    "github.com/google/go-cmp/cmp"
    "github.com/google/go-cmp/cmp/cmpopts"
)

func main() {
    x := []string{"a", "b", "c"}
    y := []string{"a", "c", "b"}
    
    less := func(a, b string) bool { return a < b }
    equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == ""
    fmt.Println(equalIgnoreOrder)
}