[Pythonクイズ]リストの末尾要素にはどんな方法でアクセスできる? おまけもあるよ(ただし、おまけが長過ぎた):Pythonステップアップクイズ
リストの末尾要素にアクセスするには幾つかの方法がありますよね。シンプルな方法を知っているかどうかをこのクイズで確認してみましょう。おまけの問題もあるので、頭の体操に使ってみてね。
【問題】
以下のPythonコードでは、mylistに代入されたリストの末尾要素を取り出して、その値を表示している。しかし、Pythonでは末尾要素をもっとシンプルに取り出せる。その方法とは何だろう。
mylist = [8, 5, 5, 10, 4, 1, 2]
n = mylist[len(mylist) - 1]
print(n) # 2
3つの大規模言語モデルに聞いてみた
今回はPythonの基本に立ち返ったようなクイズなので、ChatGPT/Claude/Geminiに素直に答えを聞いたところで違いは出ないだろうなぁと思いました。そこでおまけ要素として、スライスを使ってリストの全ての要素を取り出すにはどうすればよいかを聞いてみました。ただし、ちょっとした条件付きで。3つのLLMはそれぞれに違う答えをくれました。その答えを見た筆者の感想は以下です。
- ChatGPTの答えを見た感想:その発想はなかった。けど、確かにできるね
- Geminiの答えを見た感想:条件を満たすシンプルでよい方法が見つからなかったんですね
- Claudeの答えを見た感想:これが一番シンプルでいいと思います
さて、筆者はどんな条件を付けたのでしょうか。
どうもHPかわさきです。
今回のクイズ、ちょっと簡単過ぎますよねぇ。でも、たまにはこういうのもいいでしょ。他にも幾つか候補はあったんですよ。ここのところ、シンプルじゃないヤツが多くなってきた感があったので、前回(Noneのお話)と今回はシンプルなコードで基本的な話題を取り上げてみています。難しい問題だとコードも長くなっちゃうし、解説も長くなっちゃうし、軽く読んでもらうにはなかなか大変かなと思うこともあるのです。その分、今回は3つのLLMにヘンな質問をしていますので、そっちにも着目してくださいね。
【答え】
正解のコード例を以下に示します。
mylist = [8, 5, 5, 10, 4, 1, 2]
n = mylist[-1]
print(n) # 2
もちろん答えは「mylist[-1]」という形式で末尾要素にアクセスするでした。
負のインデックスを使うことで、末尾から順番にリスト(や他の順序付きの反復可能オブジェクト)の要素を取り出せるというのはぜひ覚えておきましょう。
【解説】
解説の必要があるかどうかは微妙なのですが、今回のmylistについて正のインデックスと負のインデックスを使ってアクセスするにはどうなるかを以下に示しておきましょう(「正のインデックス」と書いていますが、ここでは「0」を含んでいます)。
正のインデックスは0始まりでリストの先頭を基点とし、値が増えるに従ってリストの末尾方向の要素を指すようになります。負のインデックスは-1始まりでリストの末尾を基点とし、値が小さくなるに従ってリストの先頭方向の要素を指すようになります。
これを知っていれば「リストの末尾要素にアクセスしたい」ときにはlen関数を使って「len(mylist) - 1」のような計算をしなくて済みます。0始まりなので、末尾要素のインデックスは「要素数-1」となるとか、そんなことに気を使う必要もありません。
えっと、それくらいですかね。じゃあ、今日は解散!
というわけにもいかないので、以下はおまけです。おまけですが、長いですよ?
冒頭のカコミで3つのLLMに出した条件についても見てみましょう。スライスを使ってリストの全ての要素を取り出す方法に付けた条件とは以下のようなものです。
- リストの全要素を正順(リストの先頭から末尾の順番)で取り出す
- 負のスライス(インデックス)のみを使う
- len関数は使わない
スライスを使ってリストの全要素を取り出すだけならカンタンです。
mylist = [8, 5, 5, 10, 4, 1, 2]
result = mylist[:]
print(result) # [8, 5, 5, 10, 4, 1, 2]
ですが、これでは「負のスライスのみを使う」という条件は満たせません。負のスライスを使って全要素を取り出す方法として有名なのは以下ですよね。
mylist = [8, 5, 5, 10, 4, 1, 2]
result = mylist[::-1]
print(result) # [2, 1, 4, 10, 5, 5, 8]
これはスライスで先頭と末尾のインデックスを省略、つまり全範囲から要素を取り出すとともに、取り出す要素のステップに-1を指定したので、リストの全要素を逆順に取り出すことになります。よって、正順で取り出すという条件に反しています。
では負のスライス(インデックス)のみを使ってリストの全要素を取り出すにはどうしたらよいでしょう。要素数が分かっていれば、次のようなことが可能です。
mylist = [8, 5, 5, 10, 4, 1, 2]
start = -1 * len(mylist)
result = mylist[start:]
print(result) # [8, 5, 5, 10, 4, 1, 2]
ここでは要素数をlen関数で取得して、それに-1を掛けることでリスト先頭要素の負のインデックスを計算しています(「いや、目で見て数えられるから」というのもlen関数を使うのと同じですよ?)。そして、それを使ってスライスの先頭要素を負値で指定しますが、もちろんこれは「len関数は使わない」という条件に反しています。
こうやって考えるとなかなか難しい問題のようです。
実際、Geminiの最終結論は「mylist[:]のような書き方しかない」というものでした。これに対して、ChatGPTが出した答えは次のようなものです。
mylist = [8, 5, 5, 10, 4, 1, 2]
result = [*mylist[::-1]][::-1]
print(result) # [8, 5, 5, 10, 4, 1, 2]
これは負値のみを使ったスライスとリストのアンパックを組み合わせた解法です。「[*mylist[::-1]][::-1]」という部分が分かりにくいので、そこを分解してみましょう。
mylist = [8, 5, 5, 10, 4, 1, 2]
tmp = [*mylist[::-1]]
print(tmp) # [2, 1, 4, 10, 5, 5, 8]
result = tmp[::-1]
print(result) # [8, 5, 5, 10, 4, 1, 2]
「*mylist[::-1]」の「mylist[::-1]」は先ほども見たように、mylistの全要素を逆順に取り出すものです。そこに「*」が付いています。これは関数の引数として「some_func(*[1, 2, 3])」としたときに、リストの要素がアンパックされて実際には「some_func(1, 2, 3)」となるのと同じ効果を持ちます。つまり、tmpというリストには逆順に取り出されたリストの要素が展開されて渡されるということです。ちなみに「*」がないと次のようになります。
mylist = [8, 5, 5, 10, 4, 1, 2]
tmp = [mylist[::-1]]
print(tmp) # [[2, 1, 4, 10, 5, 5, 8]]
というわけで、「[*mylist[::-1]][::-1]」の「[*mylist[::-1]]」という部分はリストの全要素を逆順に取り出したリストになります。そこに「[::-1]」というスライスの指定があるので、そこからまた逆順で全要素を取り出すということです。全体としては「逆順でリストの全要素を取り出して、そこから逆順で全要素を取り出す」つまり元のリストと同じ順序のリストが得られるというわけですね。メンドくさ。でも、全ての条件は満たしています。確かにできるけど、その発想はなかったわーというのが感想です。
これに対して、Claudeが出した答えはこれ。
mylist = [8, 5, 5, 10, 4, 1, 2]
result = mylist[::-1][::-1]
print(result) # [8, 5, 5, 10, 4, 1, 2]
上の説明を読んでもらえていれば、これもまた「リストから逆順でリストの全要素を取り出して、そこからまた逆順で全要素を取り出している」ことが分かるはず。しかも、「アンパックがどうだ」とか、小難しい理屈が必要ありません。シンプル(?)で分かりやすいコードになっています。
というわけで、負のスライスだけを使って、リストから正順に全ての要素を取り出すには「逆順で全要素を取り出して、そこからまた逆順で全要素を取り出す」やり方でいけるということが分かったのでした。
以上、おまけ終わり!
書き出してから分かりました。おまけの方を問題にすべきでした……。
しょうがないから、カンファレンスなんかでよくある感じに締めたいと思います。
今日、皆さんに持ち帰ってほしいことは次の2点です。
- 末尾要素には-1というインデックスでアクセスできる
- 負のスライスだけで、あるリストから全要素をそのリストと同じ順序で取り出すには「リスト[::-1][::-1]」とする
いや、2点目は覚える必要ないです。なぜなら「リスト[:]」の方が効率が良いですから。
いいのかな。こんな終わり方で(笑)。
Copyright© Digital Advantage Corp. All Rights Reserved.