免責事項:私はifを求めていませんstop
argument of slice()
and range()
これらは排他的またはhowでこれらの関数を使用します。
range
関数とslice
関数の呼び出し、およびスライス表記[start:stop]
すべて整数のセットを指します。
range([start], stop[, step])
slice([start], stop[, step])
これらすべてにおいて、stop
整数は除外されます。
なぜ言語がこのように設計されているのか疑問に思っています。
stop
が0に等しいか省略されている場合、start
を表現された整数セットの要素数と等しくするのですか?
持っていることは:
for i in range(start, stop):
次のCコードのように見えますか?
for (i = start ; i < stop; i++) {
documentation は、これにいくつかの有用なプロパティがあることを意味します。
Word[:2] # The first two characters
Word[2:] # Everything except the first two characters
スライス操作の便利な不変式は次のとおりです:
s[:i] + s[i:]
はs
と等しいです。負でないインデックスの場合、スライスの長さが両方とも範囲内にある場合、スライスの長さはインデックスの差になります。たとえば、
Word[1:3]
の長さは2
です。
一貫性を保つために、範囲関数は同じように機能すると想定できます。
これが、Google +ユーザーの opinion です。
[...]半分開いた間隔の優雅さに私は動揺しました。特に、2つのスライスが隣接している場合、最初のスライスの終了インデックスが2番目のスライスの開始インデックスであるという不変式は、無視するには美しすぎます。たとえば、文字列をインデックスiとjで3つの部分に分割するとします。部分はa [:i]、a [i:j]、a [j:]になります。
Google+が閉じているため、リンクは機能しません。ネタバレ注意:それはグイド・ファン・ロッサムでした。
それでもこの質問には少し遅れますが、これは質問のwhy-partに回答しようとします:
その理由の一部は、メモリをアドレス指定するときにゼロベースのインデックス/オフセットを使用するためです。
最も簡単な例は配列です。 「6つの項目の配列」は、6つのデータ項目を格納する場所と考えてください。この配列の開始位置がメモリアドレス100にある場合、データ、たとえば6文字の「Apple\0」は次のように格納されます。
memory/
array contains
location data
100 -> 'a'
101 -> 'p'
102 -> 'p'
103 -> 'l'
104 -> 'e'
105 -> '\0'
したがって、6つのアイテムの場合、インデックスは100から105になります。住所はbase + offsetを使用して生成されます、したがって、最初のアイテムはベースメモリの場所100 +offset0(つまり、100 + 0)にあり、2番目のアイテムは100にあります+ 1、3番目は100 + 2、...、100 + 5が最後の場所になるまで。
これが、ゼロベースのインデックスを使用する主な理由であり、Cのfor
ループなどの言語構造につながります。
for (int i = 0; i < LIMIT; i++)
またはPythonで:
for i in range(LIMIT):
Cのような、ポインターをより直接的に処理する言語、またはアセンブリーをさらに処理する言語でプログラミングする場合、このbase + offsetスキームはより明確になります。
上記のため、多くの言語構成要素は、startからlength-1の範囲を自動的に使用します。
この記事は、Wikipediaの ゼロベースの番号付け に関する記事、および Software Engineering SEからのこの質問 にも掲載されています。
例:
たとえばCでは、配列ar
があり、それをar[3]
として添え字付けした場合、これは配列ar
の(ベース)アドレスを取得し、それに3
を追加するのと同じです=> *(ar+3)
これは、配列の内容を出力するこのようなコードにつながる可能性があり、単純なbase + offsetアプローチを示しています。
for(i = 0; i < 5; i++)
printf("%c\n", *(ar + i));
本当に同等
for(i = 0; i < 5; i++)
printf("%c\n", ar[i]);
排他的な上限がより適切なアプローチであるもう1つの理由は次のとおりです。
リスト内のアイテムのサブシーケンスに変換を適用する関数を作成したいとします。間隔が示唆するように包含的な上限を使用することになった場合、単純に次のように記述してみてください。
def apply_range_bad(lst, transform, start, end):
"""Applies a transform on the elements of a list in the range [start, end]"""
left = lst[0 : start-1]
middle = lst[start : end]
right = lst[end+1 :]
return left + [transform(i) for i in middle] + right
一見、これは簡単で正しいように見えますが、残念ながら微妙に間違っています。
次の場合はどうなりますか:
start == 0
end == 0
end < 0
?一般に、考慮すべき境界ケースがさらに存在する可能性があります。そのすべてについて考えて時間を無駄にしたい人はいますか? (これらの問題は、包括的な下限と上限を使用することで発生します空の間隔を表現する固有の方法がない)。
代わりに、上限が排他的であるモデルを使用することにより、リストを個別のスライスに分割することがより簡単でエレガントになり、したがってエラーが発生しにくくなります:
def apply_range_good(lst, transform, start, end):
"""Applies a transform on the elements of a list in the range [start, end)"""
left = lst[0:start]
middle = lst[start:end]
right = lst[end:]
return left + [transform(i) for i in middle] + right
(ご了承ください apply_range_good
は変換しませんlst[end]
;それもend
を排他的な上限として扱います。包括的な上限を使用するようにしようとしても、以前に述べた問題のいくつかがまだあります。道徳的には、上限を含むことは通常厄介です)
(主に 別のスクリプト言語での上限を含む古い私の投稿 から改作されました。)
正直に言うと、Pythonでスライスする方法はかなり直感に反し、実際にはいわゆるelegant-nessより多くの脳の処理を使用すると、それが このStackOverflowの記事 に2Kを超える賛成票があることがわかります。これは、多くの人が最初は理解していないためだと思います。
たとえば、次のコードはすでに多くのPython初心者にとって頭痛の種となっています。
x = [1,2,3,4]
print(x[0:1])
# Output is [1]
処理が難しいだけでなく、適切に説明することも困難です。たとえば、上記のコードの説明は、最初の要素の前の要素までゼロ番目の要素を取る。
ここでRubyを見てください。これは上限を使用しています。
x = [1,2,3,4]
puts x[0..1]
# Output is [1,2]
率直に言って、私は本当にRubyスライスの方法は脳にとってより良いと思いました。
もちろん、インデックスに基づいてリストを2つの部分に分割する場合、排他的な上限のアプローチは見栄えの良いコードになります。
# Python
x = [1,2,3,4]
pivot = 2
print(x[:pivot]) # [1,2]
print(x[pivot:]) # [3,4]
包括的上限アプローチを見てみましょう
# Ruby
x = [1,2,3,4]
pivot = 2
puts x[0..(pivot-1)] # [1,2]
puts x[pivot..-1] # [3,4]
明らかに、コードはそれほどエレガントではありませんが、ここで行うべき多くの頭脳処理はありません。
結局のところ、それは本当にエレガントさVS明白さの問題であり、Pythonの設計者は明白さよりもエレガントさを好みます。なぜですか? Zen of Python はBeautifulがuglyよりも優れていると述べています。