将数字列表转换为字符串范围

Sco*_*t B 7 python sequences list range sequence

我想知道是否有这样做的这种相反的简单(或者已经创建)的方式: 生成联用编号列表....此链接可用于执行以下操作:

>> list(hyphen_range('1-9,12,15-20,23'))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 15, 16, 17, 18, 19, 20, 23]:
Run Code Online (Sandbox Code Playgroud)

我想做相反的事情(请注意,包括10和21,因此它将与范围函数兼容,其中范围(1,10)= [1,2,3,4,5,6,7,8 ,9]):

>> list_to_ranges([1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 15, 16, 17, 18, 19, 20, 23])
'1-10,12,15-21,23'
Run Code Online (Sandbox Code Playgroud)

最后,我想让输出还包含一个步骤,其中输出的最后一个数字表示步骤:

>> list_to_ranges([1, 3, 5, 7, 8, 9, 10, 11])
'1-13:2,8,10'
Run Code Online (Sandbox Code Playgroud)

从本质上讲,这最终会像"反向"范围函数

>> tmp = list_to_ranges([1, 3, 5])
>> print tmp
'1-7:2'
>> range(1, 7, 2)
[1, 3, 5]
Run Code Online (Sandbox Code Playgroud)

我的猜测是,没有真正简单/简单的方法可以做到这一点,但我想在我做一些蛮力,长方法之前我会问这里.

编辑

这篇文章的答案中的代码为例,我想出了一个简单的方法来完成第一部分.但我认为确定执行步骤的模式会有点困难.

from itertools import groupby
from operator import itemgetter

data = [ 1,  4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
print data, '\n'

str_list = []
for k, g in groupby(enumerate(data), lambda (i,x):i-x):
   ilist = map(itemgetter(1), g)
   print ilist
   if len(ilist) > 1:
      str_list.append('%d-%d' % (ilist[0], ilist[-1]+1))
   else:
      str_list.append('%d' % ilist[0])
print '\n', ','.join(str_list)
Run Code Online (Sandbox Code Playgroud)

编辑2

这是我尝试包括步长...它非常接近,但第一个数字重复.我认为通过稍微调整一下,它将接近我想要的 - 或者至少足够好.

import numpy as np
from itertools import groupby

def list_to_ranges(data):
   data = sorted(data)
   diff_data = np.diff(data).tolist()
   ranges = []
   i = 0
   for k, iterable in groupby(diff_data, None):
      rng = list(iterable)
      step = rng[0]
      if len(rng) == 1:
         ranges.append('%d' % data[i])
      elif step == 1:
         ranges.append('%d-%d' % (data[i], data[i+len(rng)]+step))
      else:
         ranges.append('%d-%d:%d' % (data[i], data[i+len(rng)]+step, step))
      i += len(rng)
   return ','.join(ranges)

data = [1, 3, 5, 6, 7, 11, 13, 15, 16, 17, 18, 19, 22, 25, 28]
print data
data_str = list_to_ranges(data)
print data_str

_list = []
for r in data_str.replace('-',':').split(','):
   r = [int(a) for a in r.split(':')]
   if len(r) == 1:
      _list.extend(r)
   elif len(r) == 2:
      _list.extend(range(r[0], r[1]))
   else:
      _list.extend(range(r[0], r[1], r[2]))
print _list
print list(set(_list))
Run Code Online (Sandbox Code Playgroud)

Rik*_*ggi 6

一种方法可能是逐段“吃”输入序列并存储部分范围结果,直到获得全部结果:

def formatter(start, end, step):
    return '{}-{}:{}'.format(start, end, step)
    # return '{}-{}:{}'.format(start, end + step, step)

def helper(lst):
    if len(lst) == 1:
        return str(lst[0]), []
    if len(lst) == 2:
        return ','.join(map(str,lst)), []

    step = lst[1] - lst[0]
    for i,x,y in zip(itertools.count(1), lst[1:], lst[2:]):
        if y-x != step:
            if i > 1:
                return formatter(lst[0], lst[i], step), lst[i+1:]
            else:
                return str(lst[0]), lst[1:]
    return formatter(lst[0], lst[-1], step), []

def re_range(lst):
    result = []
    while lst:
        partial,lst = helper(lst)
        result.append(partial)
    return ','.join(result)
Run Code Online (Sandbox Code Playgroud)

我用一堆单元测试对它进行了测试,它通过了所有测试,它也可以处理负数,但它们看起来有点难看(这确实是任何人的错)。

例子:

>>> re_range([1,  4,5,6, 10, 15,16,17,18, 22, 25,26,27,28])
'1,4-6:1,10,15-18:1,22,25-28:1'
>>> re_range([1, 3, 5, 7, 8, 9, 10, 11, 13, 15, 17])
'1-7:2,8-11:1,13-17:2'
Run Code Online (Sandbox Code Playgroud)

注意:我为 Python 3 编写了代码。


表现

我没有在上面的解决方案中投入任何性能努力。特别是,每次通过切片重新构建列表时,如果输入列表具有特定形状,则可能需要一些时间。因此,第一个简单的改进就是itertools.islice()尽可能使用。

无论如何,这是相同算法的另一个实现,它使用索引扫描输入列表scan而不是切片:

def re_range(lst):
    n = len(lst)
    result = []
    scan = 0
    while n - scan > 2:
        step = lst[scan + 1] - lst[scan]
        if lst[scan + 2] - lst[scan + 1] != step:
            result.append(str(lst[scan]))
            scan += 1
            continue

        for j in range(scan+2, n-1):
            if lst[j+1] - lst[j] != step:
                result.append(formatter(lst[scan], lst[j], step))
                scan = j+1
                break
        else:
            result.append(formatter(lst[scan], lst[-1], step))
            return ','.join(result)

    if n - scan == 1:
        result.append(str(lst[scan]))
    elif n - scan == 2:
        result.append(','.join(map(str, lst[scan:])))

    return ','.join(result)
Run Code Online (Sandbox Code Playgroud)

一旦它比之前的最佳解决方案快了约 65%,我就停止了研究,这似乎已经足够了:)

无论如何,我想说可能仍然有改进的空间(特别是在中间的 for 循环中)。


归档时间:

查看次数:

6330 次

最近记录:

8 年,6 月 前