List 将整数列表转换为逗号分隔范围字符串的Pythonic方法
我有一个整数列表,需要将其解析为一个范围字符串 例如:List 将整数列表转换为逗号分隔范围字符串的Pythonic方法,list,python,List,Python,我有一个整数列表,需要将其解析为一个范围字符串 例如: [0, 1, 2, 3] -> "0-3" [0, 1, 2, 4, 8] -> "0-2,4,8" 等等 我仍在学习更多处理列表的python方法,这对我来说有点困难。我最新的想法是创建一个列表,记录成对的数字: [ [0, 3], [4, 4], [5, 9], [20, 20] ] 然后我可以遍历这个结构,将每个子列表打印为一个范围或单个值 我不喜欢在两次迭代中这样做,但我似乎无法跟踪每次迭代中的每个数字。我的想法
[0, 1, 2, 3] -> "0-3"
[0, 1, 2, 4, 8] -> "0-2,4,8"
等等
我仍在学习更多处理列表的python方法,这对我来说有点困难。我最新的想法是创建一个列表,记录成对的数字:
[ [0, 3], [4, 4], [5, 9], [20, 20] ]
然后我可以遍历这个结构,将每个子列表打印为一个范围或单个值
我不喜欢在两次迭代中这样做,但我似乎无法跟踪每次迭代中的每个数字。我的想法是这样做:
这是我最近的一次尝试。它起作用了,但我并不完全满意;我一直在想有一个更优雅的解决方案,我完全不知道。我知道,字符串处理迭代并不是最好的——对我来说,这是一大早的事情:)
有没有一种简单的方法可以将其合并到单个迭代中?我还能做些什么来让它更像蟒蛇呢?这是否是蟒蛇的问题有待讨论。但它非常紧凑。真正的关键在于
Rangify()
函数。如果你想提高效率或搞勾股,还有改进的余地
def CreateRangeString(zones):
#assuming sorted and distinct
deltas = [a-b for a, b in zip(zones[1:], zones[:-1])]
deltas.append(-1)
def Rangify((b, p), (z, d)):
if p is not None:
if d == 1: return (b, p)
b.append('%d-%d'%(p,z))
return (b, None)
else:
if d == 1: return (b, z)
b.append(str(z))
return (b, None)
return ','.join(reduce(Rangify, zip(zones, deltas), ([], None))[0])
要描述参数,请执行以下操作:
是到下一个值的距离(灵感来源于此处等)delta
对这些参数进行缩减Rangify()
-底座或累加器b
-上一个启动范围p
-区域编号z
-deltad
,'。连接。这将删除第二个循环
def createRangeString(zones):
rangeIdx = 0
ranges = [[zones[0], zones[0]]]
for zone in list(zones):
if ranges[rangeIdx][1] in (zone, zone-1):
ranges[rangeIdx][1] = zone
else:
ranges.append([zone, zone])
rangeIdx += 1
return ','.join(
map(
lambda p: '%s-%s'%tuple(p) if p[0] != p[1] else str(p[0]),
ranges
)
)
尽管我更喜欢更通用的方法:
from itertools import groupby
# auxiliary functor to allow groupby to compare by adjacent elements.
class cmp_to_groupby_key(object):
def __init__(self, f):
self.f = f
self.uninitialized = True
def __call__(self, newv):
if self.uninitialized or not self.f(self.oldv, newv):
self.curkey = newv
self.uninitialized = False
self.oldv = newv
return self.curkey
# returns the first and last element of an iterable with O(1) memory.
def first_and_last(iterable):
first = next(iterable)
last = first
for i in iterable:
last = i
return (first, last)
# convert groups into list of range strings
def create_range_string_from_groups(groups):
for _, g in groups:
first, last = first_and_last(g)
if first != last:
yield "{0}-{1}".format(first, last)
else:
yield str(first)
def create_range_string(zones):
groups = groupby(zones, cmp_to_groupby_key(lambda a,b: b-a<=1))
return ','.join(create_range_string_from_groups(groups))
assert create_range_string([0,1,2,3]) == '0-3'
assert create_range_string([0, 1, 2, 4, 8]) == '0-2,4,8'
assert create_range_string([1,2,3,4,6,7,8,9,12,13,19,20,22,22,22,23,40,44]) == '1-4,6-9,12-13,19-20,22-23,40,44'
从itertools导入groupby
#允许groupby按相邻元素进行比较的辅助函子。
按键(对象)将cmp\u类设置为\u组:
定义初始化(self,f):
self.f=f
self.uninitialized=True
定义呼叫(self,newv):
如果self.uninitialized或非self.f(self.oldv,newv):
self.curkey=newv
self.uninitialized=False
self.oldv=newv
返回self.curkey
#返回具有O(1)内存的iterable的第一个和最后一个元素。
def first_和def last(可选项):
第一个=下一个(iterable)
最后一个=第一个
因为我在iterable:
last=i
返回(第一个,最后一个)
#将组转换为范围字符串列表
def从组创建范围字符串(组):
对于分组中的g:
first,last=first_和_last(g)
如果第一最后:
产生“{0}-{1}”。格式(第一,最后)
其他:
产量(第一)
def创建范围字符串(区域):
groups=groupby(区域,cmp_to_groupby_键(lambda a,b:b-a)这是我的解决方案。在遍历列表并创建结果时,您需要跟踪各种信息-这会向我发出尖叫。因此:
def rangeStr(start, end):
'''convert two integers into a range start-end, or a single value if they are the same'''
return str(start) if start == end else "%s-%s" %(start, end)
def makeRange(seq):
'''take a sequence of ints and return a sequence
of strings with the ranges
'''
# make sure that seq is an iterator
seq = iter(seq)
start = seq.next()
current = start
for val in seq:
current += 1
if val != current:
yield rangeStr(start, current-1)
start = current = val
# make sure the last range is included in the output
yield rangeStr(start, current)
def stringifyRanges(seq):
return ','.join(makeRange(seq))
>>> l = [1,2,3, 7,8,9, 11, 20,21,22,23]
>>> l2 = [1,2,3, 7,8,9, 11, 20,21,22,23, 30]
>>> stringifyRanges(l)
'1-3,7-9,11,20-23'
>>> stringifyRanges(l2)
'1-3,7-9,11,20-23,30'
如果给我一个空列表,我的版本会正常工作,而我认为其他一些版本不会
>>> stringifyRanges( [] )
''
makeRanges将在任何返回整数并延迟返回字符串序列的迭代器上工作,因此可以在无限序列上使用
编辑:我已更新代码以处理不属于某个范围的单个数字
edit2:重构rangeStr以删除重复
def createRangeString(zones):
"""Create a string with integer ranges in the format of '%d-%d'
>>> createRangeString([0, 1, 2, 4, 8])
"0-2,4,8"
>>> createRangeString([1,2,3,4,6,7,8,9,12,13,19,20,22,22,22,23,40,44])
"1-4,6-9,12-13,19-20,22-23,40,44"
"""
buffer = []
try:
st = ed = zones[0]
for i in zones[1:]:
delta = i - ed
if delta == 1: ed = i
elif not (delta == 0):
buffer.append((st, ed))
st = ed = i
else: buffer.append((st, ed))
except IndexError:
pass
return ','.join(
"%d" % st if st==ed else "%d-%d" % (st, ed)
for st, ed in buffer)
这里的想法是将每个元素与count()配对。然后,对于连续的值,value和count()之间的差异是恒定的。groupby()完成其余的工作
正如Jeff所建议的,使用count()
的替代方法是使用enumerate()
。这增加了一些需要在print语句中去掉的额外杂质
G=(list(x) for _,x in groupby(enumerate(L), lambda (i,x):i-x))
print ",".join("-".join(map(str,(g[0][1],g[-1][1])[:len(g)])) for g in G)
更新:对于此处给出的示例列表,使用enumerate的版本运行速度比在我的计算机上使用count()的版本慢约5%这更详细,主要是因为我使用了我拥有的通用函数,这些函数是itertools函数和配方的微小变化:
from itertools import tee, izip_longest
def pairwise_longest(iterable):
"variation of pairwise in http://docs.python.org/library/itertools.html#recipes"
a, b = tee(iterable)
next(b, None)
return izip_longest(a, b)
def takeuntil(predicate, iterable):
"""returns all elements before and including the one for which the predicate is true
variation of http://docs.python.org/library/itertools.html#itertools.takewhile"""
for x in iterable:
yield x
if predicate(x):
break
def get_range(it):
"gets a range from a pairwise iterator"
rng = list(takeuntil(lambda (a,b): (b is None) or (b-a>1), it))
if rng:
b, e = rng[0][0], rng[-1][0]
return "%d-%d" % (b,e) if b != e else "%d" % b
def create_ranges(zones):
it = pairwise_longest(zones)
return ",".join(iter(lambda:get_range(it),None))
k=[0,1,2,4,5,7,9,12,13,14,15]
print create_ranges(k) #0-2,4-5,7,9,12-15
这个烂摊子怎么样
def rangefy(mylist):
mylist, mystr, start = mylist + [None], "", 0
for i, v in enumerate(mylist[:-1]):
if mylist[i+1] != v + 1:
mystr += ["%d,"%v,"%d-%d,"%(start,v)][start!=v]
start = mylist[i+1]
return mystr[:-1]
OP确实指定了当列表中出现单个数字时会发生什么情况。`[0,1,2,4,8]->“0-2,4,8”`。谢谢Kenny,我错过了。我已更新了代码以正确处理单个数字。请尝试使用L=[1,2,3,4,7,9,11,13,14,15]。如果我理解正确,它可能会崩溃。我可以收回吗?绝妙的解决方案,我向你致敬。@Muhammad,当然:)看看更新的版本-它不再需要izip
@gnibler,这看起来很神奇。我喜欢你在打印语句中使用map。我想我没有遇到下一个()方法。你能给它传递任何iterable项目吗?@gnibler:令人印象深刻。在我把它挂在墙上之前,你能用G=(列表(x)代表ux,x in.
吗?这些天我在研究itertools,寻找这样的宝石。你可以假设不同,但不一定排序。因为列表是数字的,但是排序()方法使解决该假设变得相当简单。实际上,我收回了这一点-将列表推送到函数中的代码执行预排序。没关系:)有没有人知道如何使用join重写,或者如何避免None求和,或者如何缩短上述代码?请参阅more\u itertools.continuous\u groups
工具的演示。
from itertools import tee, izip_longest
def pairwise_longest(iterable):
"variation of pairwise in http://docs.python.org/library/itertools.html#recipes"
a, b = tee(iterable)
next(b, None)
return izip_longest(a, b)
def takeuntil(predicate, iterable):
"""returns all elements before and including the one for which the predicate is true
variation of http://docs.python.org/library/itertools.html#itertools.takewhile"""
for x in iterable:
yield x
if predicate(x):
break
def get_range(it):
"gets a range from a pairwise iterator"
rng = list(takeuntil(lambda (a,b): (b is None) or (b-a>1), it))
if rng:
b, e = rng[0][0], rng[-1][0]
return "%d-%d" % (b,e) if b != e else "%d" % b
def create_ranges(zones):
it = pairwise_longest(zones)
return ",".join(iter(lambda:get_range(it),None))
k=[0,1,2,4,5,7,9,12,13,14,15]
print create_ranges(k) #0-2,4-5,7,9,12-15
def rangefy(mylist):
mylist, mystr, start = mylist + [None], "", 0
for i, v in enumerate(mylist[:-1]):
if mylist[i+1] != v + 1:
mystr += ["%d,"%v,"%d-%d,"%(start,v)][start!=v]
start = mylist[i+1]
return mystr[:-1]