web-dev-qa-db-ja.com

Pythonで包括的範囲をどのように処理する必要がありますか?

私は、範囲が包括的に記述されているドメインで働いています。 from A to Bなどの人間が読み取れる説明があります。これは、両方のエンドポイントを含む範囲を表します。 from 2 to 42, 3, 4を意味します。

Pythonコードでこれらの範囲を操作する最良の方法は何ですか?次のコードは整数の包括的範囲を生成するために機能しますが、包括的スライス操作も実行する必要があります。

def inclusive_range(start, stop, step):
    return range(start, (stop + 1) if step >= 0 else (stop - 1), step)

私が見る唯一の完全な解決策は、rangeまたはスライス表記(たとえば、range(A, B + 1)+ 1range(B, A - 1, -1))を使用するたびに明示的に- 1(またはl[A:B+1])を使用することです。この繰り返しは、包括的範囲で作業するための本当に最良の方法ですか?

編集:L3viathanの回答に感謝します。 inclusive_sliceを補完するinclusive_range関数を作成することは確かにオプションですが、おそらく次のように作成します。

def inclusive_slice(start, stop, step):
    ...
    return slice(start, (stop + 1) if step >= 0 else (stop - 1), step)

ここで...は、負のインデックスを処理するコードを表します。スライスで使用する場合は簡単ではありません。たとえば、slice_to == -1の場合、L3viathanの関数は誤った結果を与えることに注意してください。

しかし、inclusive_slice関数は使いにくいようです-l[inclusive_slice(A, B)]は本当にl[A:B+1]よりも優れていますか?

包括的な範囲を処理するより良い方法はありますか?

編集2:新しい回答をありがとう。 FrancisとCorleyは、スライス操作の意味をグローバルに、または特定のクラスに対して変更すると、大きな混乱につながることに同意します。したがって、私は今inclusive_slice関数を書くことに傾いています。

前の編集からの私自身の質問に答えるために、そのような関数(例えばl[inclusive_slice(A, B)])を使用することは手動で1(例えばl[A:B+1])を加算/減算するよりも良いだろうという結論に達しました。 1つの場所で処理されるケース(B == -1B == Noneなど)。関数を使用する際の厄介さを減らすことはできますか?

Edit 3:l[inclusive_slice(1, 5, 2)]のように見える使用構文を改善する方法を考えてきました。特に、包括的スライスの作成が標準のスライス構文に似ているとよいでしょう。これを可能にするために、inclusive_slice(start, stop, step)の代わりに、スライスをパラメーターとして取る関数inclusiveがあります。 inclusiveの理想的な使用構文は、1行です。

l[inclusive(1:5:2)]          # 1
l[inclusive(slice(1, 5, 2))] # 2
l[inclusive(s_[1:5:2])]      # 3
l[inclusive[1:5:2]]          # 4
l[1:inclusive(5):2]          # 5

残念ながら、これはPythonでは許可されておらず、:内で[]構文のみを使用できます。したがって、inclusiveは、構文2または3s_numpyが提供するバージョン )のように動作する必要があります。

他の可能性は、__getitem__を使用してinclusiveをオブジェクトにし、4構文を許可するか、_inclusiveをスライスのstopパラメーターにのみ適用することです(構文5のように)。残念ながら、inclusiveにはstep値の知識が必要なので、後者を機能させることはできないと思います。

実行可能な構文(元のl[inclusive_slice(1, 5, 2)]に加えて23、および4)のうち、使用するのに最適な構文はどれですか?または、別のより良いオプションがありますか?

最終編集:返信とコメントをありがとうございました。これは非常に興味深いものです。私は常にPythonの「一方向」哲学のファンでしたが、この問題はPythonの「一方向」と問題ドメインによって禁止されている「一方向」との間の矛盾によって引き起こされました。言語設計で [〜#〜] timtowtdi [〜#〜] に感謝していることは間違いありません。

最初に投票された最高の回答に対して、L3viathanに賞金を授与します。

43
user200783

包含スライス用の追加関数を作成し、スライスする代わりにそれを使用します。それは可能ですが、例えばサブクラスのリストを作成し、スライスオブジェクトに反応する___getitem___を実装します。あなたのコードはあなた以外の人の期待に反する振る舞いをするでしょう。

_inclusive_slice_は次のようになります。

_def inclusive_slice(myList, slice_from=None, slice_to=None, step=1):
    if slice_to is not None:
        slice_to += 1 if step > 0 else -1
    if slice_to == 0:
        slice_to = None
    return myList[slice_from:slice_to:step]
_

私が個人的に行うことは、あなたが言及した「完全な」ソリューション(range(A, B + 1)、_l[A:B+1]_)を使用し、コメントするだけです。

14
L3viathan

Pythonでは、終了インデックスは常に排他的であるため、「Python-convention」値を常に内部的に使用することを検討する価値があります。これにより、コード内で2つを混同するのを防ぐことができます。

専用の変換サブルーチンを介してのみ「外部表現」を処理します。

def text2range(text):
    m = re.match(r"from (\d+) to (\d+)",text)
    start,end = int(m.groups(1)),int(m.groups(2))+1

def range2text(start,end):
    print "from %d to %d"%(start,end-1)

あるいは、「異常な」表現を保持している変数を trueハンガリー語表記 でマークできます。

9
ivan_pozdeev

ステップサイズではなくステップ数を指定したくない場合は、開始点と終了点を含むnumpy.linspaceを使用するオプションがあります

import numpy as np

np.linspace(0,5,4)
# array([ 0.        ,  1.66666667,  3.33333333,  5.        ])
5
plonser

独自のクラスを作成せずに、関数を使用する方法があるようです。私が考えることができるのは、実際のリストを保存するのではなく、関心のある範囲のジェネレーターを返すだけです。使用構文について話しているので、ここでできることは

def closed_range(slices):
    slice_parts = slices.split(':')
    [start, stop, step] = map(int, slice_parts)
    num = start
    if start <= stop and step > 0:
        while num <= stop:
            yield num
            num += step
    # if negative step
    Elif step < 0:
        while num >= stop:
            yield num
            num += step

そして、次のように使用します:

list(closed_range('1:5:2'))
[1,3,5]

もちろん、他の誰かがこの関数を使用する場合は、他の形式の不正な入力もチェックする必要があります。

4
Rcynic

標準的な答えは、必要な場所で+1または-1を使用するだけだと思います。

スライスの理解方法をグローバルに変更したくない(多くのコードが壊れる)が、別の解決策は、スライスを含めたいオブジェクトのクラス階層を構築することです。たとえば、listの場合:

class InclusiveList(list):
    def __getitem__(self, index):
        if isinstance(index, slice):
            start, stop, step = index.start, index.stop, index.step
            if index.stop is not None:
                if index.step is None:
                    stop += 1
                else:
                    if index.step >= 0:
                        stop += 1
                    else:
                        if stop == 0: 
                            stop = None # going from [4:0:-1] to [4::-1] since [4:-1:-1] wouldn't work 
                        else:
                            stop -= 1
            return super().__getitem__(slice(start, stop, step))
        else:
            return super().__getitem__(index)

>>> a = InclusiveList([1, 2, 4, 8, 16, 32])
>>> a
[1, 2, 4, 8, 16, 32]
>>> a[4]
16
>>> a[2:4]
[4, 8, 16]
>>> a[3:0:-1]
[8, 4, 2, 1]
>>> a[3::-1]
[8, 4, 2, 1]
>>> a[5:1:-2]
[32, 8, 2]

もちろん、__setitem____delitem__でも同じことをしたいでしょう。

listを使用しましたが、それは Sequence またはMutableSequenceに対して機能します。)

4
Francis Colas

コメントするつもりでしたが、答えとしてコードを書く方が簡単です...

非常に明確でない限り、スライスを再定義するクラスは作成しません。ビットスライシングでintを表すクラスがあります。私の文脈では、「4:2」は非常に明確に包括的であり、intはスライスに使用されていないため、(ほとんど)受け入れられます(私見では、一部は同意しません)。

リストについては、次のようなことをする場合があります

list1 = [1,2,3,4,5]
list2 = InclusiveList([1,2,3,4,5])

以降のコードで

if list1[4:2] == test_list or list2[4:2] == test_list:

リストはすでに明確に定義された使用法を持っているため、それは非常に簡単な間違いです。それらは同じように見えますが、異なる動作をするため、特にこれを書いていない場合、デバッグが非常に混乱します。

それはあなたが完全に失われているという意味ではありません...スライスは便利ですが、結局のところ、それは単なる機能です。そして、あなたはそのような関数をこのようなものに追加することができるので、これはそれに到達するより簡単な方法かもしれません:

class inc_list(list):
    def islice(self, start, end=None, dir=None):
        return self.__getitem__(slice(start, end+1, dir))

l2 = inc_list([1,2,3,4,5])
l2[1:3]
[0x3,
 0x4]
l2.islice(1,3)
[0x3,
 0x4,
 0x5]

ただし、このソリューションは、他の多くのソリューションと同様に、(不完全であることを...私は知っていますが)単純なスライス表記ほど単純ではないという点でアキレス腱を持っています...リストとして渡すよりも少し簡単です議論、しかしまだ[4:2]より難しい。それを実現する唯一の方法は、何かにdifferentを渡すことです。これは異なる解釈が可能であるため、ユーザーは自分が何をしたかを読んで知ることができます。

1つの可能性...浮動小数点数。それらは異なっているので、あなたはそれらを見ることができます、そして、それらは「単純な」構文よりそれほど難しくありません。組み込みではないため、まだ「魔法」が関係していますが、構文糖に関しては悪くありません。

class inc_list(list):
    def __getitem__(self, x):
        if isinstance(x, slice):
            start, end, step = x.start, x.stop, x.step
            if step == None:
                step = 1
            if isinstance(end, float):
                end = int(end)
                end = end + step
                x = slice(start, end, step)
            return list.__getitem__(self, x)

l2 = inc_list([1,2,3,4,5])
l2[1:3]
[0x2,
 0x3]
l2[1:3.0]
[0x2,
 0x3,
 0x4]

3.0は、pythonプログラマー「ちょっと、何か異常なことが起こっている」と言うのに十分なはずです...必ずしもwhatが起こっているわけではありませんが、少なくとも「奇妙な」動作をすることは驚くことではありません。

リストに関してそれについてユニークなものは何もないことに注意してください...任意のクラスでこれを行うことができるデコレータを簡単に書くことができます:

def inc_getitem(self, x):
    if isinstance(x, slice):
        start, end, step = x.start, x.stop, x.step
        if step == None:
            step = 1
        if isinstance(end, float):
            end = int(end)
            end = end + step
            x = slice(start, end, step)
    return list.__getitem__(self, x)

def inclusiveclass(inclass):
    class newclass(inclass):
        __getitem__ = inc_getitem
    return newclass

ilist = inclusiveclass(list)

または

@inclusiveclass
class inclusivelist(list):
    pass

ただし、最初の形式の方がおそらく便利です。

3
Corley Brigman

このような基本概念をオーバーロードすることは難しく、おそらく賢明ではありません。混乱を招く可能性があるb-a + 1の新しい包含リストクラスlen(l [a:b])を使用します。
自然なpython感覚を維持しながら、BASICスタイルで読みやすくするために、次のように定義します。

STEP=FROM=lambda x:x
TO=lambda x:x+1 if x!=-1 else None 
DOWNTO=lambda x:x-1 if x!=0 else None

その後、自然なpythonロジックを維持しながら、必要に応じて管理できます。

>>>>l=list(range(FROM(0),TO(9)))
>>>>l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>l[FROM(9):DOWNTO(3):STEP(-2)] == l[9:2:-2]
True
3
B. M.

最適な構文の要求に焦点を当て、ターゲティングについてはどうですか:

l[1:UpThrough(5):2]

__index__ メソッド:

class UpThrough(object):
    def __init__(self, stop):
        self.stop = stop

    def __index__(self):
        return self.stop + 1

class DownThrough(object):
    def __init__(self, stop):
        self.stop = stop

    def __index__(self):
        return self.stop - 1

これで、特別なリストクラスさえ必要ありません(グローバル定義を変更する必要もありません):

>>> l = [1,2,3,4]
>>> l[1:UpThrough(2)]
[2,3]

たくさん使用する場合は、upIncldownIncl、またはInInRevのような短い名前を使用できます。

また、これらのクラスを構築して、スライスで使用する以外は、実際のインデックスのように機能するようにすることもできます。

def __int__(self):
    return self.stop
3
shaunc

従来型ではないAPIやリストなどのデータ型を拡張するAPIを作成する代わりに、Slice関数を組み込みsliceのラッパーとして作成して、任意のクラスに渡せるようにするのが理想的です。ここで、スライスが必要です。 Pythonは、いくつかの例外的なケースに対してこのアプローチをサポートし、その例外ケースを保証できるケースです。例として、包括的スライスは次のようになります

def islice(start, stop = None, step = None):
    if stop is not None: stop += 1
    if stop == 0: stop = None
    return slice(start, stop, step)

そして、あなたはそれのためにそれを使用することができます シーケンス型

>>> range(1,10)[islice(1,5)]
[2, 3, 4, 5, 6]
>>> "Hello World"[islice(0,5,2)]
'Hlo'
>>> (3,1,4,1,5,9,2,6)[islice(1,-2)]
(1, 4, 1, 5, 9, 2)

最後に、irangeと呼ばれる包括的範囲を作成して、包括的スライス(OPの行で記述)を補完することもできます。

def irange(start, stop, step):
    return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
3
Abhijit

これがすでにカバーされているかどうかはわかりませんが、変数が定義された範囲内にあるかどうかを確認するためにこれをどのように処理したかです:

my var=10 # want to check if it is in range(0,10) as inclusive
limits = range(0,10)
limits.append(limits[-1]+1)
if(my_var in limits):
    print("In Limit")
else:
    print("Out of Limit")

このコードは「In Limit」を返します。これは、範囲を1つ拡張し、包括的にしたためです。

0
Raghu