2

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 -}}
Sam Gilbert
  • 1,642
  • 3
  • 21
  • 38
  • Where do the data come from and what type they are stored in? – Nick Krasnov Oct 22 '17 at 14:24
  • updated the question, data stored in json – Sam Gilbert Oct 22 '17 at 14:30
  • 1
    Possible duplicate of [How to create a global variable and change in multiple places in golang html/template?](https://stackoverflow.com/questions/36521839/how-to-create-a-global-variable-and-change-in-multiple-places-in-golang-html-tem/36527130#36527130) – icza Oct 22 '17 at 16:16
  • You have a programming language at your hand, you do not need to use the template engine for this, just use `sort.Sort` on your data before executing the template. – gonutz Oct 24 '17 at 15:08

1 Answers1

0

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++
  }
}
maerics
  • 151,642
  • 46
  • 269
  • 291