索引的pythonic格式

时间:2009-09-26 13:21:53

标签: python indexing set sequence

我正在使用字符串格式来有效地表示一组索引。 例如,“1-3,6,8-10,16”将产生[1,2,3,6,8,9,10,16]

理想情况下,我也能够代表无限的序列。

是否有现成的标准方法?还是一个好的图书馆?或者你可以提出自己的格式吗?

谢谢!

编辑:哇! - 感谢所有考虑周全的回复。我同意我应该使用':'代替。关于无限列表的任何想法?我想用“1 ..”代表所有正数。

用例是购物车。对于某些产品,我需要将产品销售限制为X的倍数,对于其他产品,我要将其限制为正数。所以我使用字符串格式在数据库中表示这一点。

5 个答案:

答案 0 :(得分: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, ...

如果你必须给它一个字符串,那么你可以组合@ilya n。使用此解决方案解析器。我会延长@ilya n。解析器以支持索引和范围:

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]

答案 1 :(得分:3)

如果您正在使用Pythonic,我认为1:3,6,8:10,16将是更好的选择,因为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(',')]

然而,有一个陷阱:根据定义,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的可读性较差。

答案 2 :(得分: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]

答案 3 :(得分: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“ - meh,破折号就我所关注的一样好。

答案 4 :(得分: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 部分。