user10457989
user10457989

Reputation: 73

Golang create array from values present in all arrays

I need to create an array from multiple arrays. The new array must only contain the values that is present in all arrays passed in. For example.

array1 := []string{"hello", "germany", "brasil", "fiji"}
array2 := []string{"goodbye", "germany", "brasil", "fiji"}
array3 := []string{"hello", "brasil", "fiji"}
array4 := []string{"hello", "brasil", "fiji", "usa"}

func mergeArrays(arrs ...[]string) []string{
   // process arrays
}

myNewArray := mergeArrays(array1,array2,array3,array4)
fmt.Println(myNewArray) // ["fiji", "brasil"]

The example should return ["fiji", "brasil"] since they are the only values present in all arrays.

How could I go about writing a function that could achieve such a goal in golang?

This is my attempt but feels a bit clumsy

func mergeArrays(arrs ...[]string) []string {
    var finalArr []string
    if len(arrs) == 0 {
        return finalArr
    }

    for i, a := range arrs {
        if i == 0 {
            finalArr = arrs[0]
            continue
        }
        for i, e := range finalArr {
            if !strContains(a, e) {
                finalArr = append(finalArr[:i], finalArr[i+1:]...)
            }
        }

    }

    return finalArr
}

func strContains(s []string, e string) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

Playground link: https://play.golang.org/p/KRygw7OVBbn

Upvotes: 1

Views: 3524

Answers (3)

D.C. Joo
D.C. Joo

Reputation: 1317

@colminator's is a valid answer but the algorithm is not optimal. @hallazang's suggestion is a right approach, but why creating 2 maps if we need only one? See the next option for better efficiency.

func repeatingItemsFromArrays(arrays ...[]string) []string {
    la := len(arrays)
    out := []string{}

    // handle corner cases for efficiency
    if la == 0 {
        return out
    }
    if la == 1 {
        return arrays[0]
    }

    m := make(map[string]int)

    for i := range arrays {
        for j := range arrays[i] {
            m[arrays[i][j]]++
        }
    }

    for k, v := range m {
        if v == la {
            out = append(out, k)
        }
    }

    return out
}

Upvotes: 0

hallazzang
hallazzang

Reputation: 701

Idea:

  1. Count number of appearances of each item across arrays(arr).
  2. If that number is exactly same as len(arr), the item presents in all arrays.

Here's an example that employees this approach:

package main

import "fmt"

func uniq(arr []string) []string {
    cache := make(map[string]struct{})
    for _, s := range arr {
        cache[s] = struct{}{}
    }
    var r []string
    for s := range cache {
        r = append(r, s)
    }
    return r
}

func mergeArrays(arrs ...[]string) []string {
    count := make(map[string]int)
    for _, arr := range arrs {
        for _, s := range uniq(arr) {
            count[s]++
        }
    }
    var merged []string
    for s, n := range count {
        if n == len(arrs) {
            merged = append(merged, s)
        }
    }
    return merged
}

func main() {
    array1 := []string{"hello", "germany", "brasil", "fiji"}
    array2 := []string{"goodbye", "germany", "brasil", "fiji"}
    array3 := []string{"hello", "brasil", "fiji"}
    array4 := []string{"hello", "brasil", "fiji", "usa"}

    myNewArray := mergeArrays(array1, array2, array3, array4)
    fmt.Println(myNewArray) // ["fiji", "brasil"]
}

And playground link: https://play.golang.org/p/FB3wJ7-gaIa

EDIT: it will work properly even if there's any duplicate in each array.

Upvotes: 0

colm.anseo
colm.anseo

Reputation: 22037

Per my comment above, here's one way to do it with go maps and thus avoid iterating over potentially large slices:

func itemize(a []string) map[string]struct{} {
    m := make(map[string]struct{})
    for _, v:=range a {
        m[v] = struct{}{} // struct{}{} == an empty struct (i.e. a value that incurs no storage)
    }
    return m
}

func commonElements(arrs ...[]string) (results []string) {
    if len(arrs) == 0 {
        return // edge case
    }

    mm := itemize(arrs[0]) // master map

    for i:=1; i<len(arrs);i++ {
        m := itemize(arrs[i]) // current map
        for k := range mm {
            if _, ok := m[k]; !ok {
                delete(mm, k) // master item not in current slice, so remove from master
            }
        }
    }

    results = make([]string, len(mm)) // make a precisely sized slice...
    i:=0
    for k := range mm {
        results[i] = k // so we can insert results directly into it without using append
        i++ 
    }

    return
}

https://play.golang.org/p/pTaXR-nY9zm

Upvotes: 3

Related Questions