【问题标题】:How to check the uniqueness inside a for-loop?如何检查for循环内的唯一性?
【发布时间】:2012-03-04 07:21:41
【问题描述】:

有没有办法检查切片/映射是否存在值?

如果切片中确实存在,我想为切片添加一个值 .

这可行,但看起来很冗长。有没有更好的方法来做到这一点?

orgSlice := []int{1, 2, 3}
newSlice := []int{}
newInt := 2
    
newSlice = append(newSlice, newInt)
for _, v := range orgSlice {
    if v != newInt {
        newSlice = append(newSlice, v)
    }
}

newSlice == [2 1 3]

【问题讨论】:

  • Re:EDIT - 任何有效的地图键类型都是一样的 - 字符串是。
  • Re:EDIT2 - 如果 'newSlice' 中值的顺序无关紧要,并且将使用范围语句使用/使用它,那么它的构造是多余的 - 只需范围 'set' 的键.
  • @jnml 感谢您的 cmets。我将ints 的列表存储在GAE 数据存储中,并且为了查询它必须是一个切片([]int)。这个要求是否使我的初始技术成为更好的选择?列表会很小。
  • 您可以通过首先创建newslice := make([]int, len(set)) 来避免使用append()(以及所有重新分配)。如果你做了很多这样的“包含关键......”测试(至少超过 2 个),将切片转换为 map[int]struct{} 可能会快得多,如果你只做几个,循环直接通过切片可能更好。
  • @tux21b 好的,谢谢,非常感谢您花时间解释所有这些。

标签: for-loop go append slice


【解决方案1】:

您的方法每次插入都需要线性时间。更好的方法是使用map[int]struct{}。或者,您也可以使用map[int]bool 或类似的东西,但空的struct{} 具有不占用任何额外空间的优点。因此map[int]struct{} 是一组整数的流行选择。

示例:

set := make(map[int]struct{})
set[1] = struct{}{}
set[2] = struct{}{}
set[1] = struct{}{}
// ...

for key := range(set) {
  fmt.Println(key)
}
// each value will be printed only once, in no particular order


// you can use the ,ok idiom to check for existing keys
if _, ok := set[1]; ok {
  fmt.Println("element found")
} else {
  fmt.Println("element not found")
}

【讨论】:

  • 感谢您的回复。几个问题:然后你将如何重新创建切片?有没有办法让这个策略适用于字符串?对不起,如果这些很明显 - 我是 Go 新手。
  • 我会在计算过程中只使用地图(因为它的行为是 O(1) 而不是 O(n))。之后,您可以创建一个切片并从地图中复制每个值。之后元素将具有随机顺序,因此您可能需要对其进行排序。并且您可以使用 int、float、struct、string 和数组作为映射键(至少在 Go1 中)。
  • 特别感谢您概述空结构不会占用额外空间。我不知道这一点,会使用 map[type]interface{} 并将 nil 分配给接口。
  • 我也使用了map[type]interface{}方法,这不是也不会占用额外空间吗?
【解决方案2】:

最有效的方法可能是遍历切片并在找不到时追加。

func AppendIfMissing(slice []int, i int) []int {
    for _, ele := range slice {
        if ele == i {
            return slice
        }
    }
    return append(slice, i)
}

它简单明了,对于小列表来说很快。

此外,它总是比您当前基于地图的解决方案更快。无论如何,基于地图的解决方案都会遍历整个切片;此解决方案在发现新值已存在时立即返回。两种解决方案在迭代时都会比较元素。 (每个 map 赋值语句肯定至少在内部进行一次 map 键比较。)只有当您可以在许多插入中维护它时,一个 map 才会有用。如果您在每次插入时都重新构建它,那么所有优势都将丢失。

如果您确实需要有效地处理大型列表,请考虑按排序顺序维护列表。 (我怀疑顺序对您来说并不重要,因为您的第一个解决方案附加在列表的开头,而您的最新解决方案附加在最后。)如果您始终保持列表排序,那么您可以使用 sort.Search 功能进行有效的二进制插入。

【讨论】:

  • “无论如何,基于映射的解决方案都会遍历整个切片”——您确定这是哈希映射的工作方式吗?
  • @Ottokar,他错了吗?很多人投了赞成票,但没有任何回应。
  • @FilipBartuzi 实际上,我想我可能误解了该声明的含义。哈希映射显然不会遍历元素来查找键,但是如果我们必须将切片转换为映射然后映射回切片,则“如果唯一则附加到切片”的“基于映射的解决方案”失去了优势.
  • play.golang.org/p/kL5csJT66oh 这是工作示例
  • 如果您需要调用多个元素,则基于地图的解决方案可以工作。如果你只调用一次,那么迭代切片就可以了。无论您是否需要多次“寻找”某些东西,您的数据确实不应该在数组中。
【解决方案3】:

另一种选择:

package main
import "golang.org/x/tools/container/intsets"

func main() {
   var (
      a intsets.Sparse
      b bool
   )
   b = a.Insert(9)
   println(b) // true
   b = a.Insert(9)
   println(b) // false
}

https://pkg.go.dev/golang.org/x/tools/container/intsets

【讨论】:

    【解决方案4】:

    区分结构的数组:

    func distinctObjects(objs []ObjectType) (distinctedObjs [] ObjectType){
            var output []ObjectType
        for i:= range objs{
            if output==nil || len(output)==0{
                output=append(output,objs[i])
            } else {
                founded:=false
                for j:= range output{
                        if output[j].fieldname1==objs[i].fieldname1 && output[j].fieldname2==objs[i].fieldname2 &&......... {
                        founded=true
                    }
                }
                if !founded{
                    output=append(output,objs[i])
                }
            }
        }
        return output
    }
    

    这里的结构类似于:

    type ObjectType struct {
        fieldname1 string
        fieldname2 string
        .........
    }
    

    对象将在此处通过选中的字段来区分:

    if output[j].fieldname1==objs[i].fieldname1 && output[j].fieldname2==objs[i].fieldname2 &&......... {
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-28
      • 1970-01-01
      • 1970-01-01
      • 2018-01-23
      • 2021-08-21
      • 1970-01-01
      • 1970-01-01
      • 2015-12-28
      相关资源
      最近更新 更多