DeDuplicate Array of Structs - go

I have an array of struct and I want to remove all the duplicates element but keep the last element in the array. Something similar to hashmap where I can update the last struct matched every time to the new array
I have a struct something like this
type samplestruct struct {
value1 string
value2 string
value3 string
value4 string
value5 string
}
In my array of struct if value1, value2 and value3 of any struct is same , remove all the duplicates and keep the last struct.
func unique(sample []samplestruct) []samplestruct {
var unique []samplestruct
for _, v := range sample {
skip := false
for _, u := range unique {
if v.value1 == u.value1 && v.value2 == u.value2 && v.value3 == u.value3 {
skip = true
break
}
}
if !skip {
unique = append(unique, v)
}
}
return unique
}
This code return me the first struct that matched the condition provided but I want the last struct that matches the condition
Given Input -
[
samplestruct{"ram","rahim","india","34","india"},
samplestruct{"ram","rahim","india","38","America"},
samplestruct{"ram","rahim","india","40","Jamica"},
samplestruct{"amit","rawat","bangladesh","35","hawai"},
samplestruct{"amit","rawat","bangladesh","36","india"}
]
ExpectedOutput -
[
samplestruct{"ram","rahim","india","40","Jamica"},
samplestruct{"amit","rawat","bangladesh","36","india"}
]

The code in the question is almost there. When a matching element is found in unique, overwrite the element with the current value:
func unique(sample []samplestruct) []samplestruct {
var unique []samplestruct
sampleLoop:
for _, v := range sample {
for i, u := range unique {
if v.value1 == u.value1 && v.value2 == u.value2 && v.value3 == u.value3 {
unique[i] = v
continue sampleLoop
}
}
unique = append(unique, v)
}
return unique
}
The map-based approaches shown in other answers may be more appropriate depending on the size of the data set and number of surviving elements. Here's a correct implementation of the map approach:
func unique(sample []samplestruct) []samplestruct {
var unique []samplestruct
type key struct{ value1, value2, value3 string }
m := make(map[key]int)
for _, v := range sample {
k := key{v.value1, v.value2, v.value3}
if i, ok := m[k]; ok {
// Overwrite previous value per requirement in
// question to keep last matching value.
unique[i] = v
} else {
// Unique key found. Record position and collect
// in result.
m[k] = len(unique)
unique = append(unique, v)
}
}
return unique
}

Probably you should use a map here, use the important values as the key, when you encounter a duplicate and check for the key, you replace the value in the map.
Currently you are adding the values to the unique array if you haven't encountered them before, and then if you encounter one in the array after, you skip it. This is why you are only adding the first encounter of each struct which is the opposite of what you want.
You could either produce the key to the map as a concatenation of your important values (1 to 3), or use a struct of the three values as a key, and build the new key struct for each items and then search for it in the map.
Using a map will also be more performant than an array, as you can lookup much quicker in a map than iterating the unique array each time.

Nice little exercise, here is one solution which I will explain below:
package main
import "fmt"
func main() {
all := []person{
{"ram", "rahim", "india", "34", "india"},
{"ram", "rahim", "india", "38", "America"},
{"ram", "rahim", "india", "40", "Jamica"},
{"amit", "rawat", "bangladesh", "35", "hawai"},
{"amit", "rawat", "bangladesh", "36", "india"},
}
var deduped []person
// add the last occurrence always
for i := len(all) - 1; i >= 0; i-- {
if !contains(deduped, all[i]) {
// "append" to the front of the array
deduped = append([]person{all[i]}, deduped...)
}
}
for _, x := range deduped {
fmt.Println(x)
}
}
type person [5]string
func eq(a, b person) bool {
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2]
}
func contains(list []person, x person) bool {
for i := range list {
if eq(x, list[i]) {
return true
}
}
return false
}
We step through the input array backwards in order to catch the last of multiple equal items. Then we want to append that item to the back of the deduped array. That is why we revert the append operation, creating a new temporary one-item person slice and append the previous to it.
Efficiency-wise, this solution has some drawbacks, appending to the one-item slice will use O(n²) space as it produces a new slice every time, pre-allocating an array of len(all), appending to it, and reversing it afterwards would solve that problem.
The second performance issue that might arise if you do this for a zillion persons is the contains function which is O(n²) lookups for the program. This could be solved with a map[person]bool.

Use a map. First scan the list and set up a map with the first 3 values as the key for the map. The map value for each key will be the last found
Then walk the map it will be set to the correct values
package main
import (
"fmt"
"strings"
)
type samplestruct struct {
value1 string
value2 string
value3 string
value4 string
value5 string
}
func mkey(x samplestruct) string {
return strings.Join([]string{x.value1, x.value2, x.value3}, "-")
}
func main() {
cm := make(map[string]samplestruct)
exampledata := []samplestruct{samplestruct{"ram", "rahim", "india", "34", "india"},
samplestruct{"ram", "rahim", "india", "38", "America"},
samplestruct{"ram", "rahim", "india", "40", "Jamica"},
samplestruct{"amit", "rawat", "bangladesh", "35", "hawai"},
samplestruct{"amit", "rawat", "bangladesh", "36", "india"}}
for _, x := range exampledata {
k := mkey(x)
cm[k] = x
}
for x := range cm {
fmt.Println(cm[x])
}
}
https://play.golang.org/p/ITD0VjhFQEk

Related

How to output iterate over first N elements of slice?

I need to take applicant's first name, second name and GPA, then output only the first N applicants. For example, I have 5 applicants, but only N=3 can pass through.
To do this task, I decided to use a slice of struct.
The struct looks like this:
type Applicant struct {
firstName string
secondName string
GPA float64
}
I created a slice and initialized it:
applicants := []Applicant{}
...
fmt.Scan(&firstName, &lastName, &GPA)
applicants = append(applicants, Applicant{firstName, lastName, GPA})
Now my task is to output only names of first 3 applicants with highest GPA. I already sorted the slice from the best GPA to the worst.
I tried to do output applicants slice like this, but got error:
for _, applicant := range applicants {
fmt.Println(applicant.secondName + " " + applicant.secondName)
}
Can you help me with slice name output?
To get the first 3 with highest GPA you first sort the slice (what you alread did) and then just create a subslice:
func GetTopThree(applicants []Applicant) []Applicant {
sort.Slice(applicants, func(i, j int) bool {
return applicants[i].GPA > applicants[j].GPA
})
return applicants[:3]
}
To just get the names you can create a new slice
func GetTopThreeNames(applicants []Applicant) []string {
var topThree []string
for i := 0; i < int(math.Min(3, float64(len(applicants)))); i++ {
topThree = append(topThree, applicants[i].firstName)
}
return topThree
}
If you want to map the first names and last names separately, this could be an approach:
func TopThreeNames(applicants []Applicant) [][2]string {
top := applicants[:int(math.Min(3, float64(len(applicants))))]
var names [][2]string
for _, a := range top {
names = append(names, [2]string{a.firstName, a.secondName})
}
return names
}
The function maps each Applicant element to an array of length two, whereby the first element is equal to its first name and the second element to its second name.
For instance (unsafe since the length of the slice could be empty):
names := TopThreeNames(applicants)
first := names[0]
fmt.Printf("First name: %s and last name: %s\n", first[0], first[1])
If your task really is just to print out the names then this is one possible way
for i := 0; i < 3 && i < len(applicants); i++ {
fmt.Printf("%s %s\n", applicants[i].firstName, applicants[i].secondName)
}
Note that the list must be sorted first like is is shown in other posts.

How to write a bidirectional mapping in Go?

I am writing a simple console game and would like to map a player to a symbol. With two players my approach looks like this:
func playerToString(p player) string {
if p == 0 {
return "X"
}
return "O"
}
func stringToPlayer(s string) player {
if s == "X" {
return 0
}
return 1
}
Of cause you could also write this as two maps mapping int to string and string to int. Both the above approach and the map approach seem error-prone. Is there a more idiomatic way to write this in go? Maybe some non-iota enum way?
[I assume your example is just minimal and that your actual mapping has more than two options. I also assume you meant bi-directonal mapping]
I would write one map:
var player2string = map[int]string{
0: "0",
1: "X",
// etc...
}
And then would create a function to populate a different map string2player programmatically. Something like this:
var player2string = map[int]string{
0: "0",
1: "X",
// etc...
}
var string2player map[string]int = convertMap(player2string)
func convertMap(m map[int]string) map[string]int {
inv := make(map[string]int)
for k, v := range m {
inv[v] = k
}
return inv
}
func main() {
fmt.Println(player2string)
fmt.Println(string2player)
}
Try it on the Go playground
In addition to Eli's answer, two other changes you could make. You could make the to-symbol function a method of the player type. And because the player values are integers (sequential starting from zero), you can use a slice instead of a map to store the int-to-symbol mapping -- it's a bit more efficient to store and for lookup.
type player int
var playerSymbols = []string{"X", "O", "A", "B", "C", "D", "E", "F", "G", "H"}
func (p player) Symbol() string {
if int(p) < 0 || int(p) >= len(playerSymbols) {
return "?" // or panic?
}
return playerSymbols[p]
}
This method signature could even be String() string so it's a fmt.Stringer, which can be useful for printing stuff out and debugging.
Assuming you don't need any specific mapping and the player integer values have the sequence 0,1,2,3,...,25, you can generate the players symbols directly without using the maps as shown in following snippet :-
type player int
func ToSymbol(p player) string {
return fmt.Sprintf("%c", 'A' + p)
}
func ToPlayer(symbol string) player {
return player([]rune(symbol)[0] - 'A')
}

How to check if slice of custom struct is sorted?

We can check if a slice of strings is sorted with
var slice = []string { "a", "b }
sort.StringsAreSorted(slice)
But what about when you have struct and you want to know if a slice of that struct is sorted by a certain member?
type Person struct {
Name string
LastName string
}
var p = []Person{ {"John", "Smith" }, { "Ben", "Smith" } }
sort.StringsAreSorted(p???)
If your type implements sort.Interface, simply use the sort.IsSorted() function.
If not, you may use sort.SliceIsSorted(), passing a less() function which decides / specifies the order(ing):
sortedByName := sort.SliceIsSorted(p, func(i, j int) bool {
return p[i].Name < p[j].Name
})
fmt.Println("Sorted by name:", sortedByName)
sortedByLastName := sort.SliceIsSorted(p, func(i, j int) bool {
return p[i].LastName < p[j].LastName
})
fmt.Println("Sorted by last name:", sortedByLastName)
This will output (try it on the Go Playground):
Sorted by name: false
Sorted by last name: true
If you look into the implementation of these functions, they use a simple loop to iterate over the elements and tell if the ones being next to each other does not violate the ordering (less() function). You may just as easily use a for loop too.

How do we create an empty map and append new data in golang?

I'm having a problem with creating an empty map and append new data to it while looping on another map.
this is the error i'm getting on my IDE.
here's my data struct to be added to the map.
type Outcome struct {
QuestionIndex string
ChoiceIndex int64
Correct bool
}
func createEntryOutcome(e *entry.Entry) map[string]interface{} {
entryPicks := e.Live.Picks
outcomes := make(map[string]interface{})
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
return outcomes
}
i basically want something like below to be saved in the database.
[
{
qIndex: "1",
cIndex: 1,
correct: false,
},
{
qIndex: "1",
cIndex: 1,
correct: false,
},
]
im new to golang and any help is appreciated. thanks
As the error clearly says:
first argument to append must be slice; have map[string]interface{}
which means you need to create a slice before appending the data to outcomes which is actually slice of outcomes, like you have mentioned in the output you want.
The append function appends the elements x to the end of the slice s,
and grows the slice if a greater capacity is needed.
Create a slice of outcomes and then append the data from entryPicks to that slice:
outcomes := make([]map[string]interface{})
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
which will let you provide the outcome you want.
type Outcome struct {
QuestionIndex string
ChoiceIndex int64
Correct bool
}
func createEntryOutcome(e *entry.Entry) map[string]interface{} {
entryPicks := e.Live.Picks
var outcomes []Outcome
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
return outcomes
}
change outcomes := make(map[string]interface{}) to var outcomes []Outcome

Iota to define keys on a Go map?

Let's suppose we have a map[int]string and we want to define it like this:
var a map[int]string = {
1: "some"
3: "value"
4: "maintained"
7: "manually"
// more 100 entries...
}
I would like to maintain the values manually because they have no pattern, but the keys have. Is there a way to maintain the key list just like we do with enum values using 1 << 1 + iota?
I'm not asking if it's possible to use iota as a map key (unfortunately it's not AFAIK), just if there is an equally elegant way to create the keys on a defined sequence.
Your best bet is to store the ordered values as a slice, and then use an init function to generate the map like this:
var a map[int]string
var vals = []string{
"some",
"value",
"maintained",
"manually",
}
func init() {
a = make(map[int]string)
for idx, val := range vals {
a[idxToKey(idx)] = val
}
}
func idxToKey(i int) int {
return 1<<1 + i
}
Run it on the Go Playground.
You can change idxToKey to be whatever transformation you want. I've used the one you gave in this case, but it could be anything. The argument goes where you'd normally put the iota keyword.
One way would be have an array/slice of all the words and loop through similar to this;
var words []string
var a map[int]string
for i, v := range words {
a[1 << 1 + i] = v
}

Resources