检查无序切片的相等性。

huangapple go评论94阅读模式
英文:

check for equality on slices without order

问题

我正在尝试找到一个检查两个切片是否相等的解决方案。不幸的是,我找到的答案要求切片中的值必须按相同的顺序排列。例如,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

英文:

I am trying to find a solution to check for equality in 2 slices. Unfortanely, the answers I have found require values in the slice to be in the same order. For example, http://play.golang.org/p/yV0q1_u3xR evaluates equality to false.
I want a solution that lets []string{"a","b","c"} == []string{"b","a","c"} evaluate to true.
MORE EXAMPLES
[]string{"a","a","c"} == []string{"c","a","c"} >>> false
[]string{"z","z","x"} == []string{"x","z","z"} >>> true

答案1

得分: 21

以下是翻译好的内容:

这里是另一种解决方案,尽管可能有点冗长:

func sameStringSlice(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    // 创建一个字符串到整数的映射
    diff := make(map[string]int, len(x))
    for _, _x := range x {
        // 整数的零值是0,所以只需为字符串增加一个计数器
        diff[_x]++
    }
    for _, _y := range y {
        // 如果字符串_y不在diff中,提前退出
        if _, ok := diff[_y]; !ok {
            return false
        }
        diff[_y]--
        if diff[_y] == 0 {
            delete(diff, _y)
        }
    }
    return len(diff) == 0
}

Go Playground上尝试一下。

英文:

Here is an alternate solution, though perhaps a bit verbose:

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]--
		if diff[_y] == 0 {
			delete(diff, _y)
		}
	}
    return len(diff) == 0
}

Try it on the <kbd>Go Playground</kbd>

答案2

得分: 16

你可以使用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) // 输出 "true"
}
英文:

You can use cmp.Diff together with cmpopts.SortSlices:

less := func(a, b string) bool { return a &lt; b }
equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == &quot;&quot;

Here is a full example that runs on the Go Playground:

package main

import (
	&quot;fmt&quot;

	&quot;github.com/google/go-cmp/cmp&quot;
	&quot;github.com/google/go-cmp/cmp/cmpopts&quot;
)

func main() {
	x := []string{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}
	y := []string{&quot;a&quot;, &quot;c&quot;, &quot;b&quot;}
	
	less := func(a, b string) bool { return a &lt; b }
	equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == &quot;&quot;
	fmt.Println(equalIgnoreOrder) // prints &quot;true&quot;

}

答案3

得分: 11

其他答案的时间复杂度更好,是O(N),而不是我答案中的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
英文:

The other answers have better time complexity O(N) vs (O(N log(N)), that are in my answer, also my solution will take up more memory if elements in the slices are repeated frequently, but I wanted to add it because I think this is the most straight forward way to do it:

package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
	&quot;reflect&quot;
)

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 {&quot;a&quot;, &quot;a&quot;, &quot;c&quot;}
	b := []string {&quot;c&quot;, &quot;a&quot;, &quot;c&quot;}
	c := []string {&quot;z&quot;,&quot;z&quot;,&quot;x&quot;} 
	d := []string {&quot;x&quot;,&quot;z&quot;,&quot;z&quot;}


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

}

Result:

false
true

答案4

得分: 9

就像adrianlzt在他的答案中写的那样,可以使用testify中的assert.ElementsMatch实现这一点。但是,如果你只需要比较的布尔结果,为什么不重用实际的testify模块,而不是复制那段代码呢?testify中的实现是为测试代码而设计的,通常需要testing.T参数。

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

type dummyt struct{}

func (t dummyt) Errorf(string, ...interface{}) {}

func elementsMatch(listA, listB interface{}) bool {
	return assert.ElementsMatch(dummyt{}, listA, listB)
}

或者在The Go Playground上进行测试,这是我从adrianlzt的示例进行了适应的。

英文:

Like adrianlzt wrote in his answer, an implementation of assert.ElementsMatch from testify can be used to achieve that. But how about reusing actual testify module instead of copying that code when all you need is a bool result of the comparison? The implementation in testify is intended for tests code and usually takes testing.T argument.

It turns out that ElementsMatch can be quite easily used outside of testing code. All it takes is a dummy implementation of an interface with ErrorF method:

type dummyt struct{}

func (t dummyt) Errorf(string, ...interface{}) {}

func elementsMatch(listA, listB interface{}) bool {
	return assert.ElementsMatch(dummyt{}, listA, listB)
}

Or test it on The Go Playground, which I've adapted from the adrianlzt's example.

答案5

得分: 8

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

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
}

你需要添加一些额外的注意事项,比如如果你的数组/切片包含不同类型的元素或长度不同,可以进行短路处理。

英文:

I would think the easiest way would be to map the elements in each array/slice to their number of occurrences, then compare the maps:

func main() {
	x := []string{&quot;a&quot;,&quot;b&quot;,&quot;c&quot;}
	y := []string{&quot;c&quot;,&quot;b&quot;,&quot;a&quot;}
	
	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
}

You'll need to add some additional due dilligence, like short circuiting if your arrays/slices contain elements of different types or are of different length.

答案6

得分: 6

通用化testify ElementsMatch代码的解决方案,用于比较任何类型的对象(在示例中为[]map[string]string):

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

英文:

Generalizing the code of testify ElementsMatch, solution to compare any kind of objects (in the example []map[string]string):

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

答案7

得分: 1

由于我没有足够的声望来发表评论,我不得不再次发布一个代码可读性更好的答案:

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
}

逻辑与这个答案相同。

英文:

Since I haven't got enough reputation to comment, I have to post yet another answer with a bit better code readability:

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
}

The logic is the same as in this answer

答案8

得分: 0

我知道这个问题已经有答案了,但我还是想补充一下我的答案。通过以下代码,我们可以实现类似的功能:

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

    if aLen != bLen {
        return fmt.Sprintf("列表长度不匹配,listA长度为 %v,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("元素 %s 在 %s 中出现的次数比在 %s 中多", element, listA, listB), false
        }
    }
    return "", true
}

现在让我们来讨论一下与基于映射的解决方案相比,这种解决方案的性能如何。嗯,这实际上取决于你要比较的列表的大小。如果列表的大小很大(我会说大于20),那么使用映射的方法更好,否则这种方法就足够了。

Go PlayGround上运行时,它总是显示0秒,但在本地系统上运行时,你可以看到随着列表大小的增加,所花费的时间有所不同。

因此,我提出的解决方案是,在上面的解决方案中添加基于映射的比较:

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

    if aLen != bLen {
        return fmt.Sprintf("列表长度不匹配,listA长度为 %v,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("元素 %s 在 %s 中出现的次数比在 %s 中多", element, listA, listB), false
        }
    }
    return "", true
}

func elementsMatchByMap(x, y []string) (string, bool) {
    // 创建一个字符串到整数的映射
    diff := make(map[string]int, len(x))
    for _, _x := range x {
        // 整数的零值是0,所以只需为字符串增加一个计数器
        diff[_x]++
    }
    for _, _y := range y {
        // 如果字符串_y不在diff中,则提前退出
        if _, ok := diff[_y]; !ok {
            return fmt.Sprintf("%v 不在列表b中", _y), false
        }
        diff[_y] -= 1
        if diff[_y] == 0 {
            delete(diff, _y)
        }
    }
    if len(diff) == 0 {
        return "", true
    }
    return "", false
}
英文:

I know its been answered but still I would like to add my answer. By following code here stretchr/testify we can have something like

  func Elementsmatch(listA, listB []string) (string, bool) {
aLen := len(listA)
bLen := len(listB)
if aLen != bLen {
return fmt.Sprintf(&quot;Len of the lists don&#39;t match , len listA %v, len listB %v&quot;, aLen, bLen), false
}
visited := make([]bool, bLen)
for i := 0; i &lt; aLen; i++ {
found := false
element := listA[i]
for j := 0; j &lt; bLen; j++ {
if visited[j] {
continue
}
if element == listB[j] {
visited[j] = true
found = true
break
}
}
if !found {
return fmt.Sprintf(&quot;element %s appears more times in %s than in %s&quot;, element, listA, listB), false
}
}
return &quot;&quot;, true
}

Now lets talk about performance of this solution compared to map based ones. Well it really depends on the size of the lists which you are comparing, If size of list is large (I would say greater than 20) then map approach is better else this would be sufficent.

Well on Go PlayGround it shows 0s always, but run this on local system and you can see the difference in time taken as size of list increases

So the solution I propose is, adding map based comparision from above solution

func Elementsmatch(listA, listB []string) (string, bool) {
aLen := len(listA)
bLen := len(listB)
if aLen != bLen {
return fmt.Sprintf(&quot;Len of the lists don&#39;t match , len listA %v, len listB %v&quot;, aLen, bLen), false
}
if aLen &gt; 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 &lt; aLen; i++ {
found := false
element := listA[i]
for j := 0; j &lt; bLen; j++ {
if visited[j] {
continue
}
if element == listB[j] {
visited[j] = true
found = true
break
}
}
if !found {
return fmt.Sprintf(&quot;element %s appears more times in %s than in %s&quot;, element, listA, listB), false
}
}
return &quot;&quot;, true
}
func elementsMatchByMap(x, y []string) (string, bool) {
// create a map of string -&gt; 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(&quot; %v is not present in list b&quot;, _y), false
}
diff[_y] -= 1
if diff[_y] == 0 {
delete(diff, _y)
}
}
if len(diff) == 0 {
return &quot;&quot;, true
}
return &quot;&quot;, false
}

答案9

得分: 0

顺序对切片很重要。在不同的情况下,可以使它们不同。然后,您可以使用reflect。此实现还考虑到元素可以重复。

func slicesEqual(want, got []string) bool {
    w := map[string]int{}
    g := map[string]int{}

    for _, v := range want {
        if _, ok := w[v]; !ok {
            w[v] = 1
            continue
        }
        w[v]++
    }

    for _, v := range got {
        if _, ok := g[v]; !ok {
            g[v] = 1
            continue
        }
        g[v]++
    }

    return reflect.DeepEqual(w, g)
}
英文:

Order in slices matters. Make them something different where it doesn't. Then, you can use reflect. This implementation also takes in consideration that elements can be repeated.

func slicesEqual(want, got []string) bool {
w := map[string]int{}
g := map[string]int{}
for _, v := range want {
if _, ok := w[v]; !ok {
w[v] = 1
continue
}
w[v]++
}
for _, v := range got {
if _, ok := g[v]; !ok {
g[v] = 1
continue
}
g[v]++
}
return reflect.DeepEqual(w, g)
}

huangapple
  • 本文由 发表于 2016年3月15日 08:14:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/36000487.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定