问题
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