Golang: group and sum slice of structs

筅森魡賤 提交于 2019-12-19 17:58:02

问题


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?


回答1:


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.




回答2:


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)


来源:https://stackoverflow.com/questions/39987023/golang-group-and-sum-slice-of-structs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!