Go语言中的切片分块

43

我有一个包含大约210万条日志字符串的切片,我希望创建一个尽可能均匀分布字符串的切片。

这是我目前的代码:

// logs is a slice with ~2.1 million strings in it.
var divided = make([][]string, 0)
NumCPU := runtime.NumCPU()
ChunkSize := len(logs) / NumCPU
for i := 0; i < NumCPU; i++ {
    temp := make([]string, 0)
    idx := i * ChunkSize
    end := i * ChunkSize + ChunkSize
    for x := range logs[idx:end] {
        temp = append(temp, logs[x])
    }
    if i == NumCPU {
        for x := range logs[idx:] {
            temp = append(temp, logs[x])
        }
    }
    divided = append(divided, temp)
}

idx := i * ChunkSize 将给我当前 logs 索引的"块起始位置",end := i * ChunkSize + ChunkSize 将给我"块结束"或该块范围的结束。我在 Go 中找不到任何文档或示例来拆分切片或遍历有限范围,所以这就是我想出来的方法。然而,它只多次复制第一个块,因此无法工作。

如何尽可能平均地在 Go 中将一个切片分块?

8个回答

102
你不需要创建新的片段,只需将logs的片段附加到divided的片段中即可。 http://play.golang.org/p/vyihJZlDVy
var divided [][]string

chunkSize := (len(logs) + numCPU - 1) / numCPU

for i := 0; i < len(logs); i += chunkSize {
    end := i + chunkSize

    if end > len(logs) {
        end = len(logs)
    }

    divided = append(divided, logs[i:end])
}

fmt.Printf("%#v\n", divided)

啊啊啊啊,这就是我缺失的东西。我一直在尝试迭代有限范围,而不是通过块长度进行迭代。我花了8个小时来尝试让我的代码工作,哈哈。感谢你的答案,非常有帮助。 - SiennaD.
1
你在divided的长度上看起来有一个偏差。例如,numCPU = 3; logs = logs[:8]; chunkSize := len(logs) / numCPU; if chunkSize == 0 { chunkSize = 1 }; 对于3个CPU和8个日志进行划分时,应该是4而不是3:http://play.golang.org/p/EdhiclVR0q。对于`chunkSize`,请写成`chunkSize := (len(logs) + numCPU - 1) / numCPU;`:http://play.golang.org/p/xDyFXt45Fz。 - peterSO
@peterSO:谢谢,我只是从原始文件中复制粘贴,没有想到要检查一下。 - JimB
1
如果有人想知道(len(logs) + numCPU - 1) / numCPU是什么意思,那就是len(logs)/numCPU的上限。 - Arpan Srivastava

15

使用泛型(Go版本>=1.18):

func chunkBy[T any](items []T, chunkSize int) (chunks [][]T) {
    for chunkSize < len(items) {
        items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
    }
    return append(chunks, items)
}

Playground网址

或者,如果你想手动设置容量:

func chunkBy[T any](items []T, chunkSize int) [][]T {
    var _chunks = make([][]T, 0, (len(items)/chunkSize)+1)
    for chunkSize < len(items) {
        items, _chunks = items[chunkSize:], append(_chunks, items[0:chunkSize:chunkSize])
    }
    return append(_chunks, items)
}

Playground网址


最好的方法!但是返回值可以改进,例如 items := var s []int。我可以更新,需要吗? - Markus Schulte

6

根据Slice Tricks,批处理最小化分配。

如果您想在大型切片上进行批处理,则此方法非常有用。

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]]

6

这是另一种变体。它的速度大约比由JimB提出的方法快2.5倍。测试和基准测试结果在这里

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

func chunks(xs []string, chunkSize int) [][]string {
    if len(xs) == 0 {
        return nil
    }
    divided := make([][]string, (len(xs)+chunkSize-1)/chunkSize)
    prev := 0
    i := 0
    till := len(xs) - chunkSize
    for prev < till {
        next := prev + chunkSize
        divided[i] = xs[prev:next]
        prev = next
        i++
    }
    divided[i] = xs[prev:]
    return divided
}

它的工作速度大约快了2.5倍,但不幸的是没有解释原因。我猜测可能是减少了JT分配。 - user4466350
2
@mh-cbon 主要原因是预分配的切片,因为我们知道它的确切最终大小。这使得我们每个操作只需要9次分配,而不是53次,并且大部分速度都得到了提升。 - SIREN
也适用于 chunkSize > len(xs) - Mirco De Zorzi

1
func chunkSlice(items []int32, chunkSize int32) (chunks [][]int32) {
 //While there are more items remaining than chunkSize...
 for chunkSize < int32(len(items)) {
    //We take a slice of size chunkSize from the items array and append it to the new array
    chunks = append(chunks, items[0:chunkSize])
    //Then we remove those elements from the items array
    items = items[chunkSize:]
 }
 //Finally we append the remaining items to the new array and return it
 return append(chunks, items)
}

可视化示例

假设我们想把一个数组分成长度为3的块。

items:  [1,2,3,4,5,6,7]
chunks: []

items:  [1,2,3,4,5,6,7]
chunks: [[1,2,3]]

items:  [4,5,6,7]
chunks: [[1,2,3]]

items:  [4,5,6,7]
chunks: [[1,2,3],[4,5,6]]

items:  [7]
chunks: [[1,2,3],[4,5,6]]

items:  [7]
chunks: [[1,2,3],[4,5,6],[7]]
return

虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文将改善答案的长期价值。您可以在帮助中心找到有关编写良好答案的更多信息:https://stackoverflow.com/help/how-to-answer。祝你好运! - nima

0

有一个go-deeper/chunks模块,它允许将任何类型(使用泛型)的切片分成具有近似相等值总和的块。

package main

import (
    "fmt"

    "github.com/go-deeper/chunks"
)

func main() {
    slice := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    sliceChunks := chunks.Split(slice, 7)

    fmt.Println(sliceChunks)
}

输出:

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

0

总结:

// ChunkStringSlice divides []string into chunks of chunkSize.
func ChunkStringSlice(s []string, chunkSize int) [][]string {
    chunkNum := int(math.Ceil(float64(len(s)) / float64(chunkSize)))
    res := make([][]string, 0, chunkNum)
    for i := 0; i < chunkNum-1; i++ {
        res = append(res, s[i*chunkSize:(i+1)*chunkSize])
    }
    res = append(res, s[(chunkNum-1)*chunkSize:])
    return res
}

// ChunkStringSlice2 divides []string into chunkNum chunks.
func ChunkStringSlice2(s []string, chunkNum int) [][]string {
    res := make([][]string, 0, chunkNum)
    chunkSize := int(math.Ceil(float64(len(s)) / float64(chunkNum)))
    for i := 0; i < chunkNum-1; i++ {
        res = append(res, s[i*chunkSize:(i+1)*chunkSize])
    }
    res = append(res, s[(chunkNum-1)*chunkSize:])
    return res
}

0

使用反射来处理任何 []T 类型

https://github.com/kirito41dd/xslice

package main

import (
    "fmt"
    "github.com/kirito41dd/xslice"
)

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    i := xslice.SplitToChunks(s, 3)
    ss := i.([][]int)
    fmt.Println(ss) // [[0 1 2] [3 4 5] [6 7 8] [9]]
}

https://github.com/kirito41dd/xslice/blob/e50d91fa75241a3a03d262ad51c8e4cb2ea4b995/split.go#L12

func SplitToChunks(slice interface{}, chunkSize int) interface{} {
    sliceType := reflect.TypeOf(slice)
    sliceVal := reflect.ValueOf(slice)
    length := sliceVal.Len()
    if sliceType.Kind() != reflect.Slice {
        panic("parameter must be []T")
    }
    n := 0
    if length%chunkSize > 0 {
        n = 1
    }
    SST := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length/chunkSize+n)
    st, ed := 0, 0
    for st < length {
        ed = st + chunkSize
        if ed > length {
            ed = length
        }
        SST = reflect.Append(SST, sliceVal.Slice(st, ed))
        st = ed
    }
    return SST.Interface()
}

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