[python] スライスでリバース！！（スライスの解説もあるよ！）

スライスに関して自分の中では新しい発見だったので備忘録。
基本はいいよって人は :arrow_double_down: [8割スキップ](#%E3%81%8A%E5%BE%85%E3%81%A1%E3%81%8B%E3%81%AD%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9%E3%81%A7%E3%83%AA%E3%83%90%E3%83%BC%E3%82%B9) :arrow_double_down:

## スライスの基本！！

皆さんもリストやら文字列から要素を取り出す際にスライスして取り出すことがあると思います。
スライス？なにそれ？おいしいの？っていう人のためにも使い方を解説しておきましょう。

### 追記（2019/05/27）

python2のサポート終了を見据えて、python3に即した記法に修正しました。

### 準備

スライスじゃないですね :sweat_smile:

```py
#rangeオブジェクトでも良いのですが、説明のために敢えてlistオブジェクトに変換
a = list(range(0,10)) 
a[2] #2
```

### 普通にスライス

```py
# i番目からj-1番目の要素を取り出す
# （ただし、0 <= i < j の関係を満たす
# 　また、【i < 0 のとき i = len(a) + i】【j < 0 のとき j = len(a) + j】とする）
a[i:j] #[a[i], a[i+1], a[i+2], ..., a[j-1]]

# 2番目から7(8-1)番目の要素を取り出す
a[2:8] #[2, 3, 4, 5, 6, 7]

# i を省略すると始点から
a[:6] #[0, 1, 2, 3, 4, 5]

# j を省略すると要素の一番最後まで
a[7:] #[7, 8, 9]
```

### 後ろからスライス

負の数にすると後ろから数えてくれます。

```py
# [-1]で一番最後の要素を取り出す
# これは [len(a)-1] と同義であると考えています（ソース読んでない）
a[-1] #9

# -8番目から-6(-5-1)番目の要素を取り出す
a[-8:-5] #[2, 3, 4] （使わない）

# 始点から-7(-6-1)番目の要素を取り出す
a[:-6] #[0, 1, 2, 3] （ほぼ使わない）

# -2番目から終点までの要素を取り出す
a[-2:] #[8, 9] （拡張子の取り出しなどよく使う）
```

「i番目からj-1番目の要素を取り出す」という決まりごとは変わらないので、以下の例も可能です。

```py
# 2番目から-6(-5-1)番目の要素を取り出す
a[2:-5] #[2, 3, 4]

# -8番目から5(6-1)番目の要素を取り出す
a[-8:6] #[2, 3, 4, 5]
```

スライスの概念を一番わかりやすく解説しているのはやはり[PythonWeb](http://www.pythonweb.jp/tutorial/string/index11.html)ですね。


## 一歩踏み込んでスライス！！

前提として一歩踏み込んだスライスの使い方を紹介！
知らない人は意外といるかも？
ここからは私は知りませんでした。

### ステップスライス（勝手に名づけた）

厳密にはこのステップも含めて、pythonではスライスの仕様の範囲内です。
なので、厳密にはステップスライスと別名にて呼称するのは不自然です。
が、わかりやすければOKということでご容赦願います。。。

```py
# i番目からj-1番目の要素をk個ごとに取り出す
# （ただし、定数 c は 0 <= c < (j-i)/k を満たす最大の整数）
a[i:j:k] #[a[i+0*k], a[i+1*k], a[i+2*k], ..., a[i+c*k]]

# 全体から要素を3個ごとに取り出す
a[::3] #[0, 3, 6, 9]

# 切り出しながら要素を2個ごとに取り出す
a[2:8:2] #[2, 4, 6]
```

ステップ値（`a[::k←こいつ]`）が正の整数の場合、切り出した要素を元にしてステップします（そう考えると良いです）。
ちなみに、<font color="OrangeRed">**ステップ値に"0"は指定できません**</font>（仕様です）。

当然始点や終点の値を省略してのステップスライスも可能です。

```py
# 始点から5(6-1)番目の要素を2個ごとに取り出す
a[:6:2] #[0, 2, 4]

# 7番目から終点までの要素を2個ごとに取り出す
a[7::2] #[7, 9]
```

### 後ろからステップスライス

ステップ値を負の数にすると **逆順（←重要）** にしてステップして取り出してくれます。

```py
# 負の数を使って後ろから
a[::-3] #[9, 6, 3, 0]
```
ステップ値を負の整数にする場合は、基本的にこの使い方のみで使ったほうが良い気がします（理由は後述）。

### ステップスライスの応用

<font color="DarkGray">※この項は本筋とは関係ないので、飛ばしても問題ありません。</font>

ステップ値を負の整数にしてスライスする場合、以下のようになります。
が、難しいです。
そのため、コードレビューや後で見返した際に何をしているのかが分かりづらいといった理由で使わないほうが良いかもしれません。

```py
# 7番目から始点までの要素を-2個ごとに取り出す 
a[7::-2] # [7, 5, 3, 1]

# 終点から5番目までの要素を-2個ごとに取り出す
a[:4:-2] # [9, 7, 5]

# 8番目から3番目までの要素を-2個ごとに取り出す
a[8:2:-2] # [8, 6, 4]

# -2番目から-7番目までの要素を-3個ごとに取り出す
a[-2:-8:-3] # [8, 5]
```

実はこの場合（ステップ値が負の整数の場合）のみ概念的には「i番目から **j + 1** 番目の要素をk個ごとに取り出す」となります。
（スライスの全てにおいて、iまたはjの値が省略された場合は上記の決まりごとには当てはまりません。）
また、他の場合とは違い **i > j** の関係になることも注意が必要です。

先程重要と書いた**逆順になる**ということを思い出してください。
k < 0 の場合は i、j についても大小関係が反転すると考えると良さそうです。
（数学で言うところの辺々の正負を逆転させると、大小関係が逆転するようなイメージが近いかもしれません。）



## お待ちかね！スライスでリバース！！

一応経緯。
どうでもいいが[ここ](http://nlab.itmedia.co.jp/nl/articles/1505/10/news014.html)を見てpython使ったら反対から読む文章の作成とか楽勝だろと思い3分クッキングの感覚で入力。
（画像の **！すまし錨抜 アュギィフムアミレプ ぜかまし×たなこ** の話）

```py
konata = 'こなた×しまかぜ プレミアムフィギュア 抜錨します！'
print(''.join(reversed(konata)))
# ！すまし錨抜 アュギィフムアミレプ ぜかまし×たなこ
```

でも残念ながらこの方法は一度`reversed(konata)`の部分でオブジェクトを生成していますのでなんか気に入りません。
もっとスマートに！！
と思い色々探していたらありました！！
おなじみ、stack overflow
[Best way to create a “reversed” list in Python?](https://stackoverflow.com/questions/3705670/best-way-to-create-a-reversed-list-in-python)

上から順に読んできた方はもうお気づきですね。
以下になります。

```py
print(konata[::-1])
# 変態記法：print(konata[-1:0 - len(konata):-1])
# ！すまし錨抜 アュギィフムアミレプ ぜかまし×たなこ
```

**Very very very very SMART!!**
興奮しすぎですね。
しかも上記リンクを読んだらわかるとおり速いらしいです。（私は未検証）
そういう点においても **SMART** ですね。


## まとめ

**逆順にしたけりゃ`[リストor文字列][::-1]`にしとけ！！**

### 【おまけ】スライスの定理（？）

`[リストor文字列][i:j:k]`についてステップ値 k が無指定の場合は k = 1 であると定義する（仕様より）。
また、【i < 0 のとき i = len(`[リストor文字列]`) + i】【j < 0 のとき j = len(`[リストor文字列]`) + j】とする。
その時以下が成り立つ。

1. k > 0 のとき「 i 番目から j - 1 番目の要素を k 個ごとに取り出す（i < j）」
2. k < 0 のとき「 i 番目から j + 1 番目の要素を k 個ごとに取り出す（i > j）」

ただし、i または j の値が省略されたときは不成立。

## 参照

[4章 組み込み型 - 4.6.1 共通のシーケンス演算](https://docs.python.org/ja/3.10/library/stdtypes.html#common-sequence-operations)