I need to count elements in many different ways in long arrays.
Here is an example of the use case for a special case:
func main() {
test := []test{test{"A", "1", "$"}, test{"A", "2", "€"}, test{"B", "3", "$"}}
countA := 0
countDollar := 0
countADollar := 0
for _, e := range test {
if e.prop1 == "A" {
countA++
}
if e.prop3 == "$" {
countDollar++
}
if e.prop1 == "A" && e.prop3 == "$" {
countADollar++
}
}
fmt.Printf("countA: %v, count$: %v, countA$: %v\n", countA, countDollar, countADollar)
}
This will print
countA: 2, count$: 2, countA$: 1
https://play.golang.org/p/R0nhwFpyN7H
The question is now: is there a way in go to generalize this so that I can count different sums based on attributes in one iteration over the array, without implementing each case separately?
Edit 2: here is a slightly better Version based on the suggestion by user Volker:
package main
import "fmt"
type test struct {
prop1 string
prop2 string
prop3 string
}
func count2(data []test, f func(test) bool) {
count1 := 0;
for _, e := range data {
if f(e) {
count1++
}
}
fmt.Printf("count1: %v", count1)
}
func main() {
data := []test{test{"A", "1", "$"}, test{"A", "2", "€"}, test{"B", "3", "$"}}
myTestCrit := func(t test) bool {
return t.prop1 == "A"
}
count2(data, myTestCrit)
}
https://play.golang.org/p/kB60gJCBkyn
Edit 3: here is a further generalization which accepts multiple counters. Thanks to Volker and Eli for their input. Perhaps the sources will be useful for others, too.
func count3(data []test, f []func(test) bool) {
counts := make([]int, len(f));
for _, e := range data {
for i, fi := range f {
if fi(e) {
counts[i]++
}
}
}
fmt.Printf("counts: %v\n", counts)
}
func main() {
data := []test{test{"A", "1", "$"}, test{"A", "2", "€"}, test{"B", "3", "$"}}
myTestCrit := func(t test) bool {
return t.prop1 == "A"
}
myTestCritDollar := func(t test) bool {
return t.prop3 == "$"
}
countCrits := make([]func(t test) bool, 2)
countCrits[0] = myTestCrit
countCrits[1] = myTestCritDollar
count3(data, countCrits)
}
Edit: I am also open to suggestions on how to improve the question. Its a legitimate issue I have and a general approach would very much simplify my code.
There's no special sauce in Go to do this. You're expected to write a loop over the slice - that's OK.
Your original code is absolutely fine, especially if you need to perform several different counts in the same loop. The count2 function is fine too, it abstracts away a bit of the code (not much though), but it's only useful for a single counter - a single filter/test function.
You could keep generalizing it by, e.g. passing in a slice of Metrics structs where each one is:
type Metrics struct {
Counter int
Filter func(test) bool
}
It all depends on your exact needs.
If you're looking for the idiomatic Go way here: don't worry about premature abstraction too much. Write the clearest / most readable code to solve the immediate task at hand.
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
Is there autovivification for Go?
As #JimB correctly noticed, my definition is not that strict. About my goal: In Python we have a very elegant "emulation" for an autovivification:
class Path(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
Is there a similar solution for Go?
Go maps will return a zero value for the type if the key doesn't exist, or the map is nil
https://play.golang.org/p/sBEiXGfC1c
var sliceMap map[string][]string
// slice is a nil []string
slice := sliceMap["does not exist"]
var stringMap map[string]string
// s is an empty string
s := stringMap["does not exist"]
Since a map with numeric values return will return 0 for missing entries, Go lets you use the increment and decrement operators on non-existent keys:
counters := map[string]int{}
counters["one"]++
Also extending JimB's answer, with the combination of map, interface{} and type assertion, you can dynamically create any complex structures:
type Obj map[interface{}]interface{}
func main() {
var o Obj
o = Obj{
"Name": "Bob",
"Age": 23,
3: 3.14,
}
fmt.Printf("%+v\n", o)
o["Address"] = Obj{"Country": "USA", "State": "Ohio"}
fmt.Printf("%+v\n", o)
o["Address"].(Obj)["City"] = "Columbus"
fmt.Printf("%+v\n", o)
fmt.Printf("City = %v\n", o["Address"].(Obj)["City"])
}
Output (try it on the Go Playground):
map[Name:Bob Age:23 3:3.14]
map[Age:23 3:3.14 Address:map[Country:USA State:Ohio] Name:Bob]
map[3:3.14 Address:map[Country:USA State:Ohio City:Columbus] Name:Bob Age:23]
City = Columbus
I have this Go code:
package main
import "fmt"
type baseGroup int
const (
fooGroup baseGroup = iota + 1
barGroup
)
var groups = [...]string{
fooGroup: "foo",
barGroup: "bar",
}
var xGroups = map[baseGroup]string{
fooGroup: "foo",
barGroup: "bar",
}
func main() {
fmt.Println("groups")
for k, v := range groups {
fmt.Println(k, v)
}
fmt.Println("xGroups")
for k, v := range xGroups {
fmt.Println(k, v)
}
}
If i run the code i get:
groups
0
1 foo
2 bar
xGroups
1 foo
2 bar
I'm wondering why the different outputs?
You're expecting range to behave the same on both types but in the first case it's ranging over an array and you just have an empty index 0. The value being stored in k is the current index; 0, 1, 2. In your second example you're ranging over the map and getting the key stored in k which only contains 1 and 2.
You might be wondering how is this happening? It's because this;
var groups = [...]string{
fooGroup: "foo",
barGroup: "bar",
}
Little confusing (imo very bad) code is giving you this;
var groups = [...]string{
1: "foo",
2: "bar",
}
Which is causing groups to be allocated/initialized with an empty string in index 0. Of course, the map doesn't need a key 0 to let you put something there in key 1 so it doesn't suffer from the same problems. If this is anything more than an exercise to demonstrate the features of the language I would highly recommend you get rid of the intentionally obfuscated code and use more clear constructs for constants, types and initialization.
If your skeptical about that mechanism add the following lines to your main and you can clearly see
fmt.Printf("Lenght of groups is %d and index 0 is %s\n", len(groups), groups[0])
fmt.Printf("Lenght of xGroups is %d\n", len(xGroups))
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
}