概要
range
が返す変数の仕様で躓いたので、備忘録として残しておきます。
(下記でいうk
, v
)
package main
import "fmt"
func main() {
foo := map[string]string{"key": "value"}
for k, v := range foo {
fmt.Printf("foo[%v]: %v", k, v)
}
}
// => foo[key]: value
詳細
結論
range
を使うときは以下の方針で書くのがいいと思います。
- 要素を値を更新したい ⇒ 1つ目の変数を使う
- 要素を更新したくない ⇒ 2つ目の変数を使う
理由は、
- 1つ目の変数は配列, mapのindex, keyであり、この値を使えばループしている配列, mapの要素の実体を指定できる
- 2つ目の変数は1つ目の変数で返されたindex, keyで示される値のコピーである(値渡しである)
というGoの仕様があるからです。つまり、2つ目の引数に再代入しても何も起こらないということですね。
Tour of Goにもしっかり書いてあるのですが、私はハマってみないと理解ができなったようです…。
https://go-tour-jp.appspot.com/moretypes/16
検証
Atcoderのabc081_b - Shift onlyという問題です。
-
n
,A1, A2, ...An
の標準入力を受け取る -
A1, A2, ...An
が全て偶数か判定する - 全て偶数なら
A1, A2, ...An
の全てを2で割る - 再度偶数判定を行う…
- “全てを2で割る”を実行した回数を出力する
という流れ。
NGのコードはこちら:
package main
import (
"fmt"
)
func main() {
var length int
fmt.Scanf("%d", &length)
nums := make([]int, length)
for i, _ := range nums {
fmt.Scanf("%d", &nums[i])
}
var count int
var flag bool
for {
flag = false
for _, n := range nums {
if n%2 == 0 {
flag = true
} else {
flag = false
break
}
}
if !flag {
break
}
count++
for _, n := range nums {
n = n / 2
}
}
fmt.Print(count)
}
このコードで下記の入力を与えると処理が終わらず失敗します。
6
382253568 723152896 37802240 379425024 404894720 471526144
原因はこの部分です。
このn
はnums[index]
のコピーなので、ここでn
の値を更新しようがnums
の中身(つまりA1, A2, ...An
)は更新されず、無限ループになるわけです。
count++
for _, n := range nums {
n = n / 2
}
以下のように、range
の1つ目の引数でnums
の要素を指定するように修正すればOKのコードになります。
count++
for i, _ := range nums {
nums[i] = nums[i] / 2
}
先ほどと同じ入力を与えると、正常に処理は完了し、期待値通りの8
が出力されます。
最後に
「問題は配列のループを使えばいいのだからrange
を使わなければよかったのでは?」とも思いましたが、”要素の数だけ処理を行う”というのを強調するならrange
を使った方がわかりやすいだろうなぁと考えました。
以上です!