我正在使用字符串格式来有效地表示一组索引。 例如,“1-3,6,8-10,16”将产生[1,2,3,6,8,9,10,16]
理想情况下,我也能够代表无限的序列。
是否有现成的标准方法?还是一个好的图书馆?或者你可以提出自己的格式吗?
谢谢!
编辑:哇! - 感谢所有考虑周全的回复。我同意我应该使用':'代替。关于无限列表的任何想法?我想用“1 ..”代表所有正数。用例是购物车。对于某些产品,我需要将产品销售限制为X的倍数,对于其他产品,我要将其限制为正数。所以我使用字符串格式在数据库中表示这一点。
答案 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
部分。