在Go语言中查找切片或数组中的唯一项

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

Finding Unique Items in a Go Slice or Array

问题

我对Go语言还不太熟悉,现在感到非常困惑。

假设我有一个坐标列表,列表中可能包含一些重复的坐标。我无法弄清楚如何创建一个唯一的列表。通常在Python中,我可以使用集合和其他内置函数来“作弊”。但在Go语言中,情况就不太一样了。

以下是你提供的代码:

package main

import (
	"fmt"
	"reflect"
)

type visit struct {
	x, y int
}

func main() {
	var visited []visit
	var unique []visit

	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{2, 2})
	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{1, 1})

	unique = append(unique, visit{1, 1})

	fmt.Println(unique)

	// 遍历visited列表,找出唯一的元素
	for _, v := range visited {
		for _, u := range unique {

			fmt.Printf("Here's unique: %v\n", unique)
			fmt.Printf("Comparing %v to %v is %v\n", v, u, reflect.DeepEqual(v, u))

			if reflect.DeepEqual(v, u) {
				fmt.Println("Skip")
			} else {
				unique = append(unique, v)
			}
		}
	}

	fmt.Println(unique)
}

你可以在Playground上运行这段代码。

链接:在Playground上运行

希望这能帮到你!

英文:

I'm pretty new to go and I'm really, really confused right now.

Let's say I have a list of coordinates and lets say I have some doubles in this list of coordinates. I can't for the life of me figure out how to make a unique list. Normally in Python I can "cheat" with sets and other built-ins. Not so much in Go.

package main

import (
	"fmt"
	"reflect"
)

type visit struct {
	x, y int
}

func main() {
	var visited []visit
	var unique []visit

	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{2, 2})
	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{1, 1})

	unique = append(unique, visit{1, 1})

	fmt.Println(unique)

	// Go through the visits and find the unique elements
	for _, v := range visited {
		for _, u := range unique {

			fmt.Printf("Here's unique: %v\n", unique)
			fmt.Printf("Comparing %v to %v is %v\n", v, u, reflect.DeepEqual(v, u))

			if reflect.DeepEqual(v, u) {
				fmt.Println("Skip")
			} else {
				unique = append(unique, v)
			}
		}
	}

	fmt.Println(unique)
}

<kbd>Run it on Playground</kbd>

答案1

得分: 17

你的代码中有多个错误。最严重的错误是,由于你将visited切片的每个特定元素与unique所有元素进行比较,所以如果unique至少包含一个不同的元素,你将会将它追加到unique中。而且,如果unique中有更多不同的元素,你的内部for循环不会“中断”,所以你最终会多次将它追加到unique中。这不是你想要的,你想要追加与unique没有一个相等的元素。

此外,请注意,在Go中,如果结构体的每个字段都是可比较的,那么该结构体是可比较的。由于你的visit结构体只包含两个int字段,它是可比较的,因此你可以使用==运算符直接比较visit类型的值,而不需要使用丑陋的reflect.DeepEqual()。参见规范:比较运算符

> 如果结构体的所有字段都是可比较的,则结构体值是可比较的。如果它们对应的非空白字段相等,则两个结构体值相等。

下面是一个简化且正确的版本,应用了你的逻辑:

visited := []visit{
	visit{1, 100},
	visit{2, 2},
	visit{1, 100},
	visit{1, 1},
}
var unique []visit

for _, v := range visited {
	skip := false
	for _, u := range unique {
		if v == u {
			skip = true
			break
		}
	}
	if !skip {
		unique = append(unique, v)
	}
}

fmt.Println(unique)

输出结果(在Go Playground上尝试):

[{1 100} {2 2} {1 1}]

另一种方法

确实,Go没有内置的集合类型,但你可以很容易地使用map[visit]bool作为集合。这样做变得非常简单!请注意,visit可以作为映射中的键,因为它是可比较的(参见上文)。

visited := []visit{
	visit{1, 100},
	visit{2, 2},
	visit{1, 100},
	visit{1, 1},
}
unique := map[visit]bool{}

for _, v := range visited {
	unique[v] = true
}

fmt.Println(unique)

输出结果(在Go Playground上尝试):

map[{2 2}:true {1 1}:true {1 100}:true]

唯一的“列表”是映射中的键列表。

如果你想将唯一的visit值作为切片输出,可以使用以下变体:

var unique []visit
m := map[visit]bool{}

for _, v := range visited {
	if !m[v] {
		m[v] = true
		unique = append(unique, v)
	}
}

fmt.Println(unique)

输出结果(如预期,可以在Go Playground上尝试):

[{1 100} {2 2} {1 1}]

请注意,索引表达式m[v]的结果为true,如果v已经在映射中(作为键,true是我们在映射中存储的值)。如果v尚未在映射中,m[v]将产生值类型的零值,对于bool类型来说,这个零值是false,正确地表示值v尚未在映射中。参见规范:索引表达式

> 对于映射类型Ma
>
> ……如果映射是nil或不包含这样的条目,则a[x]M值的零值

英文:

There are multiple errors in your code. The most serious is that since you're comparing each specific element of the visited slice to all of the elements of unique, you will end up appending it if unique contains at least one which is different. And going forward, you will end up appending it multiple times if there are more elements in unique which differ as your inner for loop doesn't "break". This is not what you want, you want to append elements which equals to none of unique.

Also note that a struct in Go is comparable if each of its fields are comparable. Since your visit struct contains only 2 int fields, it is comparable and so you can compare values of visit type simply with the == operator, no need that ugly reflect.DeepEqual(). See Spec: Comparison operators:

> Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.

Here's a simplified, correct version that applies your logic:

visited := []visit{
	visit{1, 100},
	visit{2, 2},
	visit{1, 100},
	visit{1, 1},
}
var unique []visit

for _, v := range visited {
	skip := false
	for _, u := range unique {
		if v == u {
			skip = true
			break
		}
	}
	if !skip {
		unique = append(unique, v)
	}
}

fmt.Println(unique)

Output (try it on the Go Playground):

[{1 100} {2 2} {1 1}]

Alternative

It's true that Go doesn't have a built-in set type, but you can use a map[visit]bool easily as a set. With that, it becomes really simple! Note that visit can be used as key in the map because it is comparable (see above).

visited := []visit{
	visit{1, 100},
	visit{2, 2},
	visit{1, 100},
	visit{1, 1},
}
unique := map[visit]bool{}

for _, v := range visited {
	unique[v] = true
}

fmt.Println(unique)

Output (try it on the Go Playground):

map[{2 2}:true {1 1}:true {1 100}:true]

The unique "list" is the list of keys in the map.

If you want the unique visit values as a slice, see this variant:

var unique []visit
m := map[visit]bool{}

for _, v := range visited {
	if !m[v] {
		m[v] = true
		unique = append(unique, v)
	}
}

fmt.Println(unique)

Output (as expected, try it on the Go Playground):

[{1 100} {2 2} {1 1}]

Note that this index expression: m[v] evaluates to true if v is already in the map (as a key, true is the value we stored in the map). If v is not yet in the map, m[v] yields the zero value of the value type which is false for the type bool, properly telling that the value v is not yet in the map. See Spec: Index expressions:

> For a of map type M:
>
> ...if the map is nil or does not contain such an entry, a[x] is the zero value for the value type of M

答案2

得分: 3

我认为你可以使用Map来解决这个谜题。

package main

import (
	"fmt"
)

type visit struct {
	x, y int
}

func main() {
	var visited []visit
	var unique []visit

	uniqueMap := map[visit]int{}

	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{2, 2})
	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{1, 1})

	for _, v := range visited {
		if _, exist := uniqueMap[v]; !exist {
			uniqueMap[v] = 1
			unique = append(unique, v)
		} else {
			uniqueMap[v]++
		}
	}
	fmt.Printf("Uniques: %v\nMaps:%v\n", unique, uniqueMap)
}

我已经将代码翻译成中文,你可以查看上面的代码。

英文:

I think you can use help of Map to solve this puzzle

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

package main

import (
	&quot;fmt&quot;
)

type visit struct {
	x, y int
}

func main() {
	var visited []visit
	var unique []visit

	uniqueMap := map[visit]int{}

	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{2, 2})
	visited = append(visited, visit{1, 100})
	visited = append(visited, visit{1, 1})

	for _, v := range visited {
		if _, exist := uniqueMap[v]; !exist {
			uniqueMap[v] = 1
			unique = append(unique, v)
		} else {
			uniqueMap[v]++
		}
	}
	fmt.Printf(&quot;Uniques: %v\nMaps:%v\n&quot;, unique, uniqueMap)
}

答案3

得分: 2

我认为你可以创建一个访问记录的地图。就像这样:

visited := make(map[visit]bool)

然后你可以设置访问记录的值:

visited[visit] = true

最后,你可以通过以下代码获取所有已访问的位置:

var unique []visit
for k := range visited {
    unique = append(unique, k)
}

你可以参考这篇文章了解更多关于Go语言中地图的使用:Go Maps in Action

英文:

i think you can make a map of visits.
Something like this

visited := make(map[visit]Boolean)

than you can set the value of the visited.

visited[visit]=true

and lastly, you can fetch all visited locations by this code

for k, _ := range visited {
    unique = append(unique, k)
}

答案4

得分: 0

你可以通过sort.Slice()对切片进行排序,然后通过用后续的唯一元素覆盖非唯一元素来使切片变得唯一:

func Unique[T comparable](xs []T) []T {
    n := len(xs)
    if n == 0 {
        return xs
    }
    j := 0
    for i := 1; i < n; i++ {
        if xs[j] !=  xs[i] {
            j++
            if j < i {
                xs[j] = xs[i]
                for k := i + 1; k < n; k++ {
                    if xs[j] !=  xs[k] {
                        j++
                        xs[j] = xs[k]
                    }
                }
                break
            }
        }
    }
    xs = xs[0:j+1]
    return xs
}

使切片变得唯一只需要线性的工作量,然而,这在排序时被主导(除非你的输入已经排序),排序的时间复杂度为O(n log n)

示例用法:

visited = sort.Slice(visited, func(i, j int) bool {
        return visited[i].x < visited[j].x && visited[i].y < visited[j].y })
visited = Unique(visited)
unique := visited

另外,如果你不想排序或者不关心元素的顺序,你可以将所有元素存储在类似集合的映射中,这样可以消除重复。

示例:

h := map[visit]struct{} {}
for _, v := range visited {
    h[v] = struct{} {}
}
unique := make(visit[], len(h))
i := 0
for k := range h {
    unique[i] = k
    i++
}

总运行时间是线性的,除非你的数据哈希得很差。

英文:

You can sort your slice via sort.Slice() and then make the slice unique by overwriting non-unique elements with following unique ones:

func Unique[T comparable](xs []T) []T {
    n := len(xs)
    if n == 0 {
        return xs
    }
    j := 0
    for i := 1; i &lt; n; i++ {
        if xs[j] !=  xs[i] {
            j++
            if j &lt; i {
                xs[j] = xs[i]
                for k := i + 1; k &lt; n; k++ {
                    if xs[j] !=  xs[k] {
                        j++
                        xs[j] = xs[k]
                    }
                }
                break
            }
        }
    }
    xs = xs[0:j+1]
    return xs
}

Making the slice unique is only linear effort, however, this is dominated by the sorting (unless your input already is sorted), which is in O(n log n).

Example usage:

visited = sort.Slice(visited, func(i, j int) bool {
        return visited[i].x &lt; visited[j].x &amp;&amp; visited[i].y &lt; visited[j].y })
visited = Unique(visited)
unique := visited

Alternatively, if you don't want to sort or don't care about the element order you can store all elements in a set-like map, which eliminates duplicates.

Example:

h := map[visit]struct{} {}
for _, v := range visited {
    h[v] = struct{} {}
}
unique := make(visit[], len(h))
i := 0
for k := range h {
    unique[i] = k
    i++
}

The total runtime is linear, unless your data hashes badly.

huangapple
  • 本文由 发表于 2015年12月6日 06:06:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/34111476.html
匿名

发表评论

匿名网友

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

确定