banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

【轉載】關於切片操作的技巧

title: 【轉載】關於切片操作的技巧
date: 2021-08-11 10:47:54
comment: false
toc: true
category:

  • Golang
    tags:
  • 轉載
  • Go
  • 切片
  • 操作

本文轉載自:[譯] 關於切片操作的技巧 | 李文周的博客


本文翻譯自官方 wiki,整理了 Go 語言中關於切片操作的一些技巧。

備註:由於行文需要,一些細節與原文存在些許出入。

切片操作常用技巧#

複製#

將切片 a 中的元素複製到切片 b 中。

最簡單的、最常用的方法就是使用內置的copy函數。

b = make([]T, len(a))  // 一次將內存申請到位  
copy(b, a)  

除了使用內置的copy函數外,還有下面兩種使用append函數複製切片的方法。

b = append([]T(nil), a...)  
b = append(a[:0:0], a...)  

這兩種方法通常比使用copy函數複製的方法要慢一些,但是如果在複製之後有更多的元素要添加到 b 中,那麼它們的效率會更高。

剪切#

將切片 a 中索引 i~j 位置的元素剪切掉。

可以按照下面的方式,使用append函數完成。

a = append(a[:i], a[j:]...)  

刪除#

將切片 a 中索引位置為 i 的元素刪除。

同樣可以按照上面剪切的方式使用append函數完成刪除操作。

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

或者搭配copy函數使用切片表達式完成刪除操作。

a = a[:i+copy(a[i:], a[i+1:])]  

此外,如果只需要刪除掉索引為 i 的元素,無需保留切片元素原有的順序,那麼還可以使用下面這種簡單的方式進行刪除。

a[i] = a[len(a)-1]  // 將最後一個元素移到索引i處  
a = a[:len(a)-1]    // 截掉最後一個元素  

剪切或刪除操作可能引起的內存泄露#

需要特別注意的是。如果切片 a 中的元素是一個指針類型或包含指針字段的結構體類型(需要被垃圾回收),上面剪切和刪除的示例代碼會存在一個潛在的內存泄漏問題:一些具有值的元素仍被切片 a 引用,因此無法被垃圾回收機制回收掉。下面的代碼可以解決這個問題。

剪切#

copy(a[i:], a[j:])  
for k, n := len(a)-j+i, len(a); k < n; k++ {  
	a[k] = nil // 或類型T的零值  
}  
a = a[:len(a)-j+i]  

刪除#

copy(a[i:], a[i+1:])  
a[len(a)-1] = nil // 或類型T的零值  
a = a[:len(a)-1]  

刪除但不保留元素原有順序#

a[i] = a[len(a)-1]  
a[len(a)-1] = nil  
a = a[:len(a)-1]  

內部擴張#

在切片 a 的索引 i 之後擴張 j 個元素。

使用兩個append函數完成,即先將索引 i 之後的元素追加到一個長度為 j 的切片後,再將這個切片中的所有元素追加到切片 a 的索引 i 之後。

a = append(a[:i], append(make([]T, j), a[i:]...)...)  

擴張的這一部分元素為 T 類型的零值。

尾部擴張#

將切片 a 的尾部擴張 j 個元素的空間。

a = append(a, make([]T, j)...)  

擴張的這一部分元素同樣為 T 類型的零值。

過濾#

按照一定的規則將切片 a 中的元素進行就地過濾。

這裡假設過濾的條件已封裝為keep函數,使用for range遍歷切片 a 的所有元素逐一調用keep函數進行過濾。

n := 0  
for _, x := range a {  
	if keep(x) {  
		a[n] = x  // 保留該元素  
		n++  
	}  
}  
a = a[:n]  // 截取切片中需保留的元素  

插入#

將元素 x 插入切片 a 的索引 i 處。

還是使用兩個append函數完成插入 x 的操作。

a = append(a[:i], append([]T{x}, a[i:]...)...)  

第二個append函數創建了一個具有自己底層數組的新切片,並將a[i:]中的元素複製到該切片,然後由第一個append函數將這些元素複製回切片 a。

我們可以通過使用另一種方法來避免新切片的創建(以及由此產生的內存垃圾)和第二個副本:

a = append(a, 0 /* 這裡應使用元素類型的零值 */)  
copy(a[i+1:], a[i:])  
a[i] = x  

追加#

將元素 x 追加到切片 a 的最後。

這裡使用append函數即可。

a = append(a, x)  

彈出#

將切片 a 的最後一個元素彈出。

這裡使用切片表達式完成彈出操作。

x, a = a[len(a)-1], a[:len(a)-1]  

彈出切片 a 的第一個元素。

x, a = a[0], a[1:]  

前插#

將元素 x 前插到切片 a 的開始。

a = append([]T{x}, a...)  

其他技巧#

過濾而不分配內存#

此技巧使用了一個事實,即切片 b 與原始切片 a 共享相同的底層數組和容量,因此原存儲空間已重新用於過濾後的切片。當然原始切片的內容被修改了。

b := a[:0]  
for _, x := range a {  
	if f(x) {  
		b = append(b, x)  
	}  
}  

對於必須被垃圾回收的元素,在完成上述操作後可以添加以下代碼:

for i := len(b); i < len(a); i++ {  
	a[i] = nil // 或T類型的零值  
}  

翻轉#

將切片 a 的元素順序翻轉。

通過迭代兩兩互換元素完成。

for i := len(a)/2-1; i >= 0; i-- {  
	opp := len(a)-1-i  
	a[i], a[opp] = a[opp], a[i]  
}  

同樣的操作:

for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {  
	a[left], a[right] = a[right], a[left]  
}  

洗牌#

打亂切片 a 中元素的順序。

Fisher–Yates 算法:

for i := len(a) - 1; i > 0; i-- {  
    j := rand.Intn(i + 1)  
    a[i], a[j] = a[j], a[i]  
}  

從 go1.10 開始,可以使用math/rand.Shuffle

rand.Shuffle(len(a), func(i, j int) {  
	a[i], a[j] = a[j], a[i]  
})  

使用最小分配進行批處理#

如果你想對一個大型切片 a 的元素分批進行處理,這會很有用。

actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}  
batchSize := 3  
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)  

for batchSize < len(actions) {  
    actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])  
}  
batches = append(batches, actions)  

得到的效果如下:

[[0 1 2] [3 4 5] [6 7 8] [9]]  

原地刪除重複元素(元素可比較)#

import "sort"  

in := []int{3,2,1,4,3,2,1,4,1} // 切片元素可以是任意可排序的類型  
sort.Ints(in)  
j := 0  
for i := 1; i < len(in); i++ {  
	if in[j] == in[i] {  
		continue  
	}  
	j++  
	// 需要保存原始數據時  
	// in[i], in[j] = in[j], in[i]  
	// 只需要保存需要的數據時  
	in[j] = in[i]  
}  
result := in[:j+1]  
fmt.Println(result) // [1 2 3 4]  

存在就移到前面,不存在就插入到前面#

如果給定的元素在切片中存在則把該元素移到切片的頭部,如果不存在則將該元素插入到切片的頭部。

// moveToFront 把needle移動或添加到haystack的前面  
func moveToFront(needle string, haystack []string) []string {  
	if len(haystack) != 0 && haystack[0] == needle {  
		return haystack  
	}  
	prev := needle  
	for i, elem := range haystack {  
		switch {  
		case i == 0:  
			haystack[0] = needle  
			prev = elem  
		case elem == needle:  
			haystack[i] = prev  
			return haystack  
		default:  
			haystack[i] = prev  
			prev = elem  
		}  
	}  
	return append(haystack, prev)  
}  

haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]  
haystack = moveToFront("c", haystack)         // [c a b d e]  
haystack = moveToFront("f", haystack)         // [f c a b d e]  

滑動窗口#

將切片 input 生成 size 大小的滑動窗口。

func slidingWindow(size int, input []int) [][]int {  
	// 返回入參的切片作為第一個元素  
	if len(input) <= size {  
		return [][]int{input}  
	}  

	// 以所需的精確大小分配切片  
	r := make([][]int, 0, len(input)-size+1)  

	for i, j := 0, size; j <= len(input); i, j = i+1, j+1 {  
		r = append(r, input[i:j])  
	}  

	return r  
}  

示例:

a := []int{1, 2, 3, 4, 5}  
res := slidingWindow(2, a)  
fmt.Println(res)  

輸出:

[[1 2] [2 3] [3 4] [4 5]]  

參考資料: https://github.com/golang/go/wiki/SliceTricks

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。