检查两个数组是否具有相同成员的最佳方法

时间:2018-09-18 22:35:20

标签: arrays go

我有一个字符串数组,我需要将此字符串与另一个字符串数组进行比较,但是它们的顺序可能不同。比较两个数组的最佳方法是什么?

到目前为止,这是我所想的,只是想知道我是否缺少一种更简单/更有效的方法。

func unorderedEqual(first, second []string) bool {
    if len(first) != len(second) {
        return false
    }
    for _, value := range first {
        if !contains(value, second) {
            return false
        }
    }
    return true
}

func contains(needle string, haystack []string) bool {
    for _, matchValue := range haystack {
        if matchValue == needle {
            return true
        }
    }
    return false
}

6 个答案:

答案 0 :(得分:4)

鉴于您正在进行长度检查,我将假设它们是1:1,只是顺序不同。

您可以使用map[string]bool通过一次检查(每次检查)来检查两者是否存在。这利用了以下事实:当不存在密钥时,map返回bool的零值,即false

免责声明:从技术上讲,这是订单O(n)* O(map)。 Go Programming Language Specification对地图类型不做任何性能保证。

https://play.golang.org/p/2LUjN5LkXLL

func unorderedEqual(first, second []string) bool {
    if len(first) != len(second) {
        return false
    }
    exists := make(map[string]bool)
    for _, value := range first {
        exists[value] = true
    }
    for _, value := range second {
        if !exists[value] {
            return false
        }
    }
    return true
}

如果您想对内存使用情况一窍不通,可以使用bool(一堆map[string]struct{}(通常可以忽略,但每个都可以忽略)来保存自己(空结构),您只需按照本示例中的方式检查是否存在。

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

设置

exists[value] = struct{}{}

检查

if _, ok := exists[value]; !ok {
    return false
}

答案 1 :(得分:1)

理想情况下,这应该是注释,但 TLDR 是使用 Husain 的排序和比较更正确和更快。

详情。 对于任何查看上面 RayfenWindspear 答案的人(目前排名最高),乍一看它看起来不错,但请注意,它不会检查完全相等性,而只是检查第二个列表中的每个元素都在第一个列表中.反过来也需要为真,但不通过此方法检查。因此它无法通过此测试(重复 bb)。:

assert.False(t, unorderedEqual([]string{"aa", "cc", "bb"}, []string{"aa", "bb", "bb"}))

当然你可以只运行两次就得到正确的结果,这只是一个线性因素

func DoubleUnorderedEqual(a, b []string) bool {
    return unorderedEqual(a, b) && unorderedEqual(b, a)
}

Husain 提出的先排序再检查的建议应该排名更高,因为它是正确的,而且对于更大的列表也更快。

这是 Husain 在导出函数中的代码:

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

    sort.Strings(a)
    sort.Strings(b)

    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
}

你可以在上面运行一些测试(它通过)

func TestSortCompare(t *testing.T) {

    assert.True(t, SortCompare([]string{"aa"}, []string{"aa"}))
    assert.False(t, SortCompare([]string{"aa"}, []string{"bb"}))
    assert.False(t, SortCompare([]string{"aa"}, []string{"bb", "cc"}))
    assert.True(t, SortCompare([]string{"cc", "bb"}, []string{"bb", "cc"}))
    assert.True(t, SortCompare([]string{"aa", "cc", "bb"}, []string{"aa", "bb", "cc"}))
    assert.False(t, SortCompare([]string{"aa", "cc", "bb"}, []string{"aa", "bb", "bb"}))
}

这是一些示例基准测试....

func getStrings() ([]string, []string) {

    a := []string{"a", "foo", "bar", "ping", "pong"}
    b := []string{"pong", "foo", "a", "bar", "ping"}

    return a, b

}

func BenchmarkSortCompare(b *testing.B) {
    s0, s1 := getStrings()
    var outcome bool
    for n := 0; n < b.N; n++ {
        outcome = SortCompare(s0, s1)
    }
    fmt.Println(outcome)
}

func BenchmarkDoubleUnorderedEqual(b *testing.B) {
    s0, s1 := getStrings()
    var outcome bool
    for n := 0; n < b.N; n++ {
        outcome = DoubleUnorderedEqual(s0, s1)
    }
    fmt.Println(outcome)
}

结果......

BenchmarkSortCompare-32                  2637952           498 ns/op
BenchmarkDoubleUnorderedEqual-32         3060261           381 ns/op

所以在这么小的尺寸下运行两次 map 方法会稍微快一些……但是多加几个字符串,sort 方法胜出 10 倍。我没有考虑混乱程度的影响在字符串中,但它们足够无序,乍一看并不是一个明显不公平的测试。

func getStrings2() ([]string, []string) {

    a := []string{"a", "foo", "bar", "ping", "pong", "b", "c", "g", "e", "f", "d", "h", "i", "j", "q", "l", "n", "o", "p", "k", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
    b := []string{"pong", "foo", "a", "bar", "ping", "p", "r", "q", "s", "u", "t", "v", "j", "x", "y", "z", "b", "e", "d", "c", "h", "g", "f", "i", "w", "k", "l", "n", "o"}

    return a, b

}

func BenchmarkSortCompare2(b *testing.B) {
    s0, s1 := getStrings2()
    var outcome bool
    for n := 0; n < b.N; n++ {
        outcome = SortCompare(s0, s1)
    }
    fmt.Println(outcome)
}

func BenchmarkDoubleUnorderedEqual2(b *testing.B) {
    s0, s1 := getStrings2()
    var outcome bool
    for n := 0; n < b.N; n++ {
        outcome = DoubleUnorderedEqual(s0, s1)
    }
    fmt.Println(outcome)
}

结果:

BenchmarkSortCompare2-32                  454641          2797 ns/op
BenchmarkDoubleUnorderedEqual2-32          44420         26714 ns/op

结论 - 我将使用 Husain 的排序和比较......

答案 2 :(得分:0)

使用this专用软件包,您将不得不使用[]interface{}而不是[]string,然后照这样

package main

import (
    "fmt"

    "github.com/deckarep/golang-set"
)

func main() {
    some := []interface{}{"a", "b", "c"}
    other := []interface{}{"a", "b", "d"}
    fmt.Println(
        mapset.NewThreadUnsafeSetFromSlice(some).
            Difference(mapset.NewThreadUnsafeSetFromSlice(other)).Cardinality() == 0,
    )
    // Output: false
}

答案 3 :(得分:0)

与语言无关的通用语言:

  1. 使用最快的可用算法对它们进行排序
  2. 迭代表A并与B [currentIndexFromA]
  3. 进行比较
  4. 第一次发现差异时,您会知道它们持有不同的数据-抛出!
  5. 您遍历整个A? -他们都一样

我不了解GO,但是您似乎天真地从B中的A中搜索每个元素。在最坏的情况下,您会在B上获得许多迭代。使用高性能算法进行排序似乎效率更高,即使附加操作。

很不幸,我不提供代码示例,因为我不了解GO。

答案 4 :(得分:0)

您可以先对数组进行排序,然后再按索引进行检查:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s1 := []string{"first", "second", "third"}
    s2 := []string{"third", "first", "second"}

    if len(s1) != len(s2) {
        fmt.Println("not equal")
    }
    sort.Strings(s1)
    sort.Strings(s2)
    for i := 0; i < len(s1); i++ {
        if s1[i] != s2[i] {
            fmt.Println("not equal")
            return
        }
    }
    fmt.Println("equal")
}

或在playground中。

排序的思想是使比较更加容易和快捷。排序然后按索引进行比较是O(n log n),而2循环检查则需要O(n ^ 2)。

答案 5 :(得分:0)

不幸的是,我无法发表评论,因为我落后于所需的声誉(更多是读者;-))。 但我想表明 @RayfenWindspear 的答案是错误的!他的方法只是检查 second 中的元素是否存在于 first 中,反之亦然:

这是关于 Playground 的证明(我拿走了他的片段,只是改变了切片的值)

我认为 @Husain 的解决方案很好。