Python与Go列表切片越界的对比
需求
很简单的知识点,做一下小结。
最近写代码需要做一下切片的操作,比如给定这样一个切片:
lst1 = [1,2,3,4,5,6,7,8,9,10,11,12,13]
将这个切片里面的元素按照每4个为一组,每一组组成单独的切片,然后再组合到外层的切片中,结果像这样:
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13]]
python的写法
# -*- coding:utf-8 -*- lst1 = [1,2,3,4,5,6,7,8,9,10,11,12,13] lst2 = ["w","wg","wa","jj","aa","ff","gg","hh","asd","ww","ee","gg","nn","mm"] print(len(lst1)) # 13 print(len(lst2)) # 14 ### 超限切片 ret_lst1 = list() ret_lst2 = list() # 根据lst1构建新的列表 for i in range(0,len(lst1),4): curr_lst = lst1[i:i+4] ret_lst1.append(curr_lst) # 根据lst2构建新的列表 for i in range(0,len(lst2),4): curr_lst = lst2[i:i+4] ret_lst2.append(curr_lst) ### 注意结果最后一个列表不足的话不会报错 print(ret_lst1) # [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13]] print(ret_lst2) # [['w', 'wg', 'wa', 'jj'], ['aa', 'ff', 'gg', 'hh'], ['asd', 'ww', 'ee', 'gg'], ['nn', 'mm']]
Go的写法1 -- 保证不越界的写法
错误的写法
package t9 import ( "fmt" "testing" ) func TestOutRange(t *testing.T) { lst1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} lst2 := []string{"w", "wg", "wa", "jj", "aa", "ff", "gg", "hh", "asd", "ww", "ee", "gg", "nn", "mm"} fmt.Println(len(lst1)) // 13 fmt.Println(len(lst2)) // 14 // 结果 var retLst1 [][]int var retLst2 [][]string // 超限切片 for i := 0; i < len(lst1); i += 4 { currLst := lst1[i : i+4] retLst1 = append(retLst1, currLst) } for i := 0; i < len(lst2); i += 4 { currLst := lst2[i : i+4] retLst2 = append(retLst2, currLst) } }
这样写会上报一个错误:
--- FAIL: TestOutRange (0.00s) panic: runtime error: slice bounds out of range [:16] with capacity 13 [recovered] panic: runtime error: slice bounds out of range [:16] with capacity 13
错误的原因是,我们每次获取currLst的时候,是按照 lst[i i+4] 这样直接切片的,但是,如果i+4大于列表lst的长度的话,会上报越界错误!
正确的写法 ***
既然i+4可能会超出lst的长度越界,那我们在切片的时候判断一下二者的大小即可:
package t9 import ( "fmt" "testing" ) // 返回两个int最小的那个 func minInt(a, b int) int { if a < b { return a } return b } func TestOutRange(t *testing.T) { lst1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} lst2 := []string{"w", "wg", "wa", "jj", "aa", "ff", "gg", "hh", "asd", "ww", "ee", "gg", "nn", "mm"} fmt.Println(len(lst1)) // 13 fmt.Println(len(lst2)) // 14 // 结果 var retLst1 [][]int var retLst2 [][]string // 切片 for i := 0; i < len(lst1); i += 4 { // 保证不越界 currLst := lst1[i:minInt(i+4, len(lst1))] retLst1 = append(retLst1, currLst) } for i := 0; i < len(lst2); i += 4 { // 保证不越界 currLst := lst2[i:minInt(i+4, len(lst2))] retLst2 = append(retLst2, currLst) } // 注意结果有填充的默认值 fmt.Println(retLst1) // [[1 2 3 4] [5 6 7 8] [9 10 11 12] [13]] fmt.Println(retLst2) // [[w wg wa jj] [aa ff gg hh] [asd ww ee gg] [nn mm]] }
Go的写法2 -- 越界不超过容量也行,但是要注意“零值”的坑
// 错误的写法 func TestZ112(t *testing.T) { type student struct { sid int name string } lst := make([]*student, 0) for i := 0; i < 5; i++ { currStu := student{ sid: i, name: fmt.Sprintf("whw-%v", i), } lst = append(lst, &currStu) } fmt.Println("lst 长度:", len(lst), "容量:", cap(lst)) // lst 长度: 5, 容量: 8 curr := lst[0:7] fmt.Println("curr >>> ", len(curr), cap(curr), curr) // Notice 从上面的结果来看lst的容量是8,所以我们切片长度不超过8就没问题 // Notice 但是实际上lst中只有5个有效的数据,使用 [0:7] 切片的话会多出2个数据,这2个数据golang会给我们补充成 "零值"! // 7 8 [0x1400000c750 0x1400000c768 0x1400000c780 0x1400000c798 0x1400000c7b0 <nil> <nil>] // Notice 这样就很危险了!如果零值是 nil 的话,下面获取属性的操作就会报panic了!!! for _, item := range curr { fmt.Println(">>> ", item.sid, item.name) } } // 正确的写法 func TestZ1232(t *testing.T) { type student struct { sid int name string } lst := make([]*student, 0) for i := 0; i < 5; i++ { currStu := student{ sid: i, name: fmt.Sprintf("whw-%v", i), } lst = append(lst, &currStu) } fmt.Println("lst 长度:", len(lst), "容量:", cap(lst)) // lst 长度: 5, 容量: 8 curr := lst[0:len(lst)] fmt.Println("curr >>> ", len(curr), cap(curr), curr) for _, item := range curr { fmt.Println(">>> ", item.sid, item.name) } } func GetMinInt(min, max int) int { if min <= max { return min } return max }
~~~