整数列表到范围

时间:2016-08-23 16:18:43

标签: python algorithm python-2.7

我需要将int列表转换为包含列表中所有范围的字符串。 例如,输出应如下所示:

MouseListener

因此输入未排序,可能存在重复值。列表的大小范围从一个元素到4k个元素。最小值和最大值分别为1和4094。

这是性能关键代码的一部分。我一直在努力优化这一点,但我找不到一种方法来加快速度。这是我目前的代码:

getIntRangesFromList([1,3,7,2,11,8,9,11,12,15]) -> "1-3,7-9,11-12,15"

有关如何加快速度的想法吗?

4 个答案:

答案 0 :(得分:1)

这可能是itertools模块的任务。

import itertools

list_num = [1, 2, 3, 7, 8, 9, 11, 12, 15]
groups = (list(x) for _, x in
          itertools.groupby(list_num, lambda x, c=itertools.count(): x - next(c)))
print(', '.join('-'.join(map(str, (item[0], item[-1])[:len(item)])) for item in groups))

这将为您提供1-3, 7-9, 11-12, 15

要了解发生了什么,您可能需要查看groups的内容。

import itertools
list_num = [1, 2, 3, 7, 8, 9, 11, 12, 15]

groups = (list(x) for _, x in
          itertools.groupby(list_num, lambda x, c=itertools.count(): x - next(c)))
for element in groups:
    print('element={}'.format(element))

这将为您提供以下输出。

element=[1, 2, 3]
element=[7, 8, 9]
element=[11, 12]
element=[15]

基本思路是让计数器与数字并行运行。 groupby将为数字创建单独的组,其数字距离与计数器的当前值相同。

我不知道你的Python版本是否更快。你必须自己检查一下。在我的设置中,使用此数据集时速度较慢,但​​元素数量较多时速度较快。

答案 1 :(得分:0)

def _to_range(l, start, stop, idx, result):
    if idx == len(l):
        result.append((start, stop))
        return result
    if l[idx] - stop > 1:
        result.append((start, stop))
        return _to_range(l, l[idx], l[idx], idx + 1, result)
    return _to_range(l, start, l[idx], idx + 1, result)

def get_range(l):
    if not l:
        return []
    return _to_range(l, start = l[0], stop = l[0], idx = 0, result = [])

l = [1, 2, 3, 7, 8, 9, 11, 12, 15]
result = get_range(l)
print(result) 
>>> [(1, 3), (7, 9), (11, 12), (15, 15)]
# I think it's better to fetch the data as it is and if needed, change it 
# with
print(','.join('-'.join([str(start), str(stop)]) for start, stop in result))
>>> 1-3,7-9,11-12,15-15

除非你根本不关心数据,否则你可以在_to_range函数中附加str(start)+' - '+ str(stop),这样以后就不需要输入额外的' - '了。加入方法。

答案 2 :(得分:0)

我将专注于您的主要问题。我将给出2个解决方案:

1)如果存储的整数的边界在A和B之间,你可以创建一个布尔数组(即使你可以选择一个位数组来扩展你可以存储的范围)(B - A + 2) )元素,例如A = 0和B = 1 000 000,我们可以这样做(我将用C#写,抱歉XD)。这在O(A - B)中运行,如果A - B小于数字,这是一个很好的解决方案:

public string getIntRangesFromList(int[] numbers)
    {
        //You can change this 2 constants
        const int A = 0;    
        const int B = 1000000;

        //Create an array with all its values in false by default
        //Last value always will be in false in propourse, as you can see it storage 1 value more than needed for 2nd cycle 
        bool[] apparitions = new bool[B - A + 2];
        int minNumber = B + 1;
        int maxNumber = A - 1;
        int pos;
        for (int i = 0; i < numbers.Length; i++)
        {
            pos = numbers[i] - A;
            apparitions[pos] = true;

            if (minNumber > pos)
            {
                minNumber = pos;
            }
            if (maxNumber < pos)
            {
                maxNumber = pos;
            }
        }

        //I will mantain the concatenation simple, but you can make it faster to improve performance
        string result = "";
        bool isInRange = false;
        bool isFirstRange = true;
        int firstPosOfRange = 0; //Irrelevant what is its initial value
        for (int i = minNumber; i <= maxNumber + 1; i++)
        {
            if (!isInRange)
            {
                if (apparitions[i])
                {
                    if (!isFirstRange)
                    {
                        result += ",";
                    }
                    else
                    {
                        isFirstRange = false;
                    }

                    result += (i + A);
                    isInRange = true;
                    firstPosOfRange = i;
                }
            }
            else
            {
                if (!apparitions[i])
                {
                    if (i > firstPosOfRange + 1)
                    {
                        result += "-" + (i + A - 1);
                    }
                    isInRange = false;
                }
            }
        }

        return result;
    }

2)O(N * log N)

    public string getIntRangesFromList2(int[] numbers)
    {
        string result = "";

        if (numbers.Length > 0)
        {
            numbers.OrderBy(x => x); //sorting and making the algorithm complexity O(N * log N)
            result += numbers[0];
            int countNumbersInRange = 1;
            for (int i = 1; i < numbers.Length; i++)
            {
                if (numbers[i] != numbers[i - 1] + 1)
                {
                    if (countNumbersInRange > 1)
                    {
                        result += "-" + numbers[i - 1];
                    }

                    result += "," + numbers[i];
                    countNumbersInRange = 1;
                }
                else
                {
                    countNumbersInRange++;
                }
            }
        }

        return result;
    }

答案 3 :(得分:0)

我能拿到的最快的一个,测试速度比我机器上的解决方案快10%(根据时间):

def _ranges(l):
  if l:
    l.sort()
    return ''.join([(str(l[i]) + ('-' if l[i] + 1 == l[i + 1] else ',')) 
                    for i in range(0, len(l) - 1) if l[i - 1] + 2 != l[i + 1]] +
                   [str(l[-1])])
  else: return ''

以上代码假定列表中的值是唯一的。如果它们不是,它很容易修复,但是有一个微妙的黑客将不再起作用,最终结果会稍微慢一点。

我实际上因为排序而计时_ranges(u[:]); u是来自范围(1000)的600个随机选择的整数,包括235个子序列; 83是单身,152包含至少两个数字。如果列表已排序,则会节省大量时间。