12

I come from the .NET world where I had LINQ so I could do in-memory queries like the one we usually see in SQL.

I have a slice of this structure I want to group by 8 fields, and then sum another integer field. Something like:

type Register struct {
    id1 int
    id2 int
    id3 int
    id4 int
    id5 int
    id6 int
    id7 int
    id8 int
    money int
}

I thought in:

  • Creating an Equal function, to compare structures (those eight
    fields). Iterate over the collection I'm analyzing. For each item
    check if it is already in the hash table. If it is there => I sum the field. If it is not => I add the new item to hash table.

Is there a better way or any beautiful, efficient and easy ready to use library?

icza
  • 389,944
  • 63
  • 907
  • 827
Marcos
  • 355
  • 2
  • 4
  • 10
  • I don't really follow your question. What is the purpose of an `Equal` function? I'm not sure if you're expecting a map of maps where all items with an id1 value `X` are in a group, all items with another id1 value `Y` are in a different group ect. Can you give an example of the input and output? Don't need a lot of data but enough to illustrate the point which your struct does not. – evanmcdonnal Oct 11 '16 at 21:47
  • Yes, for example: – Marcos Oct 11 '16 at 21:52
  • Yes, for example: `[r1{id1: 345, money: 1500}, r2{id1: 345, id2 140, money: 2700}, r3{id1: 345, money: 1000}`. So r1 and r3 should be summed and get `rSummed1{id: 345, money: 2500}, rSummed2{id1: 345, id2: 140, money: 2700}`. It is the classical `SELECT Sum(money) GROUP BY id1, id2 ...` – Marcos Oct 11 '16 at 22:00
  • Wouldn't `r2` also be in that group? It's `id1` value is also 345. – evanmcdonnal Oct 11 '16 at 22:12
  • No, because in r2 id2 != 0. All id fields need to be exactly the same. – Marcos Oct 11 '16 at 22:32

2 Answers2

9

Basically your idXX fields are the keys, an n-tuple. And the money field is the data to be summed.

This can easily be done if you slightly refactor your types. Put only the keys into a struct, so it can be used as a key in a map. Struct values are comparable:

Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.

So the new types:

type Key struct {
    id1 int
    id2 int
    id3 int
    id4 int
    id5 int
    id6 int
    id7 int
    id8 int
}

type Register struct {
    key   Key
    money int
}

And to group and calculate sum, you can use a map[Key]int, using Register.key as the map key to "group" all registers with the same keys (same ids):

regs := []*Register{
    {Key{id1: 345}, 1500},
    {Key{id1: 345, id2: 140}, 2700},
    {Key{id1: 345, id2: 140}, 1300},
    {Key{id1: 345}, 1000},
    {Key{id3: 999}, 1000},
    {Key{id3: 999}, 2000},
}

// calculate sum:
m := map[Key]int{}
for _, v := range regs {
    m[v.key] += v.money
}

fmt.Println(m)

Output:

map[{345 0 0 0 0 0 0 0}:2500 {345 140 0 0 0 0 0 0}:4000 {0 0 999 0 0 0 0 0}:3000]

For a nice output:

fmt.Println("Nice output:")
for k, v := range m {
    fmt.Printf("%+3v: %d\n", k, v)
}

Output:

Nice output:
{id1:345 id2:  0 id3:  0 id4:  0 id5:  0 id6:  0 id7:  0 id8:  0}: 2500
{id1:345 id2:140 id3:  0 id4:  0 id5:  0 id6:  0 id7:  0 id8:  0}: 4000
{id1:  0 id2:  0 id3:999 id4:  0 id5:  0 id6:  0 id7:  0 id8:  0}: 3000

This is as easy and as efficient as it can get. Try the examples on the Go Playground.

Notes:

In the map we didn't have to check if a Key is already in it. This is so because indexing a map yields the zero value of the value type of the map if the key is not in the map. So in this case if a Key is not yet in the map, m[key] will give you 0 (0 is the zero value for the int type), properly telling that the "previous" sum for that key is 0 so far.

Also note that Key may be an embedded field in Register instead of a "regular" field, it doesn't matter, and that way you could refer to the idXX fields as if they were part of the Register.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Awesome, this was the kind of thing I was looking for. I knew there was a much better way! Thanks!! – Marcos Oct 12 '16 at 11:22
1

You can try go-linq: https://github.com/ahmetalpbalkan/go-linq/

It is similar to c# linq, use the GroupBy and Aggregate in your case

https://godoc.org/github.com/ahmetalpbalkan/go-linq#example-Query-GroupBy https://godoc.org/github.com/ahmetalpbalkan/go-linq#example-Query-Aggregate

pseudo code:

From(regs).GroupBy(merge ids to a string as group key).Select(use Aggregate or SumInts to sum money)
chendesheng
  • 1,969
  • 18
  • 14