I have some data in json
[
{
"group":"a",
"value":"10"
},
{
"group":"a",
"value":"11"
},
{
"group":"b",
"value":"2"
},
{
"group":"b",
"value":"3"
}
]
or as a table to make it easier to read...
group value
a 10
a 11
b 2
b 3
I would like to rank by group
to result in
group value rank
a 10 1
a 11 2
b 2 1
b 3 2
In other languages I would loop through the data with a counter that resets when there is a new group
value. I can range through the data but I can't get a counter working. In the below example it seems like the previous counter value isn't kept in the next iteration and so each value is 1.
{{ $counter := 1 }}
{{- range $index, $element := $data }}
{{ inline (gt $index 0) "," "" }}
{
"group" : "{{ .group }}",
"value" : "{{ .value }}",
"rank" : "{{ $counter }}"
{{ $counter := add $counter 1 }}
}
{{- end -}}
Implementing this sort of logic in a display routine is contrary to the design philosophy of go templates and generally violates the concept of separation of concerns.
That is, templates are a display (or "presentation") concern whereas ranking as you describe here is an ordering (or "business logic") concern. By performing ordering in your display component you are making the program brittle because those concerns cannot change independently of each other nor can they be reused in a general sense. For example, if you wanted to print the elements as JSON or YAML data including their rank you would have to reimplement the ranking logic you wrote in your text template; if the ranking changed for some reason then you would have to change it in multiple places.
A better approach would be to create a function to rank the elements on-demand, independently of how you display them. This way your program is more robust in many ways, mainly since you need rank your items only when they change instead of each time you display them.
For example (Go Playground):
type Item struct {
Group string `json:"group"`
Value int `json:"value,string"`
Rank int `json:"rank,omitempty"`
}
func main() {
// Parse the items.
var items []Item
err := json.Unmarshal([]byte(jsonstr), &items)
check(err)
// Rank the items.
RankItems(items)
// Display the items.
PrintTable(items)
PrintJSON(items)
PrintYAML(items) // etc...
}
func RankItems(xs []Item) {
// Order the items by group, value.
sort.Slice(xs, func(i, j int) bool {
x1, x2 := xs[i], xs[j]
if x1.Group == x2.Group {
return x1.Value < x2.Value
}
return x1.Group < x2.Group
})
// Rank the items by position within a group.
var lastGroup string
var nextRank int
for i := range xs {
if i == 0 || lastGroup != xs[i].Group {
nextRank = 1
}
xs[i].Rank = nextRank
lastGroup = xs[i].Group
nextRank++
}
}