从切片中删除元素

4

我是一个 Golang 的新手,我正在尝试根据另一个列表中的元素从一个列表中删除元素。

例如:

输入列表:urlList := []string{"test", "abc", "def", "ghi"}

需要删除的元素列表:remove := []string{"abc", "test"}

期望输出的列表:urlList := []string{"def", "ghi"}

这是我的尝试:

func main() {

    urlList := []string{"test", "abc", "def", "ghi"}
    remove := []string{"abc", "test"}
loop:
    for i, url := range urlList {
        for _, rem := range remove {
            if url == rem {
                urlList = append(urlList[:i], urlList[i+1:]...)
                continue loop
            }
        }
    }
    for _, v := range urlList {
        fmt.Println(v)
    }
}

但是它并没有按照我的预期工作。我不知道我漏了什么。

6个回答

9
问题在于当你从原始列表中删除一个元素时,所有随后的元素都将被“移位”。但是 range 循环并不知道您已更改底层切片,因此将像往常一样按索引递增,即使在这种情况下不应该递增,因为这样会“跳过”一个元素。
由于 remove 列表包含在原始列表中“相邻的 2 个元素”,第二个元素(在本例中为 “abc”)将不会被检查和删除。
一个可能的解决方案是不在外部循环中使用 range,当您删除一个元素时,您需要手动减少索引 i--,因为继续进行下一次迭代时它将自动递增:
urlList := []string{"test", "abc", "def", "ghi"}
remove := []string{"abc", "test"}

loop:
for i := 0; i < len(urlList); i++ {
    url := urlList[i]
    for _, rem := range remove {
        if url == rem {
            urlList = append(urlList[:i], urlList[i+1:]...)
            i-- // Important: decrease index
            continue loop
        }
    }
}

fmt.Println(urlList)

输出:

[def ghi]

注意:

由于外部循环在内部循环后面没有任何内容,因此您可以用简单的 break 替换标签和 continue

urlList := []string{"test", "abc", "def", "ghi"}
remove := []string{"abc", "test"}

for i := 0; i < len(urlList); i++ {
    url := urlList[i]
    for _, rem := range remove {
        if url == rem {
            urlList = append(urlList[:i], urlList[i+1:]...)
            i-- // Important: decrease index
            break
        }
    }
}

fmt.Println(urlList)

请在Go Playground上试用。

替代方案

另一个替代方案是外部循环向下进行,因此无需手动减少(或增加)索引变量,因为移位的元素不受影响(由于向下方向已处理)。


2
向下循环更高效。 - VimleshS
@VKS 是的。并且这也在答案的结尾提到了。 - icza
如果Go语言有一个与Perl的redo相对应的关键字(类似于continue,但它不会重新执行循环测试或增量),那么在用redo替换continue后,代码将是正确的。但现在...好吧,总还有goto :) - hobbs
在删除元素之前,最好将其设置为nil。否则可能会出现内存泄漏 https://github.com/golang/go/wiki/SliceTricks - creker

3
也许更简单的方法是创建一个新的切片,其中只包含您所需的元素,例如:
package main

import "fmt"

func main() {
    urlList := []string{"test", "abc", "def", "ghi"}
    remove := []string{"abc", "test"}

    new_list := make([]string, 0)

    my_map := make(map[string]bool, 0)
    for _, ele := range remove {
        my_map[ele] = true
    }

    for _, ele := range urlList {
        _, is_in_map := my_map[ele]
        if is_in_map {
            fmt.Printf("Have to ignore : %s\n", ele)
        } else {
            new_list = append(new_list, ele)    
        }
    }

    fmt.Println(new_list)

}

playground

结果:

Have to ignore : test
Have to ignore : abc
[def ghi]

1

在迭代切片时修改它时,你必须小心。

这是一种常见的方法,同时迭代并压缩数据从而从切片中删除元素。

它还使用了一个映射而不是一个切片来排除元素,当排除的项数很大时,这样可以提高效率。

Exclude会直接在原地更新xs,因此需要使用指针参数。另一种选择是更新xs的后台数组,但以与内置的append相同的方式从函数返回切片。

package main

import "fmt"

func Exclude(xs *[]string, excluded map[string]bool) {
    w := 0
    for _, x := range *xs {
        if !excluded[x] {
            (*xs)[w] = x
            w++
        }
    }
    *xs = (*xs)[:w]
}

func mapFromSlice(ex []string) map[string]bool {
    r := map[string]bool{}
    for _, e := range ex {
        r[e] = true
    }
    return r
}

func main() {
    urls := []string{"test", "abc", "def", "ghi"}
    remove := mapFromSlice([]string{"abc", "test"})
    Exclude(&urls, remove)
    fmt.Println(urls)
}

这段代码的运行时间为O(N+M),其中N是urls的长度,M是remove的长度。

0

您可以使用以下函数:

func main() {
  array := []string{"A", "B", "C", "D", "E"}
  a = StringSliceDelete(a, 2) // delete "C"
  fmt.Println(a) // print: [A, B, D E]
}

//IntSliceDelete function
func IntSliceDelete(slice []int, index int) []int {
   copy(slice[index:], slice[index+1:])
   new := slice[:len(slice)-1]
   return new
}

//StringSliceDelete function
func StringSliceDelete(slice []string, index int) []string {
    copy(slice[index:], slice[index+1:])
    new := slice[:len(slice)-1]
    return new
}

// ObjectSliceDelete function
func ObjectSliceDelete(slice []interface{}, index int) []interface{} {
    copy(slice[index:], slice[index+1:])
    new := slice[:len(slice)-1]
    return new
}

0

试一下

https://go.dev/play/p/CKvGWl7vG4_V

elements := []string{"one", "two", "three", "four"}
needToRemove := []string{"one", "four"}

newSlice := remove(elements, needToRemove) // []string{"two", "three"}


func remove(slice, elements []string) []string {
    out := []string{}
    bucket := map[string]bool{}

    for _, element := range slice {
        if !inSlice(elements, element) && !bucket[element] {
            out = append(out, element)
            bucket[element] = true
        }
    }

    return out
}

func inSlice(slice []string, elem string) bool {
    for _, i := range slice {
        if i == elem {
            return true
        }
    }

    return false
}

0

在索引的情况下:

//RemoveElements delete the element of the indexes contained in j of the data in input
func RemoveElements(data []string, j []int) []string {
    var newArray []string
    var toAdd bool = true
    var removed int = 0
    //sort.Ints(j)
    for i := 0; i < len(data); i++ {
        for _, k := range j {
            // if k < i || k > i {
            //  break
            // } else
            if i == k {
                toAdd = false
                break
            }
        }
        if toAdd {
            newArray = append(newArray, data[i])
            removed++
        }
        toAdd = true
    }
    return newArray
}

当切片不是很大时(排序时间),可以删除注释以提高性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接