Iterate Go map get index
Asked Answered
B

4

10

In order to use revel's even keyword in templates I would like to get the index of a map entry when iterating with range. Is there any way to do so? My map has the structure:

map[string][]string
Baeyer answered 22/3, 2017 at 10:12 Comment(3)
Your map keys are strings like "foo" and "wtf". What is an even string? That's not how things work.Illustrational
Must you use even, or could you use CSS styling (:nth-child(odd), :nth-child(even)) and forget about even?Windsor
@Windsor this actually solved my problem. If you post it as Answer I will accept it, so the case is clearedBaeyer
S
4

You can't do this only with template actions, but you may register a function which provides the necessary help.

You may register a function which returns a function (closure), which alternates its return value whenever called (exactly how "odd" and "even" indices alternate):

func isEven() func() bool {
    e := false
    return func() bool {
        e = !e
        return e
    }
}

I named it isEven() to not collide with ravel's even(). Using it:

func main() {
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "isEven": isEven,
    }).Parse(templ))

    m := map[string]string{
        "a": "A", "b": "B", "c": "C", "d": "D",
    }
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [even:{{call $e}}] key={{$k}}; value={{$v}}
{{end}}`

Output (try it on the Go Playground):

[even:true] key=a; value=A
[even:false] key=b; value=B
[even:true] key=c; value=C
[even:false] key=d; value=D

If you want different output for odd and even iterations, you can call $e in an {{if}} action, like this:

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [{{if call $e}}even{{else}}odd {{end}}] key={{$k}}; value={{$v}}
{{end}}`

Output of this (try it on the Go Playground):

[even] key=a; value=A
[odd ] key=b; value=B
[even] key=c; value=C
[odd ] key=d; value=D

Under the hood

This template action:

{{$e := isEven}}

Creates a new template variable named $e, and its value will be the result (return value) of the isEven() function call. isEven() returns a function value, a closure that has access to a local variable e of type bool. When later you do {{call $e}}, you're not calling the isEven() Go function, but the function it returned (the closure) and is stored in $e. That closure has a reference to the local bool variable e, it is not "freed" until the function returned by isEvent() is accessible.

So whenever you do {{call $e}}, it calls the closure, which "has" an e variable of type bool, whose value is retained between calls of this $e.

If you would call isEvent in the template again, that would return a new function (closure), wrapping a new instance of the local variable e, being independent of the first wrapped variable of the closure returned by the first isEvent() call.

Sacculus answered 22/3, 2017 at 11:20 Comment(2)
Its works, but I am curious, why does e not gets reset to false every time I call isEven()?Baeyer
@Mrlenny Added a new Under the hood section to explain the internals.Sacculus
C
14

A Simple way to achieve index while looping through a map:

package main

import (
    "fmt"
)

func main() {
    mm := map[string]int{"xx" : 1, "gg" : 2}
    cnt := 0
    for a, b:= range mm{
        fmt.Println("a", a, "b",b, "c" , cnt)
        cnt++
    }
    fmt.Println("Hello, playground")
}

And prints:

a xx b 1 c 0
a gg b 2 c 1
Hello, playground
Cobaltous answered 17/5, 2018 at 2:9 Comment(0)
S
4

You can't do this only with template actions, but you may register a function which provides the necessary help.

You may register a function which returns a function (closure), which alternates its return value whenever called (exactly how "odd" and "even" indices alternate):

func isEven() func() bool {
    e := false
    return func() bool {
        e = !e
        return e
    }
}

I named it isEven() to not collide with ravel's even(). Using it:

func main() {
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "isEven": isEven,
    }).Parse(templ))

    m := map[string]string{
        "a": "A", "b": "B", "c": "C", "d": "D",
    }
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [even:{{call $e}}] key={{$k}}; value={{$v}}
{{end}}`

Output (try it on the Go Playground):

[even:true] key=a; value=A
[even:false] key=b; value=B
[even:true] key=c; value=C
[even:false] key=d; value=D

If you want different output for odd and even iterations, you can call $e in an {{if}} action, like this:

const templ = `{{$e := isEven}}
{{- range $k, $v := . -}}
    [{{if call $e}}even{{else}}odd {{end}}] key={{$k}}; value={{$v}}
{{end}}`

Output of this (try it on the Go Playground):

[even] key=a; value=A
[odd ] key=b; value=B
[even] key=c; value=C
[odd ] key=d; value=D

Under the hood

This template action:

{{$e := isEven}}

Creates a new template variable named $e, and its value will be the result (return value) of the isEven() function call. isEven() returns a function value, a closure that has access to a local variable e of type bool. When later you do {{call $e}}, you're not calling the isEven() Go function, but the function it returned (the closure) and is stored in $e. That closure has a reference to the local bool variable e, it is not "freed" until the function returned by isEvent() is accessible.

So whenever you do {{call $e}}, it calls the closure, which "has" an e variable of type bool, whose value is retained between calls of this $e.

If you would call isEvent in the template again, that would return a new function (closure), wrapping a new instance of the local variable e, being independent of the first wrapped variable of the closure returned by the first isEvent() call.

Sacculus answered 22/3, 2017 at 11:20 Comment(2)
Its works, but I am curious, why does e not gets reset to false every time I call isEven()?Baeyer
@Mrlenny Added a new Under the hood section to explain the internals.Sacculus
H
2

Map entries have no index in Go; there's no way you can get an index out of any element. Also, each time you iterate on a map with range, you get a different order - another hint that there's no index concept in maps.

Indexes are related to ordered data structures only (e.g. arrays, slices, lists, etc), not maps. Take a look at https://blog.golang.org/go-maps-in-action for more details.

Haggle answered 22/3, 2017 at 11:59 Comment(0)
D
-3
{{range $key, $element := .Map}}
  {{$index := index .Map $key}}
{{end}}
Duckworth answered 22/3, 2017 at 10:29 Comment(2)
here both $index and $element are string/[]string. But I want the number of iterationBaeyer
with the edit i get ` error calling index: cannot index slice/array with type string`. I worked around it by now with help of @keynnytm's commentBaeyer

© 2022 - 2024 — McMap. All rights reserved.