-
-
Notifications
You must be signed in to change notification settings - Fork 684
Update range-iteration concept docs
#3060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| { | ||
| "blurb": "Go has a special \"for range\" loop to easily iterate over collections of data.", | ||
| "blurb": "The for range loop iterates over slices, arrays, maps, strings, and channels.", | ||
| "authors": [ | ||
| "brugnara", | ||
| "tehsphinx" | ||
| ], | ||
| "contributors": [] | ||
| "contributors": [ | ||
| "BNAndras" | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,99 +1,118 @@ | ||
| # About | ||
|
|
||
| You now have the knowledge on how to iterate a slice, or a map, in Go. | ||
| In Go, `range` iterates over slices, arrays, maps, and strings. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And more :) |
||
| Depending on what is being ranged over, each iteration yields one or two values. | ||
|
|
||
| As a recap, you learnt how every iteration returns two values: | ||
| the index/key and a copy of the element at that index/key. | ||
| ## Iterating over a Slice | ||
|
|
||
| ## Iterate over a slice | ||
|
|
||
| Easy as pie, loops over a slice, ordered as expected. | ||
| `range` over a slice yields the index and value of each element in order: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for i, x := range xi { | ||
| fmt.Println(i, x) | ||
| vals := []int{10, 20, 30} | ||
| for i, v := range vals { | ||
| fmt.Println(i, v) | ||
| } | ||
| // outputs: | ||
| // 0, 10 | ||
| // 1, 20 | ||
| // 2, 30 | ||
| // 0 10 | ||
| // 1 20 | ||
| // 2 30 | ||
| ``` | ||
|
|
||
| ## Iterate over a map | ||
| ## Iterating over a Map | ||
|
|
||
| Iterating over a map raises a new problem. | ||
| The order is now random. | ||
| `range` over a map yields each key and value, but the iteration order is not guaranteed. | ||
| Go deliberately randomizes map iteration order, so the same program may produce different output on each run. | ||
|
|
||
| ```go | ||
| hash := map[int]int{9: 10, 99: 20, 999: 30} | ||
| for k, v := range hash { | ||
| fmt.Println(k, v) | ||
| fmt.Println(k, v) | ||
| } | ||
| // outputs, for example: | ||
| // 99 20 | ||
| // 999 30 | ||
| // 9 10 | ||
| ``` | ||
|
|
||
| ~~~~exercism/note | ||
| It may seem the above output is incorrect, as one would expect the first key/value pair on the declaration of the map `9 10` to be the first one printed and not the last. | ||
| However, maps are unordered by nature - there isn't a first or last key/value pair. | ||
| Because of that, when iterating over the entries of a map, the order by which entries will be visited will be random and not follow any specific pattern. | ||
| This means the above output is possible but might differ from what you get if you try to run this yourself. | ||
| To learn more about this see [Go Language Spec: range clause](https://go.dev/ref/spec#RangeClause). | ||
| ~~~~ | ||
|
|
||
| ## Iteration omitting key or value | ||
| ## Omitting Index or Value | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this come after all the range-types? Should we cover ranging over strings? Channels? Maybe mention channels are a later concept? |
||
|
|
||
| In Go an unused variable will raise an error at build time. | ||
| Sometimes you only need the value, as per the first example: | ||
| Go will not compile if a variable is declared but never used. | ||
| If only the value is needed, assign the index or key to `_` to ignore it. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line should probably be moved after the codeblock. |
||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for i, x := range xi { | ||
| fmt.Println(x) | ||
| vals := []int{10, 20, 30} | ||
| for i, v := range vals { | ||
| fmt.Println(v) | ||
| } | ||
| // Go build failed: i declared but not used | ||
| // Go build failed: declared and not used: i | ||
| ``` | ||
|
|
||
| You can replace the `i` with `_` which tells the compiler we don't use that value: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for _, x := range xi { | ||
| fmt.Println(x) | ||
| vals := []int{10, 20, 30} | ||
| for _, v := range vals { | ||
| fmt.Println(v) | ||
| } | ||
| // outputs: | ||
| // 10 | ||
| // 20 | ||
| // 30 | ||
| ``` | ||
|
|
||
| If you want to only print the index, you can replace the `x` with `_`, or simply omit the declaration: | ||
| If only the index is needed, omit it completely: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| // for i, _ := range xi { | ||
| for i := range xi { | ||
| fmt.Println(i) | ||
| vals := []int{10, 20, 30} | ||
| for i := range vals { | ||
| fmt.Println(i) | ||
| } | ||
| // outputs: | ||
| // 0 | ||
| // 1 | ||
| // 2 | ||
| ``` | ||
|
|
||
| Last but not least, if you are required to perform some action but you are not | ||
| interested in values nor keys of the slice or map, you can omit both index and | ||
| value: | ||
| If neither the index nor the value is needed, both can be omitted: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| vals := []int{10, 20, 30} | ||
| count := 0 | ||
| for range xi { | ||
| count++ | ||
| for range vals { | ||
| count++ | ||
| } | ||
| // count == 3 | ||
| ``` | ||
|
|
||
| ## Range over an Integer | ||
|
|
||
| Since Go 1.22, `range` can iterate over an integer directly, yielding values from `0` up to but not including that integer. | ||
| Zero and negative values are valid but produce no iterations. | ||
|
|
||
| ```go | ||
| for n := range 3 { | ||
| fmt.Println(n) | ||
| } | ||
| // 0 | ||
| // 1 | ||
| // 2 | ||
| ``` | ||
|
|
||
| ## Range over an Iterator | ||
|
|
||
| Since Go 1.23, `range` accepts an iterator of type `iter.Seq[V]` or `iter.Seq2[K, V]`. | ||
| An iterator is a function that produces a sequence of values one at a time. | ||
| `range` can then step through those values. | ||
|
|
||
| ```go | ||
| text := "The quick brown fox" | ||
| for word := range strings.FieldsSeq(text) { | ||
| fmt.Println(word) | ||
| } | ||
| // The | ||
| // quick | ||
| // brown | ||
| // fox | ||
|
|
||
| names := []string{"Alice", "Bob", "Vera"} | ||
| for i, v := range slices.All(names) { | ||
| fmt.Println(i, ":", v) | ||
| } | ||
| // count value: | ||
| // 3 | ||
| // 0 : Alice | ||
| // 1 : Bob | ||
| // 2 : Vera | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,100 +1,118 @@ | ||
| # Introduction | ||
|
|
||
| In Go, you can iterate over a `slice` using `for` and an index, or you can use `range`. | ||
| `range` also allows you to iterate over a `map` or a `channel`. | ||
| This concept will cover iterating over a `map` but iterating over a `channel` is out of the scope for this concept. | ||
| In Go, `range` iterates over slices, arrays, maps, and strings. | ||
| Depending on what is being ranged over, each iteration yields one or two values. | ||
|
|
||
| Every iteration returns two values: the index/key and a copy of the element at that index/key. | ||
| ## Iterating over a Slice | ||
|
|
||
| ## Iterate over a slice | ||
|
|
||
| Easy as pie, loops over a slice, ordered as expected. | ||
| `range` over a slice yields the index and value of each element in order: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for i, x := range xi { | ||
| fmt.Println(i, x) | ||
| vals := []int{10, 20, 30} | ||
| for i, v := range vals { | ||
| fmt.Println(i, v) | ||
| } | ||
| // outputs: | ||
| // 0, 10 | ||
| // 1, 20 | ||
| // 2, 30 | ||
| // 0 10 | ||
| // 1 20 | ||
| // 2 30 | ||
| ``` | ||
|
|
||
| ## Iterate over a map | ||
| ## Iterating over a Map | ||
|
|
||
| Iterating over a map raises a new problem. | ||
| The order is now random. | ||
| `range` over a map yields each key and value, but the iteration order is not guaranteed. | ||
| Go deliberately randomizes map iteration order, so the same program may produce different output on each run. | ||
|
|
||
| ```go | ||
| hash := map[int]int{9: 10, 99: 20, 999: 30} | ||
| for k, v := range hash { | ||
| fmt.Println(k, v) | ||
| fmt.Println(k, v) | ||
| } | ||
| // outputs, for example: | ||
| // 99 20 | ||
| // 999 30 | ||
| // 9 10 | ||
| ``` | ||
|
|
||
| ~~~~exercism/note | ||
| It may seem the above output is incorrect, as one would expect the first key/value pair on the declaration of the map `9 10` to be the first one printed and not the last. | ||
| However, maps are unordered by nature - there isn't a first or last key/value pair. | ||
| Because of that, when iterating over the entries of a map, the order by which entries will be visited will be random and not follow any specific pattern. | ||
| This means the above output is possible but might differ from what you get if you try to run this yourself. | ||
| To learn more about this see [Go Language Spec: range clause](https://go.dev/ref/spec#RangeClause). | ||
| ~~~~ | ||
|
|
||
| ## Iteration omitting key or value | ||
| ## Omitting Index or Value | ||
|
|
||
| In Go an unused variable will raise an error at build time. | ||
| Sometimes you only need the value, as per the first example: | ||
| Go will not compile if a variable is declared but never used. | ||
| If only the value is needed, assign the index or key to `_` to ignore it. | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for i, x := range xi { | ||
| fmt.Println(x) | ||
| vals := []int{10, 20, 30} | ||
| for i, v := range vals { | ||
| fmt.Println(v) | ||
| } | ||
| // Go build failed: i declared but not used | ||
| // Go build failed: declared and not used: i | ||
| ``` | ||
|
|
||
| You can replace the `i` with `_` which tells the compiler we don't use that value: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| for _, x := range xi { | ||
| fmt.Println(x) | ||
| vals := []int{10, 20, 30} | ||
| for _, v := range vals { | ||
| fmt.Println(v) | ||
| } | ||
| // outputs: | ||
| // 10 | ||
| // 20 | ||
| // 30 | ||
| ``` | ||
|
|
||
| If you want to only print the index, you can replace the `x` with `_`, or simply omit the declaration: | ||
| If only the index is needed, omit it completely: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| // for i, _ := range xi { | ||
| for i := range xi { | ||
| fmt.Println(i) | ||
| vals := []int{10, 20, 30} | ||
| for i := range vals { | ||
| fmt.Println(i) | ||
| } | ||
| // outputs: | ||
| // 0 | ||
| // 1 | ||
| // 2 | ||
| ``` | ||
|
|
||
| Last but not least, if you are required to perform some action but you are not | ||
| interested in values nor keys of the slice or map, you can omit both index and | ||
| value: | ||
| If neither the index nor the value is needed, both can be omitted: | ||
|
|
||
| ```go | ||
| xi := []int{10, 20, 30} | ||
| vals := []int{10, 20, 30} | ||
| count := 0 | ||
| for range xi { | ||
| count++ | ||
| for range vals { | ||
| count++ | ||
| } | ||
| // count == 3 | ||
| ``` | ||
|
|
||
| ## Range over an Integer | ||
|
|
||
| Since Go 1.22, `range` can iterate over an integer directly, yielding values from `0` up to but not including that integer. | ||
| Zero and negative values are valid but produce no iterations. | ||
|
|
||
| ```go | ||
| for n := range 3 { | ||
| fmt.Println(n) | ||
| } | ||
| // 0 | ||
| // 1 | ||
| // 2 | ||
| ``` | ||
|
|
||
| ## Range over an Iterator | ||
|
|
||
| Since Go 1.23, `range` accepts an iterator of type `iter.Seq[V]` or `iter.Seq2[K, V]`. | ||
| An iterator is a function that produces a sequence of values one at a time. | ||
| `range` can then step through those values. | ||
|
|
||
| ```go | ||
| text := "The quick brown fox" | ||
| for word := range strings.FieldsSeq(text) { | ||
| fmt.Println(word) | ||
| } | ||
| // The | ||
| // quick | ||
| // brown | ||
| // fox | ||
|
|
||
| names := []string{"Alice", "Bob", "Vera"} | ||
| for i, v := range slices.All(names) { | ||
| fmt.Println(i, ":", v) | ||
| } | ||
| // count value: | ||
| // 3 | ||
| // 0 : Alice | ||
| // 1 : Bob | ||
| // 2 : Vera | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...and iterators? Or leave it vauge?