英文:
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.Diff
和cmpopts.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 < b }
equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == ""
Here is a full example that runs on the 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) // prints "true"
}
答案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 (
"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))
}
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{"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
}
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
):
答案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("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
}
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("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
}
答案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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论