6

私は、一連のインデックスを効率的に表すための文字列形式を求めています。たとえば、「1-3,6,8-10,16」は[1,2,3,6,8,9,10,16]を生成します。

理想的には、無限のシーケンスを表現することもできます。

これを行うための既存の標準的な方法はありますか?それとも良い図書館ですか?または、独自のフォーマットを提案できますか?

ありがとう!

編集:すごい!-よく考えられたすべての回答に感謝します。代わりに「:」を使用することに同意します。無限のリストについて何かアイデアはありますか?すべての正の数を表すために「1..」を使用することを考えていました。

ユースケースはショッピングカート用です。一部の製品では、製品の販売をXの倍数に制限する必要があります。その他の製品では、正の数に制限する必要があります。だから私はデータベースでこれを表すために文字列形式を求めています。

4

5 に答える 5

7

そのための文字列は必要ありません。これは可能な限り簡単です。

from types import SliceType

class sequence(object):
    def __getitem__(self, item):
        for a in item:
            if isinstance(a, SliceType):
                i = a.start
                step = a.step if a.step else 1
                while True:
                    if a.stop and i > a.stop:
                        break
                    yield i
                    i += step
            else:
                yield a

print list(sequence()[1:3,6,8:10,16])

出力:

[1, 2, 3, 6, 8, 9, 10, 16]

シーケンス範囲を表現するためにPythonスライスタイプのパワーを使用しています。また、メモリ効率を高めるためにジェネレータを使用しています。

スライスストップに1を追加していることに注意してください。そうしないと、スライスのストップが含まれていないため、範囲が異なります。

次の手順をサポートします。

>>> list(sequence()[1:3,6,8:20:2])
[1, 2, 3, 6, 8, 10, 12, 14, 16, 18, 20]

そして無限のシーケンス:

sequence()[1:3,6,8:]
1, 2, 3, 6, 8, 9, 10, ...

文字列を指定する必要がある場合は、@ilyanを組み合わせることができます。このソリューションのパーサー。@ilyanを拡張します。インデックスと範囲をサポートするパーサー:

def parser(input):
    ranges = [a.split('-') for a in input.split(',')]
    return [slice(*map(int, a)) if len(a) > 1 else int(a[0]) for a in ranges]

これで、次のように使用できます。

>>> print list(sequence()[parser('1-3,6,8-10,16')])
[1, 2, 3, 6, 8, 9, 10, 16]
于 2009-09-26T14:00:28.457 に答える
3

1:3,6,8:10,16Pythonのようなものに興味がある場合は、インデックス範囲の標準的な表記法と同様に、より良い選択だと思いますx:y。構文により、オブジェクトでこの表記法を使用できます。呼び出しに注意してください

z[1:3,6,8:10,16]

に翻訳されます

z.__getitem__((slice(1, 3, None), 6, slice(8, 10, None), 16))

これは組み込みのコンテナであるTypeError場合でもz、NumPyの配列など、妥当なものを返すクラスを自由に作成できます。

5:また、慣例により、無限のインデックス範囲を表すと言うこともでき:5ます(Pythonには、負または無限に大きい正のインデックスを持つ組み込み型がないため、これは少し拡張されています)。

slice(16, None, None)そして、これがパーサーです(以下で説明するグリッチに悩まされている美しいワンライナー):

def parse(s):
    return [slice(*map(int, x.split(':'))) for x in s.split(',')]

ただし、落とし穴が1つあります。8:10定義上、インデックス8と9のみが含まれ、上限はありません。それがあなたの目的に受け入れられないのであれば、あなたは確かに別のフォーマットが必要であり、1-3,6,8-10,16私には似合います。その場合、パーサーは次のようになります。

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

def parse(s):
    return [myslice(*map(int, x.split('-'))) for x in s.split(',')]

更新:結合された形式の完全なパーサーは次のとおりです。

from sys import maxsize as INF

def indices(s: 'string with indices list') -> 'indices generator':
    for x in s.split(','):
        splitter = ':' if (':' in x) or (x[0] == '-') else '-'
        ix = x.split(splitter)
        start = int(ix[0]) if ix[0] is not '' else -INF
        if len(ix) == 1:
            stop = start + 1
        else:
            stop = int(ix[1]) if ix[1] is not '' else INF
        step = int(ix[2]) if len(ix) > 2 else 1
        for y in range(start, stop + (splitter == '-'), step):
            yield y

これは負の数も処理するので、

 print(list(indices('-5, 1:3, 6, 8:15:2, 20-25, 18')))

プリント

[-5, 1, 2, 6, 7, 8, 10, 12, 14, 20, 21, 22, 23, 24, 25, 18, 19]

さらに別の方法は、使用すること...です(Pythonは組み込みの定数省略記号として認識されるため、必要に応じて呼び出すことができます)が、読みにくいz[...]と思います。1,...,3,6, 8,...,10,16

于 2009-09-26T13:48:36.193 に答える
2

これはおそらく可能な限り怠惰です。つまり、非常に大きなリストでも問題ありません。

def makerange(s):
    for nums in s.split(","): # whole list comma-delimited
        range_ = nums.split("-") # number might have a dash - if not, no big deal
        start = int(range_[0])
        for i in xrange(start, start + 1 if len(range_) == 1 else int(range_[1]) + 1):
            yield i

s = "1-3,6,8-10,16"
print list(makerange(s))

出力:

[1, 2, 3, 6, 8, 9, 10, 16]
于 2009-09-26T13:48:38.503 に答える
1

これは今朝私のコーヒーと一緒に行くのが楽しいパズルのように見えました。与えられた構文(最後にいくつかのメモがありますが、私には問題ないように見えます)に落ち着いたら、入力文字列を受け取り、整数のリストを返すpyparsingコンバーターを次に示します。

from pyparsing import *

integer = Word(nums).setParseAction(lambda t : int(t[0]))
intrange = integer("start") + '-' + integer("end")
def validateRange(tokens):
    if tokens.from_ > tokens.to:
        raise Exception("invalid range, start must be <= end")
intrange.setParseAction(validateRange)
intrange.addParseAction(lambda t: list(range(t.start, t.end+1)))

indices = delimitedList(intrange | integer)

def mergeRanges(tokens):
    ret = set()
    for item in tokens:
        if isinstance(item,int):
            ret.add(item)
        else:
            ret += set(item)
    return sorted(ret)

indices.setParseAction(mergeRanges)

test = "1-3,6,8-10,16"
print indices.parseString(test)

これにより、「3-8,4,6,3,4」などの重複または重複するエントリも処理され、一意の整数のみのリストが返されます。

パーサーは、「10-3」のような範囲が許可されていないことの検証を処理します。本当にこれを許可し、「1,5-3,7」のようなものが1,5,4,3,7を返すようにしたい場合は、intrangeおよびmergeRanges解析アクションを微調整して、この単純な結果を取得できます(そして破棄します) validateRange解析アクション全体)。

式に空白が含まれる可能性が非常に高くなりますが、これは重要ではないと思います。「1、2、3-6」は「1,2,3-6」と同じように扱われます。Pyparsingはデフォルトでこれを行うため、上記のコードには特別な空白の処理は表示されません(ただし、そこにあります...)

このパーサーは負のインデックスを処理しませんが、それも必要な場合は、整数の定義を次のように変更してください。

integer = Combine(Optional('-') + Word(nums)).setParseAction(lambda t : int(t[0]))

あなたの例にはネガがリストされていなかったので、今のところ省略しました。

Pythonは範囲区切り文字に「:」を使用するため、元の文字列は「1:3,6,8:10,16」のようになり、Pascalは配列範囲に「..」を使用して「1..3、 6,8..10,16 "-まあ、ダッシュは私に関する限り同じくらい良いです。

于 2009-09-26T13:50:38.513 に答える
1
import sys

class Sequencer(object):
    def __getitem__(self, items):
        if not isinstance(items, (tuple, list)):
            items = [items]
        for item in items:
            if isinstance(item, slice):
                for i in xrange(*item.indices(sys.maxint)):
                    yield i
            else:
                yield item


>>> s = Sequencer()
>>> print list(s[1:3,6,8:10,16])
[1, 2, 6, 8, 9, 16]

xrange組み込みを使用してシーケンスを生成していることに注意してください。デフォルトでは上位のシーケンスが含まれていないため、最初は厄介に思えますが、非常に便利であることがわかります。次のようなことができます。

>>> print list(s[1:10:3,5,5,16,13:5:-1])
[1, 4, 7, 5, 5, 16, 13, 12, 11, 10, 9, 8, 7, 6]

つまり、のstep一部を使用できますxrange

于 2009-09-26T19:28:50.470 に答える