1

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

  • 3
    Have you tried at all? Can you should what you've tried? – Adrian Jul 12 '19 at 13:28
  • 1
    Possible duplicate of [Concatenate two slices in Go](https://stackoverflow.com/questions/16248241/concatenate-two-slices-in-go) – Kirill Jul 12 '19 at 13:32
  • @Adrian update the question to show the work I've tried – user10457989 Jul 12 '19 at 13:41
  • @g4s8 I think my use case is a bit different – user10457989 Jul 12 '19 at 13:42
  • @user10457989: Do you care about order? –  Jul 12 '19 at 13:46
  • @TimCooper Nope – user10457989 Jul 12 '19 at 14:00
  • 2
    Use a go `map` to track unique entries. Populate a "master" map with the first slice (note I used the term *slice* not array - as arrays have fixed immutable sizes - you are using slices). Then iterate the remaining slices, creating an individual map based on that slice. With this iterate the master-map removing any keys from the master that are not in this individual map. Rince & Repeat. Finally output a slice of the remaining master-map keys - that will be your set of words common to all slices. – colm.anseo Jul 12 '19 at 14:11
  • @colminator would you mind showing an example? – user10457989 Jul 12 '19 at 14:15

3 Answers3

3

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

colm.anseo
  • 19,337
  • 4
  • 43
  • 52
0

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.

hallazzang
  • 651
  • 8
  • 18
0

@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
}
D.C. Joo
  • 1,087
  • 7
  • 8