Simple mapReduce operation on strings - go

I have a list of strings
elems := [n]string{...}
I want to perform a simple mapReduce operation, such that I
Map every string to a different string, let's say string -> $string
Reduce all the strings to one string with a separator, e.g. {s1, s2, s3} -> s1#s2#s3
all in all: {s1, s2, s3} -> $s1#$s2#$s3
What's the best way to do this?
I'm looking for efficiency and readability
Bonus points if it's generic enough to work not only on strings

For mapping just a list, you won't have much choice other than to go over each string. If the transform algo is time-consuming and you need speed, you can consider splitting the job and use a go routine. Finally you can use the strings.Join function which has an option to specify a separator, this normally performs the reduce part efficiently. The size of the dataset can also be a consideration, and for larger sized lists you may want to compare performance with strings.Join and your own customized algo and see if you want to use multiple go routines/channels to achieve what you want to.

If you don't need to do the 2 things separately, the end result can be achieved simply by using strings.Join():
package main
import (
"fmt"
"strings"
)
func main() {
a := []string{"a", "b", "c"}
p := "$"
fmt.Println(p + strings.Join(a[:], "#"+p))
}
prints $a#$b#$c
playground

Go is explicitly NOT a functional programming language.
You map and reduce using a for loop.
a := []string{"a", "b", "c"}
result := "initvalue"
for n, i := range a {
result += i + string(n)
}

If you are not going to perform any sort of IO operations inside your map functions (means they are doing just some computations), making it concurrent would make it slower for sure and even if you are doing some IO, you should benchmark. Concurrency would not make things faster necessarily and some times add unnecessary complications. In many cases just a simple for loop is sufficient.
If the map functions here are IO bound or are doing some sort of computation heavy calculations that do benefit from going concurrent, solutions can vary. For example NATS can be used to go beyond one machine and distribute the workload.
This is a relatively simple sample. Reduce phase is not multistage and is blocking:
import (
"fmt"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
type elem struct {
index int
value interface{}
}
func feed(elems []interface{}) <-chan elem {
result := make(chan elem)
go func() {
for k, v := range elems {
e := elem{
index: k,
value: v,
}
result <- e
}
close(result)
}()
return result
}
func mapf(
input <-chan elem,
mapFunc func(elem) elem) <-chan elem {
result := make(chan elem)
go func() {
for e := range input {
eres := mapFunc(e)
result <- eres
}
close(result)
}()
return result
}
// is blocking
func reducef(
input <-chan elem,
reduceFunc func([]interface{}) interface{}) interface{} {
buffer := make(map[int]interface{})
l := 0
for v := range input {
buffer[v.index] = v.value
if v.index > l {
l = v.index
}
}
data := make([]interface{}, l+1)
for k, v := range buffer {
data[k] = v
}
return reduceFunc(data)
}
func fanOutIn(
elemFeed <-chan elem,
mapFunc func(elem) elem, mapCount int,
reduceFunc func([]interface{}) interface{}) interface{} {
MR := make(chan elem)
wg := &sync.WaitGroup{}
for i := 0; i < mapCount; i++ {
mapResult := mapf(elemFeed, mapFunc)
wg.Add(1)
go func() {
defer wg.Done()
for v := range mapResult {
MR <- v
}
}()
}
go func() {
wg.Wait()
close(MR)
}()
return reducef(MR, reduceFunc)
}
func Test01(t *testing.T) {
elemFeed := feed([]interface{}{1, 2, 3})
finalResult := fanOutIn(
elemFeed,
func(e elem) elem {
return elem{
index: e.index,
value: fmt.Sprintf("[%v]", e.value),
}
},
3,
func(sl []interface{}) interface{} {
strRes := make([]string, len(sl))
for k, v := range sl {
strRes[k] = v.(string)
}
return strings.Join(strRes, ":")
})
assert.Equal(t, "[1]:[2]:[3]", finalResult)
}
And since it uses interface{} as the element type, it can get generalized.

Related

What does the underscore(_) do in for loop Golang?

I am just getting started learning the Golang language!
In for loop, I saw sometimes adding an underscore or without underscore.
Whatever add _ or not, I got the same result.
package main
import (
"fmt"
)
func main() {
doSomething()
sum := addValues(5, 8)
fmt.Println("The sum is", sum)
multiSum, multiCount := addAllValues(4, 7, 9)
fmt.Println("multisum", multiSum)
fmt.Println("multiCount", multiCount)
}
func doSomething() {
fmt.Println("Doing Something")
}
func addValues(value1 int, value2 int) int {
return value1 + value2
}
func addAllValues(values ...int) (int, int) {
total := 0
for _, v := range values {
total += v
}
return total, len(values)
}
func addAllValues(values ...int) (int, int) {
total := 0
for v := range values {
total += v
}
return total, len(values)
}
All I know is I don't care about the index. Is that all? or there is something more what I have to know??
I really appreciate your help!
For range over slices:
In for v := range values { the v is the index of the element in the slice.
In for _, v := range values { the v is the actual element value.
In for i, v := range values { the i is the index and the v is the element.
In for i, _ := range values { the i is the index of the element in the slice.
You can run this playground example to see the differences.
Range expression 1st value 2nd value
array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E
For more details see the spec.
If you don't want to use the variable that iterates in the loop, you can use _ to simply let Go ignore it:
mySlice := [int]{1,3,4,59,5}
for _,x := range mySlice {
fmt.Println(x)
}
By placing underscore you are telling the compiler this:
Ok, I'm aware that this function is returning something but I don't care! For example:
package main
import "fmt"
func main() {
mul1, add1 := test_function(2, 3)
fmt.Println(mul1, add1)
mul2, _ := test_function(4, 5)
fmt.Println(mul2)
_, add3 := test_function(7, 8)
fmt.Println(add3)
}
func test_function(a int, b int) (mul int, add int) {
return a * b, a + b
}
just to add to the amazing answer above:
I think one of the main benefits is to maintain readability in your program: if you replace the blank identifier with a variable then you have to use it or your program will not compile.
also this decrease memory allocation be neglecting one of the returned parameters...

Unexpected result when sorting digits using Goroutines

I am doing some exercises in Go, and I have a strange behavior with my code. If someone could explain me the reason it could be really great.
Here is the code : https://play.golang.org/p/4fQYHWpD6Cj
package main
import (
"fmt"
"sort"
"strconv"
)
func sortList (list []int, c chan []int) {
sort.Ints(list)
c <- list
}
func convert(sStr []string) (sInt []int){
cnvLoop: for _, r := range sStr {
digit, err := strconv.Atoi(r)
if err != nil {
break cnvLoop
}
sInt = append(sInt, digit)
}
return sInt
}
func split(list []int, size int) (tmpS []int, splS[]int) {
tmpS = list[size:]
splS = list[:size]
return tmpS, splS
}
func main() {
list := []string{"-9", "-11", "12", "13", "9"}
fmt.Println("Your unsorted digits: ", list)
sInt := convert(list)
size := len(sInt) / 4
tmpS, splS1 := split(sInt, size)
tmpS, splS2 := split(tmpS, size)
tmpS, splS3 := split(tmpS, size)
splS4 := tmpS
// sort in different go routines
c := make(chan []int)
go sortList(splS1, c)
go sortList(splS2, c)
go sortList(splS3, c)
go sortList(splS4, c)
// receive from a channel
sortedS1 := <-c
sortedS2 := <-c
sortedS3 := <-c
sortedS4 := <-c
// merge the 4 sorted slices
var sortedList1, sortedList2 []int
sortedList1 = append(sortedS1, sortedS2...)
sortedList2 = append(sortedS3, sortedS4...)
finalList := append(sortedList1, sortedList2...)
// we need to sort it again
sort.Ints(finalList)
fmt.Println("Here your digits sorted: ", finalList)
}
Now if you gonna test the code on Goplayground it will work every time, but if you run it on your computer the results may defer sometimes. (tried GoLand on Windows and vim-go on Ubuntu)
The purpose of this exercise is to just to input numbers on a slice, cut this slice on four part and sorts them using go routines and then merge them into one sorted slice.
The purpose of the exercise is totally silly, since I have to sort the big slice in the end anyway, so please put the purpose aside and so my algorithm since we could much simplify.
But the different results bugs me so much and I can't understand the problem since the last 24 hours.
This exercise just ruin all what I thought understand on channels and go routines.
Actually, your problem has nothing to do with channels, it's related to array/slice usage. You are using slices everywhere, so initially tere's only one copy of data - sort.Ints() performs operation in place and channel also operates on slices. This means that until you append, there's only one array in memory.
And now append does its job. Here's the key information from documentation:
If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
According to that if capacity of slice is sufficient, append adds items to existing array (there's no reallocation) and overwrites previous data. So, the faulty part of your code is the consolidation part.
All right I get the point but when I print the slice after the append loop the results of the append loop is correct every time and the final sorted result is incorrect.
Beside I took both code (the incorrect one and the correct one) I arranged them so they look similar and I still have different result, the only difference is on the incorrect one the whole slice is converted to int and spliced to 4, in the correct one they are spliced to 4 first and then converted
I can imagine the append in the second solution are made on small slices but still. I'm still confused, I'm still thinking there is some race condition problem.
Please take a look again on those two :
1- https://play.golang.org/p/-vOseYE1XLt
package main
import (
"fmt"
"sort"
"strconv"
)
func send(slice []int, c chan []int) {
sort.Ints(slice)
c <- slice
}
// Split strings.
func split(list []string) (tmpS []string, splS []string) {
tmpS = list[len(list)/2:]
splS = list[0 : len(list)/2]
return tmpS, splS
}
func convert(sStr []string) (sInt []int) {
for _, r := range sStr {
digit, _ := strconv.Atoi(r)
sInt = append(sInt, digit)
}
fmt.Println("Sorted sub array: ", sInt)
return sInt
}
func main() {
list := []string{"-9", "-11", "12", "13", "9"}
fmt.Println("Your unsorted digits: ", list)
splS1, splS2 := split(list)
splS11, splS12 := split(splS1)
splS21, splS22 := split(splS2)
// Convert each slices.
sInt1 := convert(splS11)
sInt2 := convert(splS12)
sInt3 := convert(splS21)
sInt4 := convert(splS22)
// Send in different go routines.
c := make(chan []int)
go send(sInt1, c)
go send(sInt2, c)
go send(sInt3, c)
go send(sInt4, c)
// Receive from a channel.
sortedS1 := <-c
sortedS2 := <-c
sortedS3 := <-c
sortedS4 := <-c
// Merge the 4 sorted slices.
sortedList1 := append(sortedS1, sortedS2...)
sortedList2 := append(sortedS3, sortedS4...)
finalList := append(sortedList1, sortedList2...)
// Sort it again
sort.Ints(finalList)
fmt.Println("Here your digits sorted: ", finalList)
}
2 -https://play.golang.org/p/L8WcrlTOVxZ
package main
import (
"fmt"
"sort"
"strconv"
)
func send(list []int, c chan []int) {
sort.Ints(list)
c <- list
}
// Split integers.
func split(list []int) (tmpS []int, splS []int) {
tmpS = list[len(list)/2:]
splS = list[0 : len(list)/2]
return tmpS, splS
}
func convert(sStr []string) (sInt []int) {
for _, r := range sStr {
digit, _ := strconv.Atoi(r)
sInt = append(sInt, digit)
}
fmt.Println("Sorted sub array: ", sInt)
return sInt
}
func main() {
list := []string{"-9", "-11", "12", "13", "9"}
fmt.Println("Your unsorted digits: ", list)
// Convert the whole slice
sInt := convert(list)
splS1, splS2 := split(sInt)
splS11, splS12 := split(splS1)
splS21, splS22 := split(splS2)
// Send in different go routines.
c := make(chan []int)
go send(splS11, c)
go send(splS12, c)
go send(splS21, c)
go send(splS22, c)
// Receive from a channel.
sortedS1 := <-c
sortedS2 := <-c
sortedS3 := <-c
sortedS4 := <-c
// Merge the 4 sorted slices.
sortedList1 := append(sortedS1, sortedS2...)
sortedList2 := append(sortedS3, sortedS4...)
finalList := append(sortedList1, sortedList2...)
// Sort it again
sort.Ints(finalList)
fmt.Println("Here your digits sorted: ", finalList)
}

How to get intersection of two slice in golang?

Is there any efficient way to get intersection of two slices in Go?
I want to avoid nested for loop like solution
slice1 := []string{"foo", "bar","hello"}
slice2 := []string{"foo", "bar"}
intersection(slice1, slice2)
=> ["foo", "bar"]
order of string does not matter
How do I get the intersection between two arrays as a new array?
Simple Intersection: Compare each element in A to each in B (O(n^2))
Hash Intersection: Put them into a hash table (O(n))
Sorted Intersection: Sort A and do an optimized intersection (O(n*log(n)))
All of which are implemented here
https://github.com/juliangruber/go-intersect
simple, generic and mutiple slices ! (Go 1.18)
Time Complexity : may be linear
func interSection[T constraints.Ordered](pS ...[]T) []T {
hash := make(map[T]*int) // value, counter
result := make([]T, 0)
for _, slice := range pS {
duplicationHash := make(map[T]bool) // duplication checking for individual slice
for _, value := range slice {
if _, isDup := duplicationHash[value]; !isDup { // is not duplicated in slice
if counter := hash[value]; counter != nil { // is found in hash counter map
if *counter++; *counter >= len(pS) { // is found in every slice
result = append(result, value)
}
} else { // not found in hash counter map
i := 1
hash[value] = &i
}
duplicationHash[value] = true
}
}
}
return result
}
func main() {
slice1 := []string{"foo", "bar", "hello"}
slice2 := []string{"foo", "bar"}
fmt.Println(interSection(slice1, slice2))
// [foo bar]
ints1 := []int{1, 2, 3, 9, 8}
ints2 := []int{10, 4, 2, 4, 8, 9} // have duplicated values
ints3 := []int{2, 4, 8, 1}
fmt.Println(interSection(ints1, ints2, ints3))
// [2 8]
}
playground : https://go.dev/play/p/lE79D0kOznZ
It's a best method for intersection two slice. Time complexity is too low.
Time Complexity : O(m+n)
m = length of first slice.
n = length of second slice.
func intersection(s1, s2 []string) (inter []string) {
hash := make(map[string]bool)
for _, e := range s1 {
hash[e] = true
}
for _, e := range s2 {
// If elements present in the hashmap then append intersection list.
if hash[e] {
inter = append(inter, e)
}
}
//Remove dups from slice.
inter = removeDups(inter)
return
}
//Remove dups from slice.
func removeDups(elements []string)(nodups []string) {
encountered := make(map[string]bool)
for _, element := range elements {
if !encountered[element] {
nodups = append(nodups, element)
encountered[element] = true
}
}
return
}
if there exists no blank in your []string, maybe you need this simple code:
func filter(src []string) (res []string) {
for _, s := range src {
newStr := strings.Join(res, " ")
if !strings.Contains(newStr, s) {
res = append(res, s)
}
}
return
}
func intersections(section1, section2 []string) (intersection []string) {
str1 := strings.Join(filter(section1), " ")
for _, s := range filter(section2) {
if strings.Contains(str1, s) {
intersection = append(intersection, s)
}
}
return
}
Try it
https://go.dev/play/p/eGGcyIlZD6y
first := []string{"one", "two", "three", "four"}
second := []string{"two", "four"}
result := intersection(first, second) // or intersection(second, first)
func intersection(first, second []string) []string {
out := []string{}
bucket := map[string]bool{}
for _, i := range first {
for _, j := range second {
if i == j && !bucket[i] {
out = append(out, i)
bucket[i] = true
}
}
}
return out
}
https://github.com/viant/toolbox/blob/a46fd679bbc5d07294b1d1b646aeacd44e2c7d50/collections.go#L869-L920
Another O(m+n) Time Complexity solution that uses a hashmap.
It has two differences compared to the other solutions discussed here.
Passing the target slice as a parameter instead of new slice returned
Faster to use for commonly used types like string/int instead of reflection for all
Yes there are a few different ways to go about it.. Here's an example that can be optimized.
package main
import "fmt"
func intersection(a []string, b []string) (inter []string) {
// interacting on the smallest list first can potentailly be faster...but not by much, worse case is the same
low, high := a, b
if len(a) > len(b) {
low = b
high = a
}
done := false
for i, l := range low {
for j, h := range high {
// get future index values
f1 := i + 1
f2 := j + 1
if l == h {
inter = append(inter, h)
if f1 < len(low) && f2 < len(high) {
// if the future values aren't the same then that's the end of the intersection
if low[f1] != high[f2] {
done = true
}
}
// we don't want to interate on the entire list everytime, so remove the parts we already looped on will make it faster each pass
high = high[:j+copy(high[j:], high[j+1:])]
break
}
}
// nothing in the future so we are done
if done {
break
}
}
return
}
func main() {
slice1 := []string{"foo", "bar", "hello", "bar"}
slice2 := []string{"foo", "bar"}
fmt.Printf("%+v\n", intersection(slice1, slice2))
}
Now the intersection method defined above will only operate on slices of strings, like your example.. You can in theory create a definition that looks like this func intersection(a []interface, b []interface) (inter []interface), however you would be relying on reflection and type casting so that you can compare, which will add latency and make your code harder to read. It's probably easier to maintain and read to write a separate function for each type you care about.
func intersectionString(a []string, b []string) (inter []string),
func intersectionInt(a []int, b []int) (inter []int),
func intersectionFloat64(a []Float64, b []Float64) (inter []Float64), ..ect
You can then create your own package and reuse once you settle how you want to implement it.
package intersection
func String(a []string, b []string) (inter []string)
func Int(a []int, b []int) (inter []int)
func Float64(a []Float64, b []Float64) (inter []Float64)

How to collect values from N goroutines executed in a specific order?

Below is a struct of type Stuff. It has three ints. A Number, its Double and its Power. Let's pretend that calculating the double and power of a given list of ints is an expensive computation.
type Stuff struct {
Number int
Double int
Power int
}
func main() {
nums := []int{2, 3, 4} // given numbers
stuff := []Stuff{} // struct of stuff with transformed ints
double := make(chan int)
power := make(chan int)
for _, i := range nums {
go doubleNumber(i, double)
go powerNumber(i, power)
}
// How do I get the values back in the right order?
fmt.Println(stuff)
}
func doubleNumber(i int, c chan int) {
c <- i + i
}
func powerNumber(i int, c chan int) {
c <- i * i
}
The result of fmt.Println(stuff) should be the same as if stuff was initialized like:
stuff := []Stuff{
{Number: 2, Double: 4, Power: 4}
{Number: 3, Double: 6, Power: 9}
{Number: 4, Double: 8, Power: 16}
}
I know I can use <- double and <- power to collect values from the channels, but I don't know what double / powers belong to what numbers.
Goroutines run concurrently, independently, so without explicit synchronization you can't predict execution and completion order. So as it is, you can't pair returned numbers with the input numbers.
You can either return more data (e.g. the input number and the output, wrapped in a struct for example), or pass pointers to the worker functions (launched as new goroutines), e.g. *Stuff and have the goroutines fill the calculated data in the Stuff itself.
Returning more data
I will use a channel type chan Pair where Pair is:
type Pair struct{ Number, Result int }
So calculation will look like this:
func doubleNumber(i int, c chan Pair) { c <- Pair{i, i + i} }
func powerNumber(i int, c chan Pair) { c <- Pair{i, i * i} }
And I will use a map[int]*Stuff because collectable data comes from multiple channels (double and power), and I want to find the appropriate Stuff easily and fast (pointer is required so I can also modify it "in the map").
So the main function:
nums := []int{2, 3, 4} // given numbers
stuffs := map[int]*Stuff{}
double := make(chan Pair)
power := make(chan Pair)
for _, i := range nums {
go doubleNumber(i, double)
go powerNumber(i, power)
}
// How do I get the values back in the right order?
for i := 0; i < len(nums)*2; i++ {
getStuff := func(number int) *Stuff {
s := stuffs[number]
if s == nil {
s = &Stuff{Number: number}
stuffs[number] = s
}
return s
}
select {
case p := <-double:
getStuff(p.Number).Double = p.Result
case p := <-power:
getStuff(p.Number).Power = p.Result
}
}
for _, v := range nums {
fmt.Printf("%+v\n", stuffs[v])
}
Output (try it on the Go Playground):
&{Number:2 Double:4 Power:4}
&{Number:3 Double:6 Power:9}
&{Number:4 Double:8 Power:16}
Using pointers
Since now we're passing *Stuff values, we can "pre-fill" the input number in the Stuff itself.
But care must be taken, you can only read/write values with proper synchronization. Easiest is to wait for all "worker" goroutines to finish their jobs.
var wg = &sync.WaitGroup{}
func main() {
nums := []int{2, 3, 4} // given numbers
stuffs := make([]Stuff, len(nums))
for i, n := range nums {
stuffs[i].Number = n
wg.Add(2)
go doubleNumber(&stuffs[i])
go powerNumber(&stuffs[i])
}
wg.Wait()
fmt.Printf("%+v", stuffs)
}
func doubleNumber(s *Stuff) {
defer wg.Done()
s.Double = s.Number + s.Number
}
func powerNumber(s *Stuff) {
defer wg.Done()
s.Power = s.Number * s.Number
}
Output (try it on the Go Playground):
[{Number:2 Double:4 Power:4} {Number:3 Double:6 Power:9} {Number:4 Double:8 Power:16}]
Writing different slice elements concurrently
Also note that since you can write different array or slice elements concurrently (for details see Can I concurrently write different slice elements), you can write the results directly in a slice without channels. See Refactor code to use a single channel in an idiomatic way how this can be done.
Personally, I would use a chan Stuff to pass the results back on, then spin up goroutines computing a full Stuff and pass it back. If you need the various part of a single Stuff computed concurrently, you can spawn goroutines from each goroutine, using dedicated channels. Once you've collected all the results, you can then (optionally) sort the slice with the accumulated values.
Example of what I mean below (you could, in principle, use a sync.WaitGroup to coordinate things, but if the input count is known, you don't strictly speaking need it).
type Stuff struct {
number int64
double int64
square int64
}
// Compute a Stuff with individual computations in-line, send it out
func computeStuff(n int64, out chan<- Stuff) {
rv := Stuff{number: n}
rv.double = n * 2
rv.square = n * n
out <- rv
}
// Compute a Stuff with individual computations concurrent
func computeStuffConcurrent(n int64, out chan<- Stuff) {
rv := Stuff{number: n}
dc := make(chan int64)
sc := make(chan int64)
defer close(dc)
defer close(sc)
go double(n, dc)
go square(n, sc)
rv.double = <-dc
rv.square = <-sc
out <- rv
}
func double(n int64, result chan<- int) {
result <- n * 2
}
func square(n int64, result chan<- int) {
result <- n * n
}
func main() {
inputs := []int64{1, 2, 3}
results := []Stuff{}
resultChannel := make(chan Stuff)
for _, input := range inputs {
go computeStuff(input, resultChannel)
// Or the concurrent version, if the extra performance is needed
}
for c := 0; c < len(inputs); c++ {
results = append(results, <- resultChannel)
}
// We now have all results, sort them if you need them sorted
}

Short way to apply a function to all elements in a list in golang

Suppose I would like to apply a function to every element in a list, and then put the resulting values in another list so I can immediately use them. In python, I would do something like this:
list = [1,2,3]
str = ', '.join(multiply(x, 2) for x in list)
In Go, I do something like this:
list := []int{1,2,3}
list2 := []int
for _,x := range list {
list2 := append(list2, multiply(x, 2))
}
str := strings.Join(list2, ", ")
Is it possible to do this in a shorter way?
I would do exactly as you did, with a few tweaks to fix typos
import (
"fmt"
"strconv"
"strings"
)
func main() {
list := []int{1,2,3}
var list2 []string
for _, x := range list {
list2 = append(list2, strconv.Itoa(x * 2)) // note the = instead of :=
}
str := strings.Join(list2, ", ")
fmt.Println(str)
}
This is an old question, but was the top hit in my Google search, and I found information that I believe will be helpful to the OP and anyone else who arrives here, looking for the same thing.
There is a shorter way, although you have to write the map function yourself.
In go, func is a type, which allows you to write a function that accepts as input the subject slice and a function, and which iterates over that slice, applying that function.
See the Map function near the bottom of this Go by Example page : https://gobyexample.com/collection-functions
I've included it here for reference:
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
You then call it like so:
fmt.Println(Map(strs, strings.ToUpper))
So, yes: The shorter way you are looking for exists, although it is not built into the language itself.
I've created a small utility package with Mapand Filter methods now that generics have been introduced in 1.18 :)
https://pkg.go.dev/github.com/sa-/slicefunk
Example usage
package main
import (
"fmt"
sf "github.com/sa-/slicefunk"
)
func main() {
original := []int{1, 2, 3, 4, 5}
newArray := sf.Map(original, func(item int) int { return item + 1 })
newArray = sf.Map(newArray, func(item int) int { return item * 3 })
newArray = sf.Filter(newArray, func(item int) bool { return item%2 == 0 })
fmt.Println(newArray)
}
With go1.18+ you can write a much cleaner generic Map function:
func Map[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}
Usage, e.g:
input := []int{4, 5, 3}
outputInts := Map(input, func(item int) int { return item + 1 })
outputStrings := Map(input, func(item int) string { return fmt.Sprintf("Item:%d", item) })
Found a way to define a generic map array function
func Map(t interface{}, f func(interface{}) interface{} ) []interface{} {
switch reflect.TypeOf(t).Kind() {
case reflect.Slice:
s := reflect.ValueOf(t)
arr := make([]interface{}, s.Len())
for i := 0; i < s.Len(); i++ {
arr[i] = f(s.Index(i).Interface())
}
return arr
}
return nil
}
origin := []int{4,5,3}
newArray := Map(origin, func(item interface{}) interface{} { return item.(int) + 1})
You can use lo's Map in order to quickly apply a function to all elements. For example, in order to multiply by 2 and convert to string, you can use:
l := lo.Map[int, string]([]int{1, 2, 3, 4}, func(x int, _ int) string { return strconv.Itoa(x * 2) })
Then you can convert back to a comma delimited string like so:
strings.Join(l, ",")

Resources