索引的pythonic格式

索引的pythonic格式,python,indexing,set,sequence,Python,Indexing,Set,Sequence,我在寻找一种字符串格式来有效地表示一组索引。 例如,“1-3,6,8-10,16”将产生[1,2,3,6,8,9,10,16] 理想情况下,我也能够表示无限序列 是否有一个现有的标准方法来实现这一点?还是一个好的图书馆?或者你能提出你自己的格式吗 谢谢 编辑:哇感谢所有经过深思熟虑的回复。我同意我应该用“:”来代替。关于无限列表有什么想法吗?我想用“1..”来表示所有的正数 该用例用于购物车。对于某些产品,我需要将产品销售额限制为X的倍数,对于其他产品,我需要限制为正数。所以我想用字符串格式在数

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

理想情况下,我也能够表示无限序列

是否有一个现有的标准方法来实现这一点?还是一个好的图书馆?或者你能提出你自己的格式吗

谢谢

编辑:哇感谢所有经过深思熟虑的回复。我同意我应该用“:”来代替。关于无限列表有什么想法吗?我想用“1..”来表示所有的正数


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

如果你喜欢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)
故障):

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

这可能是最懒惰的做法,这意味着即使是非常大的列表也可以:

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]

今天早上我喝咖啡的时候,这看起来像是一个有趣的拼图游戏。如果您选择给定的语法(在我看来还可以,最后还有一些注释),下面是一个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)
你的例子没有列出任何负面因素,所以我暂时不提了


Python使用“:”作为范围分隔符,因此您的原始字符串可能看起来像“1:3,6,8:10,16”,Pascal使用“..”作为数组范围,给出“1..3,6,8..10,16”-meh,破折号就我而言也一样好。

您不需要字符串,这很简单:

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切片类型power来表示序列范围。我还使用生成器来节省内存

请注意,我将向切片停止添加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]
请注意,我正在使用
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]

这意味着您可以使用
xrange

步骤
部分,这取决于您想对该结构做什么。。。您有什么约束条件?它必须是人类可读的字符串吗?您的问题陈述中存在一些歧义:1。是否应保留或折叠副本?2.结果是否应按原始字符串中指定的顺序进行排序或返回?3.“3-1”是否有效,并转换为[3,2,1],还是不允许?4.是否允许出现负指示?这是因为解决方案工作不正常。在OP示例中尝试它,它会产生错误的范围:[切片(1,4,无),切片(6,无,无),切片(8,11,无),切片(16,无,无)],因为切片(16,无,无)=16,17,18,…=16尽管我喜欢这种方法,但它可以与我的解决方案结合起来产生最终结果:)的确,它产生了
切片(16,无,无)
,但您仍然可以在程序中将其解释为简单的16!不管怎样,我发布了一个组合解析器,它可以正确地生成索引。太好了,这也很有效。但是我认为最好不要改变既定的惯例,即
1:3
不包括3——对于习惯Python的人来说,事情会变得非常混乱。
>>> print list(sequence()[parser('1-3,6,8-10,16')])
[1, 2, 3, 6, 8, 9, 10, 16]
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]
>>> 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]