LoginSignup
1
4

More than 3 years have passed since last update.

Pythonのインデックスとスライスについてのまとめ

Last updated at Posted at 2019-11-17

はじめに

何番煎じかわからないが、自分で体験して自分の言葉で書くのが大事だ。

インデックス(特に文字列に対して)

普通に文字列を定義するだけで、一文字ずつ格納された一次元配列のように扱うことができる。

letters="ABCDEFG"

# (0から数えて)3番めは?
letters[3]  # 'D'

マイナスを使って後ろからカウントすることもできる。
正順のときは0からカウントするが、後ろからの場合は最後は-1。-0ではない。当然か。

# 後ろから2番めは?
letters[-2]  # 'F'

文字列を一次元配列として一文字ずつ取り出すこともできる。

ソース
for letter in letters:
  print (letter)
結果
A
B
C
D
E
F
G

今回の趣旨からは離れるが、enumerate関数を覚えておくと便利。インデックスと要素を同時に取得することができる。
これを使わなくても同等の結果を得ることは難しくない。可読性の問題だ。もしくはこの関数を持たない他言語への移植を考慮しているとか。
「一度で複数の変数に値を取得できるのは気持ち悪い」という意見には大いに同意するが、これは慣れたほうがよい。

enumerate関数を使ったソース
for i,letter in enumerate(letters):
  print (i,letter)
enumerate関数を使わないソース
for i in range(len(letters)):
  print (i,letters[i])
結果は同じ
0 A
1 B
2 C
3 D
4 E
5 F
6 G

昭和な香りのするプチコンにも、マニュアルには明記されていないが隠しで文字列に対するインデックスの仕様が織り込まれている(enumerate関数はない)。
なお、下の画像は合成であり、3DSのプチコン3号は上画面でリストを表示させながら下画面で実行させることはできない。どうでもいいけど。

petitcom3.png

ある文字列の中に特定の文字列が含まれるかどうかを調べるのがこんなに簡単なのは、文字列が一次元配列としても使えるという仕様のおかげかもしれない。

ソース
if "CD" in "ABCDEFG":  # 変数だとやっていることが分かりづらいので見えるようにした
  print ("含まれるぞ")
else:
  print ("含まれないぞ")
結果
含まれるぞ

スライス

スライスは配列の一部分を取り出すことができるワザ。[start: end: step]で表現する。startを省略すれば最初から、endを省略すれば最後まで、stepを省略すればstep=1となる。
インデックスと同様0からカウントし、endに指定した値そのものは含まれない。

文字列/一次元配列にスライスする

基本

# 確認しやすくするために再度定義
letters="ABCDEFG"

# startとendを指定 stepは省略
letters[3:5]  # 'DE'

endで指定したインデックスが実際は含まれないというのは不便に思えるが、利点もある。

# 確認しやすくするために再度定義
letters="ABCDEFG"

# endのみ指定=いわゆるleft関数
letters[:4]  # 'ABCD'

# startのみ指定=指定した位置から最後まで
letters[4:]  # 'EFG'

このように、インデックスを一つ指定することで配列を分割することができるのだ。

マイナスを指定

マイナスの扱いにも慣れておこう。

# 確認しやすくするために再度定義
letters="ABCDEFG"

# 後ろから3文字目(スライスでなくインデックスによる指定、既出)
print(letters[-3])  # 'E'

# startのみ指定=「後ろから3文字目」から最後まで いわゆるright関数
print(letters[-3:])  # 'EFG'

# endのみ指定=最初から「後ろから3文字目」まで
print(letters[:-3])  # 'ABCD'

# 利用頻度は低いだろうがもちろん両方マイナスにしてもいい。大小関係に注意。
print(letters[-5:-3])  # 'CD'

mid関数/left関数/right関数

スライスを利用してmid関数を自作することもできる。
関数化するまでもないかもしれないが、ついでにleft関数とright関数も作ってみよう。
lengthのデフォルト値で0を指定したのはlenghを省略したときに「指定した位置から最後まで」を出力させるため。
-1は先頭の文字を0文字目でなく1文字目とカウントするためのもの。
そういえばExcelではワークシート関数のMIDとVBAのMid関数で挙動が違んだよなあ。

def left(text, length):
  return text[:length]

def right(text, length):
  return text[-length:]

def mid(text, pos, length=0):
  if not length:
    length=len(text)
  return text[pos-1:pos+length-1]

stepを指定

stepを指定するとこうなる。
まずは普通にステップ2で取得していく。

# 確認しやすくするために再度定義
letters="ABCDEFG"

letters[1:5:2]  # 'BD'

# これはこういう仕組み。
# ABCDEFG
# 0123456
#  ◯ ◯ ×
#      第2引数=5なので5文字目は含まない

stepにマイナスを指定すると逆方向に進む。
逆方向に進むのでendのほうが小さくstartのほうが大きい必要がある。

letters[5:1:-2]  # 'FD'

stepがマイナスだなんてそんなイレギュラーな使い方しねーよと思っていたのだが、startとendを全指定しstepを-1にする、つまり::-1とすると全要素が逆順になるという動きは頻繁に使うので覚えておくとよい。
何に使うかというと、OpenCVのimreadで読み込んだ画像はBGRで色が格納されているのだが、これをRGBに変換するときに使えるのだ。OpenCVには色をコンバートする関数もあるが、こっちのほうがスマートだ。

ソース
print (letters)
print (letters[::-1])
結果
ABCDEFG
GFEDCBA

二次元配列

本当はここから話がしたかった。
まず二次元配列を作る。ここでいきなりつまづいたのが前回の話。

ソース
# お約束
import numpy as np

# 高さと幅
H,W = 5,6

# 最初にサイズだけ定義した空配列を作る
arr=np.full((H,W),"",dtype=object)

# Excelのセル名のような値を定義する
letters="ABCDEFG"
for c in range(W):
  for r in range(H):
    arr[r,c]=letters[c]+str(r)

print (arr)
結果
[['A0' 'B0' 'C0' 'D0' 'E0' 'F0']
 ['A1' 'B1' 'C1' 'D1' 'E1' 'F1']
 ['A2' 'B2' 'C2' 'D2' 'E2' 'F2']
 ['A3' 'B3' 'C3' 'D3' 'E3' 'F3']
 ['A4' 'B4' 'C4' 'D4' 'E4' 'F4']]

普通にインデックスで指定するとこうなる。
何度も書くが、カウントは0から。

r,c=2,1
arr[r,c]  # 'B2'

スライスを行と列に指定するとこうなる。
基本が[start: end]なので座標チックに[r1: r2, c1: c2]としてもいいが、[r: r+h, c: c+w]といった書き方で覚えたほうがよいと思う。

ソース
r,c = 2,1  # 始点の行と列
h,w = 3,4  # 範囲の高さと幅
print(arr[r:r+h, c:c+w])
結果
[['B2' 'C2' 'D2' 'E2']
 ['B3' 'C3' 'D3' 'E3']
 ['B4' 'C4' 'D4' 'E4']]

無事に指定した行数・列数の配列が取得できている。

インデックスとスライスの合せ技、というか注意点

インデックスによる指定は一つの要素を返すが、スライスは配列の一部分を返す。たとえ1行1列であっても。

ソース
r,c = 2,1  # 始点の行と列
h,w = 1,1  # 範囲の高さと幅
print ("インデックス",arr[r, c])
print ("スライス",arr[r:r+h, c:c+w])
インデックス B2
スライス [['B2']]

とはいえ、これ、文字列には該当しないようだ。

ソース
# 確認しやすくするために再度定義
letters="ABCDEFG"

print ("インデックス",letters[3])
print ("スライス",letters[3:3+1])
結果
インデックス D
スライス D

二次元配列の行列のいずれかをインデックスで、他方をスライスでそれぞれ指定すると、一次元配列になる。
一次元配列というか単なる列挙、リストだ。

ソース
r=3
print (arr[r,:])  # 列は全指定

c=4
print (arr[:,c])  # 行は全指定
結果
['A3' 'B3' 'C3' 'D3' 'E3' 'F3']
['E0' 'E1' 'E2' 'E3' 'E4']

1行1列をスライスした結果を上に書いたが、もう少し実用的な例を挙げていこう。
二次元配列の行をスライスで1行指定し、列もスライスすると以下のようになる。
ブラケットが二重になっている。1行w列の二次元配列だ。

ソース
r=3
print (arr[r:r+1,:])
結果
[['A3' 'B3' 'C3' 'D3' 'E3' 'F3']]

行をスライスで指定して列もスライスで1行指定すると、h行1列の二次元配列になる。

ソース
c=4
print (arr[:,c:c+1])
結果
[['E0']
 ['E1']
 ['E2']
 ['E3']
 ['E4']]

二次元配列でスライスのステップを使う

普通のステップの使い方でなく::-1で反転させる例のみ示す。

ソース
print("元の形")
print(arr)
print("第一引数=行を反転する")
print(arr[::-1])
print("第二引数=列を反転する 第一引数は全指定。::でもいいし:でもいい")
print(arr[::, ::-1])
print("行と列の両方を反転する")
print(arr[::-1, ::-1])
結果
元の形
[['A0' 'B0' 'C0' 'D0' 'E0' 'F0']
 ['A1' 'B1' 'C1' 'D1' 'E1' 'F1']
 ['A2' 'B2' 'C2' 'D2' 'E2' 'F2']
 ['A3' 'B3' 'C3' 'D3' 'E3' 'F3']
 ['A4' 'B4' 'C4' 'D4' 'E4' 'F4']]
第一引数行を反転する
[['A4' 'B4' 'C4' 'D4' 'E4' 'F4']
 ['A3' 'B3' 'C3' 'D3' 'E3' 'F3']
 ['A2' 'B2' 'C2' 'D2' 'E2' 'F2']
 ['A1' 'B1' 'C1' 'D1' 'E1' 'F1']
 ['A0' 'B0' 'C0' 'D0' 'E0' 'F0']]
第二引数列を反転する 第一引数は全指定::でもいいし:でもいい
[['F0' 'E0' 'D0' 'C0' 'B0' 'A0']
 ['F1' 'E1' 'D1' 'C1' 'B1' 'A1']
 ['F2' 'E2' 'D2' 'C2' 'B2' 'A2']
 ['F3' 'E3' 'D3' 'C3' 'B3' 'A3']
 ['F4' 'E4' 'D4' 'C4' 'B4' 'A4']]
行と列の両方を反転する
[['F4' 'E4' 'D4' 'C4' 'B4' 'A4']
 ['F3' 'E3' 'D3' 'C3' 'B3' 'A3']
 ['F2' 'E2' 'D2' 'C2' 'B2' 'A2']
 ['F1' 'E1' 'D1' 'C1' 'B1' 'A1']
 ['F0' 'E0' 'D0' 'C0' 'B0' 'A0']]

こういうの、自分でやってみないと覚えないものだ。

次回予告

ここからOpenCVの画像に入りたかったのだが力尽きてしまった。次こそは画像の話をするぞ。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4