我需要将int列表转换为包含列表中所有范围的字符串。 例如,输出应如下所示:
MouseListener
因此输入未排序,可能存在重复值。列表的大小范围从一个元素到4k个元素。最小值和最大值分别为1和4094。
这是性能关键代码的一部分。我一直在努力优化这一点,但我找不到一种方法来加快速度。这是我目前的代码:
getIntRangesFromList([1,3,7,2,11,8,9,11,12,15]) -> "1-3,7-9,11-12,15"
有关如何加快速度的想法吗?
答案 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包含至少两个数字。如果列表已排序,则会节省大量时间。