Golang中与itertools.chain等效的函数是什么?

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

Golang's equivalent of itertools.chain?

问题

在Golang中,我如何使用单个for循环迭代三个切片,而不创建一个包含所有三个切片元素副本的新切片?是否有类似Python的itertools.chain的功能?

英文:

In Golang, how can I iterate over three slices with a single for loop, without creating a new slice containing copies of the elements in all three slices? Is there something like Python's itertools.chain?

答案1

得分: 2

使用泛型的简单解决方案

package main

import "fmt"

func Chain[T any](f func(e T), slices ...[]T) {
	for _, slice := range slices {
		for _, e := range slice {
			f(e)
		}
	}
}

func main() {
	slice1 := []int{1, 2, 3}
	slice2 := []int{10, 20, 30}
	slice3 := []int{100, 200, 300}

	Chain(func(e int) {
		fmt.Println(e)
	}, slice1, slice2, slice3)
}

输出:

1
2
3
10
20
30
100
200
300

函数Chain接受一个函数参数,该函数将按顺序对每个切片中的每个元素执行。这个函数将作为循环体代码。

该解决方案可以扩展以保留其他循环特性,如break和索引号:

func Chain[T any](f func(i int, e T) bool, slices ...[]T) {
	var i int
	for _, slice := range slices {
		for _, e := range slice {
			if !f(i, e) {
				return
			}
			i++
		}
	}
}

...

	Chain(func(i int, e int) bool {
		fmt.Println(i, "-", e)
		return (e <= 20)
	}, slice1, slice2, slice3)
...

f返回false时,循环将“中断”。

输出:

0 - 1
1 - 2
2 - 3
3 - 10
4 - 20
5 - 30
英文:

A simple solution using generics

package main

import &quot;fmt&quot;

func Chain[T any](f func(e T), slices ...[]T) {
	for _, slice := range slices {
		for _, e := range slice {
			f(e)
		}
	}
}

func main() {
	slice1 := []int{1, 2, 3}
	slice2 := []int{10, 20, 30}
	slice3 := []int{100, 200, 300}

	Chain(func(e int) {
		fmt.Println(e)
	}, slice1, slice2, slice3)
}

Output:

1
2
3
10
20
30
100
200
300

The function Chain takes a function parameter, which will be executed for each element in each slice consecutively. This function will serve as your loop body code.

The solution can be extended to retain other looping features such as break and index numbers:

func Chain[T any](f func(i int, e T) bool, slices ...[]T) {
	var i int
	for _, slice := range slices {
		for _, e := range slice {
			if !f(i, e) {
				return
			}
			i++
		}
	}
}

...

	Chain(func(i int, e int) bool {
		fmt.Println(i, &quot;-&quot;, e)
		return (e &lt;= 20)
	}, slice1, slice2, slice3)
...

The loop will "break" when f returns false.

Output:

0 - 1
1 - 2
2 - 3
3 - 10
4 - 20
5 - 30

答案2

得分: 0

你可以使用通道来构建自己的chain()方法。

package main

import "fmt"

func main() {
    slice1 := []int{2, 3, 5, 7, 11, 13}
    slice2 := []int{21321, 12313, 213}
    slice3 := []int{8987, 988, 675676, 6587686}

    for val := range chain(slice1, slice2, slice3) {
        fmt.Println(val)
    }
}

func chain[T any](slices ...[]T) <-chan T {
    channel := make(chan T)
    go func() {
        for _, slice := range slices {
            for _, val := range slice {
                channel <- val
            }
        }
        close(channel)
    }()
    return channel
}

这里是Go Playground的链接。

基本上,这个想法是使用可变参数slices,它可以容纳任意数量的切片,在函数中我们创建一个给定类型的通道,并返回该通道,以便调用者可以从中检索值,在我们执行这个操作之前,我们启动一个 Goroutine,它将实际上通过通道发送值。为了能够使用range,我们需要在循环遍历完所有切片后close()通道。

这使用了在最近发布的Go 1.18中提供的泛型功能。

编辑

正如Zombo所指出的,如果在上面的循环中使用break,将会泄漏一个 Goroutine,因为并不是所有发送到通道的值都会从中检索出来。

为了获得break行为而不泄漏 Goroutine,你可以传递一个predicate函数,当它返回true时,将关闭通道,从而防止泄漏。

类似地,你可以根据你的用例传递回调函数来处理各种情况(例如过滤)。

package main

import "fmt"

func main() {
    slice1 := []int{2, 3, 5, 7, 11, 13}
    slice2 := []int{21321, 12313, 213}
    slice3 := []int{8987, 988, 675676, 6587686}

    for val := range chainUntil(func(item int) bool {
        return item > 6
    }, slice1, slice2, slice3) {
        fmt.Println(val)
    }
}

func chainUntil[T any](predicate func(item T) bool, slices ...[]T) <-chan T {
    channel := make(chan T)
    go func() {
        for _, slice := range slices {
            for _, val := range slice {
                if predicate(val) == true {
                    close(channel)
                    return
                }
                channel <- val
            }
        }
        close(channel)
    }()
    return channel
}

预期输出:

2
3
5
英文:

You could use channels to build your own chain() method.

package main

import &quot;fmt&quot;

func main() {
	slice1 := []int{2, 3, 5, 7, 11, 13}
	slice2 := []int{21321, 12313, 213}
	slice3 := []int{8987, 988, 675676, 6587686}

	for val := range chain(slice1, slice2, slice3) {
		fmt.Println(val)
	}
}

func chain[T any](slices ...[]T) &lt;-chan T {
	channel := make(chan T)
	go func() {
		for _, slice := range slices {
			for _, val := range slice {
				channel &lt;- val
			}
		}
		close(channel)
	}()
	return channel
}

Here the link to Go Playground.

Essentially the idea is to use variadic argument slices which can hold and undetermined number of slices and in the function we then create a channel of the given type return that channel so a caller can retrieve values from it and before we do that we start a Goroutine which will actually send the values over the channel. In order to be able to use range we need to close() the channel once we have looped over all slices.

This uses Generics which are available as of Go 1.18 which has just recently been released.

Edit

As correctly indicated by Zombo you will leak a goroutine if you break in the range loop above as not all values sent to the channel will be retrieved from it.

In order to get a break behavior without leaking a go routine you could pass a predicate function which, when it returns true will close the channel and therefore prevent a leak.

Similarly you could pass callback functions for all kinds of stuff (e.g. filtering) according to your use case.

package main

import &quot;fmt&quot;

func main() {
	slice1 := []int{2, 3, 5, 7, 11, 13}
	slice2 := []int{21321, 12313, 213}
	slice3 := []int{8987, 988, 675676, 6587686}

	for val := range chainUntil(func(item int) bool {
		return item &gt; 6
	}, slice1, slice2, slice3) {
		fmt.Println(val)
	}
}

func chainUntil[T any](predicate func(item T) bool, slices ...[]T) &lt;-chan T {
	channel := make(chan T)
	go func() {
		for _, slice := range slices {
			for _, val := range slice {
				if predicate(val) == true {
					close(channel)
					return
				}
				channel &lt;- val
			}
		}
		close(channel)
	}()
	return channel
}

Expected output:

2
3
5

huangapple
  • 本文由 发表于 2022年4月15日 05:44:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/71877950.html
匿名

发表评论

匿名网友

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

确定