Sorting on a shared nested struct property in Go - sorting

I have set of json data being pulled from a rethink database, the data is then serialised into structs using rethinkgo. I need to be able to work with a subset of this data and sort it based on the value of one of its properties.
To save complicating the problem with the workings of what I mentioned above, I've created a simplified (fruit based) example of the structs being used and what I'm trying to achieve.
package main
import (
"fmt"
"sort"
)
type Fruit struct {
AvgNumSeeds int
Name string
}
type Apple struct {
Fruit
Diameter int
}
type Banana struct {
Fruit
Length int
}
type ByNumSeeds []Apple //[]Fruit
func (p ByNumSeeds) Len() int {
return len(p)
}
func (p ByNumSeeds) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (p ByNumSeeds) Less(i, j int) bool {
return p[i].AvgNumSeeds < p[j].AvgNumSeeds
}
func main() {
apples := []Apple{
Apple{Fruit: Fruit{AvgNumSeeds: 4, Name: "Cox"}, Diameter: 10},
Apple{Fruit: Fruit{AvgNumSeeds: 6, Name: "Granny Smith"}, Diameter: 20},
Apple{Fruit: Fruit{AvgNumSeeds: 5, Name: "Pink Lady"}, Diameter: 21},
Apple{Fruit: Fruit{AvgNumSeeds: 2, Name: "Russett"}, Diameter: 15},
Apple{Fruit: Fruit{AvgNumSeeds: 1, Name: "Crab"}, Diameter: 7},
Apple{Fruit: Fruit{AvgNumSeeds: 7, Name: "Brambley"}, Diameter: 40},
Apple{Fruit: Fruit{AvgNumSeeds: 3, Name: "Braeburn"}, Diameter: 25},
}
bananas := []Banana{
Banana{Fruit: Fruit{AvgNumSeeds: 40, Name: "Lacatan"}, Length: 20},
Banana{Fruit: Fruit{AvgNumSeeds: 60, Name: "Lady Finger"}, Length: 22},
Banana{Fruit: Fruit{AvgNumSeeds: 50, Name: "Senorita"}, Length: 25},
Banana{Fruit: Fruit{AvgNumSeeds: 20, Name: "Cavendish"}, Length: 30},
Banana{Fruit: Fruit{AvgNumSeeds: 10, Name: "Goldfinger"}, Length: 27},
Banana{Fruit: Fruit{AvgNumSeeds: 70, Name: "Gros Michel"}, Length: 15},
Banana{Fruit: Fruit{AvgNumSeeds: 30, Name: "Red Dacca"}, Length: 19},
}
fmt.Println("Apples")
fmt.Printf("%+v\n\n", apples)
sort.Sort(ByNumSeeds(apples))
fmt.Printf("%+v\n\n\n", apples)
fmt.Println("Bananas")
fmt.Printf("%+v\n\n", bananas)
//sort.Sort(ByNumSeeds(bananas))
fmt.Printf("%+v\n\n", bananas)
}
http://play.golang.org/p/EjWOf58N3x
As you can see I've two structs, Apples and Bananas, both of which share properties from the struct Fruit; a sort (inc. interface functions Len, Swap, Less) and the main function which sets up the data structures for apples and bananas and then tries to sort them.
What I am wanting for both Apples and Bananas is one sort (type ByNumSeeds, Len, Swap, Less) that is capable of sorting both Apples and Bananas separately, on a property that they both share from the Fruit struct, AvgNumSeeds.
The sort I have created in this code takes a slice of Apples as its interface and does indeed sort my array of apples by the AvgNumSeeds. However I am unable to find a way to get it to work with both Apple and Banana structs.
My initial thought was to treat the interface as a slice of Fruit but, understandably, I then get the error:
60: cannot convert apples (type []Apple) to type ByNumSeeds
My next thought was to fix this error by somehow casting a slice of Apples/Bananas to a slice of Fruit but this doesn't quite feel like the right thing to do.
In my investigation for a solution I've come across a package called sortutil which has a function called AscByField that takes the struct and the name of the field to sort by. I've not tried it yet but the package makes it quite clear that its not efficient as it works by using reflection and to try to use the standard interface method first.
Is there a way I can achieve the sorting of a nested struct without having to duplicate a sort for each 'child' struct type?

The solution to polymorphism is go is interfaces. Embedding on its own doesn't really work here as you see, because you still have distinct types. Here's a re-work of your example to get you started http://play.golang.org/p/7HV_HJ3Gw0, or maybe this is a little easier to read through (it's common to hide an un-exported struct behind the exported interface) http://play.golang.org/p/z3CHj002Jq
package main
import (
"fmt"
"sort"
)
type fruit struct {
avgNumSeeds int
name string
}
type Fruit interface {
Name() string
AvgNumSeeds() int
}
func (f fruit) Name() string {
return f.name
}
func (f fruit) AvgNumSeeds() int {
return f.avgNumSeeds
}
type Apple struct {
fruit
Diameter int
}
type Banana struct {
fruit
Length int
}
type ByNumSeeds []Fruit
func (p ByNumSeeds) Len() int {
return len(p)
}
func (p ByNumSeeds) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (p ByNumSeeds) Less(i, j int) bool {
return p[i].AvgNumSeeds() < p[j].AvgNumSeeds()
}
func main() {
apples := []Fruit{
Apple{fruit: fruit{avgNumSeeds: 4, name: "Cox"}, Diameter: 10},
Apple{fruit: fruit{avgNumSeeds: 6, name: "Granny Smith"}, Diameter: 20},
Apple{fruit: fruit{avgNumSeeds: 5, name: "Pink Lady"}, Diameter: 21},
Apple{fruit: fruit{avgNumSeeds: 2, name: "Russett"}, Diameter: 15},
Apple{fruit: fruit{avgNumSeeds: 1, name: "Crab"}, Diameter: 7},
Apple{fruit: fruit{avgNumSeeds: 7, name: "Brambley"}, Diameter: 40},
Apple{fruit: fruit{avgNumSeeds: 3, name: "Braeburn"}, Diameter: 25},
}
bananas := []Fruit{
Banana{fruit: fruit{avgNumSeeds: 40, name: "Lacatan"}, Length: 20},
Banana{fruit: fruit{avgNumSeeds: 60, name: "Lady Finger"}, Length: 22},
Banana{fruit: fruit{avgNumSeeds: 50, name: "Senorita"}, Length: 25},
Banana{fruit: fruit{avgNumSeeds: 20, name: "Cavendish"}, Length: 30},
Banana{fruit: fruit{avgNumSeeds: 10, name: "Goldfinger"}, Length: 27},
Banana{fruit: fruit{avgNumSeeds: 70, name: "Gros Michel"}, Length: 15},
Banana{fruit: fruit{avgNumSeeds: 30, name: "Red Dacca"}, Length: 19},
}
fmt.Println("Apples")
fmt.Printf("%+v\n\n", apples)
sort.Sort(ByNumSeeds(apples))
fmt.Printf("%+v\n\n\n", apples)
fmt.Println("Bananas")
fmt.Printf("%+v\n\n", bananas)
sort.Sort(ByNumSeeds(bananas))
fmt.Printf("%+v\n\n", bananas)
}
I am wary from your example though, that your trying to force composition to work like inheritance (but that could just be from the simplified example). Embedding doesn't give you an "is a" relationship like inheritance, only a "has a". Having your types provide a common interface allows you to run all compliant types through the same sort function.
The only real gotcha in your example is going to be that []struct is not interchangeable with []interface. If you need to convert the two, you have to create a new slice and copy the values.

Related

Why do Go generics fail when function returns a function?

I've just started trying out generics on Go, and I run into a situation which I don't fully understand why it's failing.
I've refactored the following function, from this:
func PositivePercentageAbove(above int) func(list []uint8) bool {
return func(list []uint8) bool {
acum := 0
for _, x := range list {
acum += int(x)
}
return (float64(acum) / float64(len(list)) * 100) >= float64(above)
}
}
into this:
func PositivePercentageAbove[T constraints.Integer](above int) func(list []T) bool {
return func(list []T) bool {
acum := 0
for _, x := range list {
acum += int(x)
}
return (float64(acum) / float64(len(list)) * 100) >= float64(above)
}
}
The unit test for this function is failing with error: tests/utils/NumberUtils_test.go:82:50: cannot infer T . The source is:
func Test_TruePercentageAbove(t *testing.T) {
tables := []struct {
percentage int
list []uint8
output bool
}{
{percentage: 100, list: []uint8{1, 1, 1, 1}, output: true},
{percentage: 100, list: []uint8{1, 1, 0, 1}, output: false},
{percentage: 80, list: []uint8{1, 1, 1, 1, 0}, output: true},
{percentage: 90, list: []uint8{1, 1, 1, 1, 0}, output: false},
{percentage: 100, list: []uint8{1, 1, 1, 1, 0}, output: false},
{percentage: 40, list: []uint8{0, 1, 0, 1, 0, 1}, output: true},
{percentage: 60, list: []uint8{0, 1, 0, 1, 0, 1}, output: false},
{percentage: 70, list: []uint8{0, 1, 0, 1, 0, 1}, output: false},
}
for _, table := range tables {
result := utils.PositivePercentageAbove(table.percentage)(table.list)
if result != table.output {
t.Errorf("Slice %v with percentage above %v expected to return %v but returned %v", table.list, table.percentage, table.output, result)
}
}
}
I've changed similar functions from int to generics, I'm not sure why this one in particular is not working. I assume it might be somehow related with the function returning another function, but I can't figure exactly why. Thanks.
As often, the answer lies in the Type Parameters proposal:
The only type arguments that can be inferred are those that are used for the types of the function‘s (non-type) input parameters. If there are some type parameters that are used only for the function’s result parameter types, or only in the body of the function, then those type arguments cannot be inferred using function argument type inference.
In the case of
func PositivePercentageAbove[T constraints.Integer](above int) func(list []T) bool
because type parameter T does not appear in the parameter list, the corresponding type argument cannot be inferred.

Is it possible to sort a slice of structs by time in Golang?

I am trying to sort the structs Session in the slice Session by each Session's start time and hall_id. Here is the code:
Sessions := []Session{
Session{
name: "superman",
hall_id: 1,
startTime: time.Date(2022, time.August, 15, 17, 35, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 18, 35, 0, 0, time.UTC),
},
Session{
name: "thor",
hall_id: 2,
startTime: time.Date(2022, time.August, 15, 16, 30, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 17, 30, 0, 0, time.UTC),
},
Session{
name: "joker",
hall_id: 3,
startTime: time.Date(2022, time.August, 15, 19, 40, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 20, 30, 0, 0, time.UTC),
},
Session{
name: "batman",
hall_id: 1,
startTime: time.Date(2022, time.August, 15, 17, 40, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 18, 20, 0, 0, time.UTC),
},
}
The point is that I am using "time" package, and in order to create a date, you need to use Date() function, which requires multiple stuff like: year int, month, day int, hour int, minute int, etc.
I have tried the AndreKR's answer which is:
slice.Sort(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
but it seems that it does not work with multiple "parameters" of a struct. I tried this:
sort.Slice(Sessions[:], func(i, j int) bool {
return Sessions[i].startTime.Year() < Sessions[j].startTime.Year(),
int(Sessions[i].startTime.Month()) < int(Sessions[j].startTime.Month()),
Sessions[i].startTime.Day() < Sessions[j].startTime.Day(),
Sessions[i].startTime.Hour() < Sessions[j].startTime.Hour(),
Sessions[i].startTime.Minute() < Sessions[j].startTime.Minute()
})
I am new to the Golang, so if I have made an obvious mistake I am sorry ;(
Alright, I have got the answer:
sort.Slice(Sessions[:], func(i, j int) bool {
return Sessions[i].startTime.Before(Sessions[j].startTime)
})
this code will solve this problem

Finding numbers above the mean

I could use some help finding all the numbers from a struct array that are above a calculated mean!
//MeanMedianMode struct
type MeanMedianMode struct {
numbers []float64
}
func main() {
// range of numbers
dataType := MeanMedianMode{
numbers: []float64{
84, 25, 88, 56, 10, 19, 11, 80,
45, 83, 22, 40, 22, 52, 61, 13, 73, 23, //Data to be used
90, 89, 6,
},
}
I've figured out how to pass my data easily and find the average as follows...
//CalcMean float64
func (mm *MeanMedianMode) CalcMean() float64 {
total := 0.0
for _, v := range mm.numbers {
total += v
}
return (total / float64(len(mm.numbers)))
//return math.Round(total / float64(len(mm.numbers))) //Should it need to be rounded
}
My biggest issue is replicating that process and using the values stored in the array within another function and iterating over them to find the values greater than (>) the found mean!
I appreciate the insights!
I don't know how you'd like to do it, but something like this I guess:
package main
import (
"fmt"
)
//MeanMedianMode struct
type MeanMedianMode struct {
numbers []float64
}
func main() {
m := &MeanMedianMode{
numbers: []float64{
84, 25, 88, 56, 10, 19, 11, 80,
45, 83, 22, 40, 22, 52, 61, 13, 73, 23,
90, 89, 6,
},
}
mean := m.CalcMean()
for _, n := range m.numbers {
if n > mean {
fmt.Printf("%.3f is greater than the mean\n", n)
}
}
}
//CalcMean float64
func (mm *MeanMedianMode) CalcMean() float64 {
total := 0.0
for _, v := range mm.numbers {
total += v
}
return (total / float64(len(mm.numbers)))
}

Difficulty with variadic functions [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
From An Introduction to Programming in Go, page 91, Exercise Question no 4, Topic: Functions:
Write a function with one variadic parameter that finds the greatest number in a list of numbers?
So far I had written this code but it is showing errors
package main
import (
"fmt"
)
func findMaximum(args ...[]int) []int {
max := args[0]
for _, v := range args {
if v > []args {
max = v
}
}
return args
}
func main() {
x := []int{
48, 96, 86, 68,
57, 82, 63, 70,
37, 34, 83, 27,
19, 97, 9, 17,
}
fmt.Println(findMaximum(x))
}
I had taken reference from this Program
(Page, 75, Question No. - 4, Topic: Arrays, Slices and Maps)
Write a program that finds the smallest number
in this list:
x := []int{
48,96,86,68,
57,82,63,70,
37,34,83,27,
19,97, 9,17,
}
This is the program I had written to solve this Question
package main
import "fmt"
func main() {
arr := []uint{
48, 96, 86, 68,
57, 82, 63, 70,
37, 34, 83, 27,
19, 97, 9, 17,
}
min := arr[0] // assume first value is smallest
for _, value := range arr {
if value < min {
min = value // found another value, replace previous value of min
}
}
fmt.Println("The smallest value is : ", min)
}
This Question Program is running but the First one is not I don't know why.
In mathematics and in computer programming, a variadic function is a
function of indefinite arity, i.e., one which accepts a variable
number of arguments.
Source: Wikipedia
Your function signature is slightly incorrect.
func findMaximum(args ...[]int) []int
This indicates that findMaximum takes in a variable number of int slices as arguments and returns an int slice. The problem that you're trying to solve is asking to take in a variable number of int arguments and return a singular int which is the largest in the set provided.
Calling your function would look something like this:
largest := findMaximum([]int{1, 2, 3}, []int{4, 5, 6}, []int{7, 8, 9})
In this case, largest would be of type []int indicating that the function returned multiple int values in the form of a slice. This wouldn't make sense, since there should only be one largest value (assuming no duplicates).
You're wanting a function signature that looks like this:
func findMaximum(args ...int) int
Calling this function would look like this:
largest := findMaximum(1, 2, 3, 4, 5, 6, 7, 8, 9)
...or if you had your numbers in a slice:
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
largest := findMaximum(nums...)
In this case, largest would be of type int, which is the correct return value you're looking for in this problem.
(Go Playground)
Good luck!
If you run your code through https://play.golang.org/ you will see a couple of syntax errors. Below is the correct version which finds max in a slice.
As you can note you had extra ... in the slice argument call.
package main
import (
"fmt"
)
func findMaximum(args []int) int {
max := args[0]
for _, v := range args {
if v > max{
max = v
}
}
return max
}
func main() {
x := []int{
48, 96, 86, 68,
57, 82, 63, 70,
37, 34, 83, 27,
19, 97, 9, 17,
}
fmt.Println(findMaximum(x))
}

How do I sort a go slice by multiple values?

type Item struct {
Y int
X int
otherProp int
}
I have a slice of structs like the above one. How do I sort the slice item []Item by the X value first and then by the Y value like in SQLs ORDER BY X,Y?
I see that you can use sort.Slice() since go 1.8 but is there a simple way to solve this without looping over the slice several times?
[...] is there a simple way to solve this without looping over the slice several times?
No. Comparison-based sorting basically always involves looping over the slice at least once plus a bit more. But do not worry: sort.Slice does not do too much work.
What's your question?
Following the first example from here: Package sort, I wrote the following...
Inside the Less() function I check if X are equal and if so, I then check Y.
playground demo
package main
import (
"fmt"
"sort"
)
type Item struct {
X int
Y int
otherProp int
}
func (i Item) String() string {
return fmt.Sprintf("X: %d, Y: %d, otherProp: %d\n", i.X, i.Y, i.otherProp)
}
// ByX implements sort.Interface for []Item based on
// the X field.
type ByX []Item
func (o ByX) Len() int { return len(o) }
func (o ByX) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ByX) Less(i, j int) bool {
if o[i].X == o[j].X {
return o[i].Y < o[j].Y
} else {
return o[i].X < o[j].X
}
}
func main() {
items := []Item{
{1,2,3},
{5,2,3},
{3,2,3},
{9,2,3},
{1,1,3},
{1,0,3},
}
fmt.Println(items)
sort.Sort(ByX(items))
fmt.Println(items)
}
output:
[X: 1, Y: 2, otherProp: 3
X: 5, Y: 2, otherProp: 3
X: 3, Y: 2, otherProp: 3
X: 9, Y: 2, otherProp: 3
X: 1, Y: 1, otherProp: 3
X: 1, Y: 0, otherProp: 3
]
[X: 1, Y: 0, otherProp: 3
X: 1, Y: 1, otherProp: 3
X: 1, Y: 2, otherProp: 3
X: 3, Y: 2, otherProp: 3
X: 5, Y: 2, otherProp: 3
X: 9, Y: 2, otherProp: 3
]

Resources