我有一系列索引列表,例如,
a = [
[2],
[0, 1, 3, 2],
[1],
[0, 3]
]
我现在想要"反转"此列表:数字0
显示在索引1
和3
,因此:
b = [
[1, 3],
[1, 2],
[0, 1],
[1, 3]
]
有关如何快速完成此操作的任何提示? (我处理的清单可能很大。)
奖金:我知道每个索引在a
中都会出现两次(就像上面的例子一样)。
答案 0 :(得分:5)
使用字典收集倒排索引,使用enumerate()
生成a
条目的索引:
inverted = {}
for index, numbers in enumerate(a):
for number in numbers:
inverted.setdefault(number, []).append(index)
b = [inverted.get(i, []) for i in range(max(inverted) + 1)]
字典为您提供了有效的随机访问以添加反转,但这确实意味着您需要考虑反转中可能缺少的索引,因此range(max(inverted))
循环以确保0和之间的所有索引覆盖最大值。
演示:
>>> a = [
... [2],
... [0, 1, 3, 2],
... [1],
... [0, 3]
... ]
>>> inverted = {}
>>> for index, numbers in enumerate(a):
... for number in numbers:
... inverted.setdefault(number, []).append(index)
...
>>> [inverted.get(i, []) for i in range(max(inverted) + 1)]
[[1, 3], [1, 2], [0, 1], [1, 3]]
答案 1 :(得分:5)
此代码不依赖于每个数字恰好出现两次的事实。它也非常简单,避免了构建字典然后从那里复制结果的开销:
a = [
[2],
[0, 1, 3, 2],
[1],
[0, 3]
]
b = []
for i, nums in enumerate(a):
# For each number found at this index
for num in nums:
# If needed, extend b to cover the new needed range
b += [[] for _ in range(num + 1 - len(b)]
# Store the index
b[num].append(i)
print(b)
# Output:
# [[1, 3], [1, 2], [0, 1], [1, 3]]
答案 2 :(得分:1)
假设每个索引只出现两次,则以下代码有效:
from itertools import chain
a = [[2],
[0, 1, 3, 2],
[1],
[0, 3]]
b = (max(chain(*a)) + 1) * [None]
for i, lst in enumerate(a):
for j in lst:
if not b[j]:
b[j] = [i, None]
else:
b[j][1] = i
正如@smarx所指出的,如果我们进一步假设len(a)
表示值的范围,如示例中所示,上述解决方案可以简化为:
a = [[2],
[0, 1, 3, 2],
[1],
[0, 3]]
b = len(a) * [[None]]
for i, lst in enumerate(a):
for j in lst:
if not b[j]:
b[j] = [i, None]
else:
b[j][1] = i
编辑: 解决方案的比较。
使用append
对于大型数组来说并不是最佳选择,因为它会重新分配内存。因此,在数组a
上循环两次可能会更快。
为了测试它,我创建了一个函数gen_list
,它根据问题的假设生成一个列表。代码如下:
# This answer's solution
def solution1(a):
from itertools import chain
b = (max(chain(*a)) + 1)* [None]
for i, lst in enumerate(a):
for j in lst:
if not b[j]:
b[j] = [i, None]
else:
b[j][1] = i
return b
# smarx's solution
def solution2(a):
b = []
for i, nums in enumerate(a):
# For each number found at this index
for num in nums:
# If needed, extend b to cover the new needed range
for _ in range(num + 1 - len(b)):
b.append([])
# Store the index
b[num].append(i)
return b
# Martijn Pieters's solution
def solution3(a):
inverted = {}
for index, numbers in enumerate(a):
for number in numbers:
inverted.setdefault(number, []).append(index)
return [inverted.get(i, []) for i in range(max(inverted) + 1)]
# eugene y's solution
def solution4(a):
b = []
for i, lst in enumerate(a):
for j in lst:
if j >= len(b):
b += [[] for _ in range(j - len(b) + 1)]
b[j].append(i)
def gen_list(n):
from numpy.random import choice
lst = []
for _ in range(n):
lst.append([])
for i in range(n):
lst[choice(n)].append(i)
lst[choice(n)].append(i)
return lst
然后,测试解决方案的速度产生:
In [1]: a = gen_list(10)
In [2]: %timeit solution1(a)
The slowest run took 8.68 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 9.45 µs per loop
In [3]: %timeit solution2(a)
The slowest run took 4.88 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 14.5 µs per loop
In [4]: %timeit solution3(a)
100000 loops, best of 3: 12.2 µs per loop
In [5]: %timeit solution4(a)
The slowest run took 5.69 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 10.3 µs per loop
In [6]: a = gen_list(100)
In [7]: %timeit solution1(a)
10000 loops, best of 3: 70.5 µs per loop
In [8]: %timeit solution2(a)
10000 loops, best of 3: 135 µs per loop
In [9]: %timeit solution3(a)
The slowest run took 5.28 times longer than the fastest. This could mean that an intermediate result is being cached
10000 loops, best of 3: 115 µs per loop
In [10]: %timeit solution4(a)
The slowest run took 6.75 times longer than the fastest. This could mean that an intermediate result is being cached
10000 loops, best of 3: 76.6 µs per loop
答案 3 :(得分:1)
这是一个非常简单的O(n)
解决方案,仅使用列表,并且还包含:
a
中出现两次的事实。a
。
a = [[2], [0, 1, 3, 2], [1], [0, 3]]
b = []
for i, lst in enumerate(a):
for j in lst:
if j >= len(b):
# extend b to accomodate for the new index
b += [[] for _ in range(j - len(b) + 1)]
b[j].append(i)
print(b) # [[1, 3], [1, 2], [0, 1], [1, 3]]
答案 4 :(得分:1)
这应该有效:
import itertools
b = [[] for _ in range(1 + max(itertools.chain.from_iterable(a)))]
for i, lst in enumerate(a):
for j in lst:
if i not in b[j]:
b[j].append(i)
请注意,上面的代码并不假设a
中可以显示的值的范围是range(len(a))
。为避免b
子列表中的重复值,我会在追加之前检查:if i not in b[j]:
答案 5 :(得分:1)
>>> a = [[2], [0, 1, 3, 2], [1], [0, 3]]
>>> b = [[] for _ in range(sum(map(len, a)) / 2)]
>>> for u, edges in enumerate(a):
for edge in edges:
b[edge].append(u)
>>> b
[[1, 3], [1, 2], [0, 1], [1, 3]]
答案 6 :(得分:0)
与smarx's答案基本相同。除此之外,在您继续操作时删除原始数组中的项目,使算法在内存上更有效(取决于垃圾收集器的实现方式)
a = [
[2],
[0, 1, 3, 2],
[1],
[0, 3]
]
b = []
# iterate over list a in reverse so that we can delete item when we
# are finished with them
for i in range(len(a)-1, -1, -1):
nums = a[i]
# For each number found at this index
for num in nums:
# If needed, extend b to cover the new needed range
for _ in range(num + 1 - len(b)):
b.append([])
# Store the index
b[num].append(i)
# delete this inner list, we are done with it now
del a[i]
print(b)
# Output:
# [[3, 1], [2, 1], [1, 0], [3, 1]]
请注意内部列表的顺序是相反的。