如何在遍历切片时删除其中的元素?

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

How to remove items from a slice while ranging over it?

问题

在遍历切片时,从切片中删除元素的最佳方法是使用切片的索引进行删除。在上面的示例中,可以使用切片的索引来删除不需要的元素。以下是修改后的代码:

type MultiDataPoint []*DataPoint

func (m MultiDataPoint) Json() ([]byte, error) {
	for i := range m {
		err := m[i].clean()
		if err != nil {
			// 从切片中删除元素
			m = append(m[:i], m[i+1:]...)
			// 更新索引,因为切片长度已经改变
			i--
		}
	}
	return json.Marshal(m)
}

在上述代码中,使用 append 函数将需要保留的元素重新组合成切片,从而实现删除元素的效果。注意,在删除元素后,需要更新索引 i 的值,以便正确遍历切片。

希望对你有帮助!如果有任何其他问题,请随时提问。

英文:

What is the best way to remove items from a slice while ranging over it?

For example:

type MultiDataPoint []*DataPoint

func (m MultiDataPoint) Json() ([]byte, error) {
	for i, d := range m {
		err := d.clean()
		if ( err != nil ) {
			//Remove the DP from m
		}
	}
	return json.Marshal(m)
}

答案1

得分: 99

如您在其他地方提到的那样,您可以分配新的内存块,并将仅有效的元素复制到其中。但是,如果您想避免分配,您可以原地重写切片:

i := 0 // 输出索引
for _, x := range s {
    if isValid(x) {
        // 复制并增加索引
        s[i] = x
        i++
    }
}
// 通过擦除截断的值来防止内存泄漏
// (如果值不包含指针,直接或间接地,则不需要)
for j := i; j < len(s); j++ {
    s[j] = nil
}
s = s[:i]

完整示例:http://play.golang.org/p/FNDFswPeDJ

请注意,这将在底层数组中索引 i 之后保留旧值,因此,如果值是指针或包含指针,则会在切片本身被垃圾回收之前导致内存泄漏。您可以在截断切片之前将从索引 i 到切片末尾的所有值设置为 nil 或零值来解决这个问题。

英文:

As you have mentioned elsewhere, you can allocate new memory block and copy only valid elements to it. However, if you want to avoid the allocation, you can rewrite your slice in-place:

i := 0 // output index
for _, x := range s {
    if isValid(x) {
        // copy and increment index
        s[i] = x
        i++
    }
}
// Prevent memory leak by erasing truncated values 
// (not needed if values don&#39;t contain pointers, directly or indirectly)
for j := i; j &lt; len(s); j++ {
    s[j] = nil
}
s = s[:i]

Full example: http://play.golang.org/p/FNDFswPeDJ

Note this will leave old values after index i in the underlying array, so this will leak memory until the slice itself is garbage collected, if values are or contain pointers. You can solve this by setting all values to nil or the zero value from i until the end of the slice before truncating it.

答案2

得分: 28

我知道这个问题很久以前就有人回答了,但是我在其他语言中使用类似的方法,但我不知道这是否是Go语言的方式。

只需从后往前迭代,这样你就不必担心被删除的索引。我正在使用与Adam相同的示例。

m := []int{3, 7, 2, 9, 4, 5}

for i := len(m)-1; i >= 0; i-- {
    if m[i] < 5 {
        m = append(m[:i], m[i+1:]...)
    }
}
英文:

I know its answered long time ago but i use something like this in other languages, but i don't know if it is the golang way.

Just iterate from back to front so you don't have to worry about indexes that are deleted. I am using the same example as Adam.

m = []int{3, 7, 2, 9, 4, 5}

for i := len(m)-1; i &gt;= 0; i-- {
	if m[i] &lt; 5 {
		m = append(m[:i], m[i+1:]...)
	}
}

答案3

得分: 17

可能有更好的方法,但这里有一个示例,可以从切片中删除偶数值:

m := []int{1,2,3,4,5,6}

deleted := 0
for i := range m {
    j := i - deleted
    if (m[j] & 1) == 0 {
        m = m[:j+copy(m[j:], m[j+1:])]
        deleted++
    } 
}

请注意,我没有使用i, d := range m语法来获取元素,因为一旦你开始从切片中删除元素,d将会被设置为错误的元素。

英文:

There might be better ways, but here's an example that deletes the even values from a slice:

m := []int{1,2,3,4,5,6}

deleted := 0
for i := range m {
    j := i - deleted
    if (m[j] &amp; 1) == 0 {
        m = m[:j+copy(m[j:], m[j+1:])]
        deleted++
    } 
}

Note that I don't get the element using the i, d := range m syntax, since d would end up getting set to the wrong elements once you start deleting from the slice.

答案4

得分: 12

这里是一种更符合Go语言习惯的从切片中删除元素的方法。

temp := s[:0]
for _, x := range s {
    if isValid(x) {
        temp = append(temp, x)
    }
}
s = temp

Playground链接:https://play.golang.org/p/OH5Ymsat7s9

注意:这个示例和Playground链接是基于@tomasz的答案https://stackoverflow.com/a/20551116/12003457。

英文:

Here is a more idiomatic Go way to remove elements from slices.

temp := s[:0]
for _, x := range s {
	if isValid(x) {
		temp = append(temp, x)
	}
}
s = temp

Playground link: https://play.golang.org/p/OH5Ymsat7s9

Note: The example and playground links are based upon @tomasz's answer https://stackoverflow.com/a/20551116/12003457

答案5

得分: 8

另一种选择是使用普通的for循环,使用切片的长度,并在每次删除一个值时将索引减1。请参考以下示例:

m := []int{3, 7, 2, 9, 4, 5}

for i := 0; i < len(m); i++ {
    if m[i] < 5 {
        m = append(m[:i], m[i+1:]...)
        i-- // 切片变短,索引减1
    }
}

我不知道len()是否会使用足够的资源以产生任何差异,但你也可以只运行一次并从长度值中减去相应的值:

m := []int{3, 7, 2, 9, 4, 5}

for i, s := 0, len(m); i < s; i++ {
    if m[i] < 5 {
        m = append(m[:i], m[i+1:]...)
        s--
        i--
    }
}
英文:

One other option is to use a normal for loop using the length of the slice and subtract 1 from the index each time a value is removed. See the following example:

m := []int{3, 7, 2, 9, 4, 5}

for i := 0; i &lt; len(m); i++ {
	if m[i] &lt; 5 {
		m = append(m[:i], m[i+1:]...)
		i-- // -1 as the slice just got shorter
	}
}

I don't know if len() uses enough resources to make any difference but you could also run it just once and subtract from the length value too:

m := []int{3, 7, 2, 9, 4, 5}

for i, s := 0, len(m); i &lt; s; i++ {
	if m[i] &lt; 5 {
		m = append(m[:i], m[i+1:]...)
		s--
		i--
	}
}

答案6

得分: 4

类似于:

m = append(m[:i], m[i+1:]...)
英文:

Something like:

m = append(m[:i], m[i+1:]...)

答案7

得分: 0

你甚至不需要倒数计算,但你需要检查是否已经到达数组的末尾,因为建议使用的append()会失败。以下是一个从排序列表中删除重复正整数的示例:

// 删除重复数字
numbers := []int{1, 2, 3, 3, 4, 5, 5}
log.Println(numbers)
for i, numbersCount, prevNum := 0, len(numbers), -1; i < numbersCount; numbersCount = len(numbers) {
    if numbers[i] == prevNum {
        if i == numbersCount-1 {
            numbers = numbers[:i]
        } else {
            numbers = append(numbers[:i], numbers[i+1:]...)
        }
        continue
    }
    prevNum = numbers[i]
    i++
}
log.Println(numbers)

Playground链接:https://play.golang.org/p/v93MgtCQsaN

英文:

You don't even need to count backwards but you do need to check that you're at the end of the array where the suggested append() will fail. Here's an example of removing duplicate positive integers from a sorted list:

<!-- language:go -->

// Remove repeating numbers
numbers := []int{1, 2, 3, 3, 4, 5, 5}
log.Println(numbers)
for i, numbersCount, prevNum := 0, len(numbers), -1; i &lt; numbersCount; numbersCount = len(numbers) {
	if numbers[i] == prevNum {
		if i == numbersCount-1 {
			numbers = numbers[:i]
		} else {
			numbers = append(numbers[:i], numbers[i+1:]...)

		}
		continue
	}
	prevNum = numbers[i]
	i++

}
log.Println(numbers)

Playground: https://play.golang.org/p/v93MgtCQsaN

答案8

得分: 0

我刚刚实现了一个方法,可以删除切片中的所有nil元素。

我将它用于解决了一个LeetCode问题,它完美地工作了。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNil(lists *[]*ListNode) {
    for i := 0; i < len(*lists); i++ {
        if (*lists)[i] == nil {
            *lists = append((*lists)[:i], (*lists)[i+1:]...)
            i--
        }
    }
}
英文:

I just implement a method which removes all nil elements in slice.

And I used it to solve a leetcode problems, it works perfectly.

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
 func removeNil(lists *[]*ListNode) {
	for i := 0; i &lt; len(*lists); i++ {
		if (*lists)[i] == nil {
			*lists = append((*lists)[:i], (*lists)[i+1:]...)
			i--
		}
	}
}

答案9

得分: 0

你可以避免内存泄漏,如@tomasz的答案中所建议的,通过使用完整切片表达式来控制底层数组的容量。看一下下面这个函数,它可以从一个整数切片中删除重复的元素:

package main

import "fmt"

func removeDuplicates(a []int) []int {
	for i, j := 0, 1; i < len(a) && j < len(a); i, j = i+1, j+1 {
		if a[i] == a[j] {
			copy(a[j:], a[j+1:])
			// 使用"完整切片表达式"调整底层数组的容量
			// a[low : high : max]
			a = a[:len(a)-1:len(a)-1]
			i--
			j--
		}
	}
	return a
}

func main() {
	a := []int{2, 3, 3, 3, 6, 9, 9}
	fmt.Println(a)
	a = removeDuplicates(a)
	fmt.Println(a)
}
// 输出:
// [2 3 3 3 6 9 9]
// [2 3 6 9]
英文:

You can avoid memory leaks, as suggested in @tomasz's answer, controlling the capacity of the underlying array with a full slice expression. Look at the following function that remove duplicates from a slice of integers:

package main

import &quot;fmt&quot;

func removeDuplicates(a []int) []int {

	for i, j := 0, 1; i &lt; len(a) &amp;&amp; j &lt; len(a); i, j = i+1, j+1 {
		if a[i] == a[j] {
			copy(a[j:], a[j+1:])
			// resize the capacity of the underlying array using the &quot;full slice expression&quot;
			// a[low : high : max]
			a = a[: len(a)-1 : len(a)-1]
			i--
			j--
		}
	}
	return a
}

func main() {
	a := []int{2, 3, 3, 3, 6, 9, 9}
	fmt.Println(a)
	a = removeDuplicates(a)
	fmt.Println(a)
}
// [2 3 3 3 6 9 9]
// [2 3 6 9]

答案10

得分: 0

根据@tomasz解释的原因,直接删除元素存在问题。这就是为什么在golang中的惯例是不这样做,而是重新构建切片。因此,一些答案超出了@tomasz的答案。

如果元素应该是唯一的,通常使用map的键来实现。我想贡献一个使用map进行删除的示例。

很好的一点是,布尔值可以用于第二个目的。在这个示例中,我计算Set a减去Set b。由于Golang没有真正的集合,我确保输出是唯一的。我也使用布尔值来进行算法。

这个map接近于O(n)。我不知道具体的实现。append()应该是O(n)。因此,运行时间与原地删除相似快。真正的原地删除会导致上限的移动以进行清理。如果不批量执行,运行时间应该更差。

在这种特殊情况下,我还将map用作寄存器,以避免在Set aSet b上进行嵌套循环,以使运行时间接近O(n)

type Set []int

func differenceOfSets(a, b Set) (difference Set) {
    m := map[int]bool{}
    for _, element := range a {
        m[element] = true
    }
    for _, element := range b {
        if _, registered := m[element]; registered {
            m[element] = false
        }
    }
    for element, present := range m {
        if present {
            difference = append(difference, element)
        }
    }
    return difference
}
英文:

For reasons @tomasz has explained, there are issues with removing in place. That's why it is practice in golang not to do that, but to reconstruct the slice. So several answers go beyond the answer of @tomasz.

If elements should be unique, it's practice to use the keys of a map for this. I like to contribute an example of deletion by use of a map.

What's nice, the boolean values are available for a second purpose. In this example I calculate Set a minus Set b. As Golang doesn't have a real set, I make sure the output is unique. I use the boolean values as well for the algorithm.

The map gets close to O(n). I don't know the implementation. append() should be O(n). So the runtime is similar fast as deletion in place. Real deletion in place would cause a shifting of the upper end to clean up. If not done in batch, the runtime should be worse.

In this special case, I also use the map as a register, to avoid a nested loop over Set a and Set b to keep the runtime close to O(n).

type Set []int

func differenceOfSets(a, b Set) (difference Set) {
	m := map[int]bool{}
	for _, element := range a {
		m[element] = true
	}
	for _, element := range b {
		if _, registered := m[element]; registered {
			m[element] = false
		}
	}
	for element, present := range m {
		if present {
			difference = append(difference, element)
		}
	}
	return difference
}

答案11

得分: -2

尝试使用SortBinary search

示例代码:

package main

import (
	"fmt"
	"sort"
)

func main() {
	// 我们的切片。
	s := []int{3, 7, 2, 9, 4, 5}
	
	// 1. 遍历切片。
	for i, v := range s {
		func(i, v int) {}(i, v)
	}
	
	// 2. 对其进行排序(按照您的条件)。
	sort.Slice(s, func(i, j int) bool {
		return s[i] < s[j]
	})
	
	// 3. 仅切割一次。
	i := sort.Search(len(s), func(i int) bool { return s[i] >= 5 })
	s = s[i:]
	
	// 完成!
	fmt.Println(s) // [5 7 9]
}

链接:https://play.golang.org/p/LnF6o0yMJGT

英文:

Try Sort and Binary search.

Example:

package main

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

func main() {
	// Our slice.
	s := []int{3, 7, 2, 9, 4, 5}
	
	// 1. Iterate over it.
	for i, v := range s {
		func(i, v int) {}(i, v)
	}
	
	// 2. Sort it. (by whatever condition of yours)
	sort.Slice(s, func(i, j int) bool {
		return s[i] &lt; s[j]
	})
	
	// 3. Cut it only once.
	i := sort.Search(len(s), func(i int) bool { return s[i] &gt;= 5 })
	s = s[i:]
	
	// That&#39;s it!
	fmt.Println(s) // [5 7 9]
}

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

huangapple
  • 本文由 发表于 2013年12月12日 22:08:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/20545743.html
匿名

发表评论

匿名网友

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

确定