![](/img/trans.png)
[英]Python - reordering items in list by moving some items to the front while keeping the rest in the same order
[英]How to move list items to the front if they are in another list while keeping the order
我有兩個示例列表,
vals = ["a", "c", "d", "e", "f", "g"]
xor = ["c", "g"]
我想根據xor
列表對vals
列表進行排序,即xor
中的值應按確切順序放在vals
列表中的第一位。 vals
中存在的值的 rest 應保持相同的順序。
此外,在這些情況下, xor
中的值可能不在vals
中,只是忽略這些值。 而且,在重復的情況下,我只需要一個值。
所需的 output:
vals = ["c", "g", "a", "d", "e", "f"]
# here a, d, e, f are not in xor so we keep them in same order as found in vals.
我的方法:
new_list = []
for x in vals:
for y in xor:
if x == y:
new_list.append(x)
for x in vals:
if x not in xor:
new_list.append(x)
vals
列表目前有大約 80 萬個單詞或短語。 xor
列表有 300k 個單詞或短語,但以后可能會增加。 有些短語也有點長。 解決問題的最有效方法是什么?
在xor
中構建一個 indeces 的 order dict 並將其用作排序鍵:
order = {n: i for i, n in enumerate(xor)}
sorted(vals, key=lambda x: order.get(x, len(xor)))
# ['c', 'g', 'a', 'd', 'e', 'f']
使用len(vals)
作為默認值可確保所有不在xor
中的值都將在后面結束。 這當然假設,您希望xor
中xor
順序進行排序(使過程O(M+NlogN)
)。 否則,您可以更快( O(M+N)
):
from operator import contains
from functools import partial
s = set(xor)
result = list(filter(partial(contains, s), vals))
result.extend(v for v in vals if v not in s)
或者以更易讀的方式:
s = set(xor)
result = [v for v in vals if v in s]
result += (v for v in vals if v not in s)
Append 來自xor
的所有值也存在於vals
中,以及xor
上不存在的所有vals
值的列表:
sorted_list = [v for v in xor if v in vals] + [v for v in vals if v not in xor]
另一種方法:
output = list(filter(lambda x: x in vals, xor)) + list(filter(lambda x: x not in xor, vals))
鑒於添加到問題中的說明,此處發布的一些答案實際上並未提供預期的答案,而其他答案則效率不高。
一種解決方案是采用@dreamcrash 的答案,但使用集合進行查找:
def order_by1(vals, xor):
set_vals = set(vals)
set_xor = set(xor)
return [v for v in xor if v in set_vals] + [v for v in vals if v not in set_xor]
這應該通過在循環的每次迭代中消除耗時的( O(n) )列表查找來提高效率。
由於您不關心在xor
中找不到的值的順序,因此一種變體是用集合操作替換第二個列表推導:
def order_by2(vals, xor):
set_vals = set(vals)
return [v for v in xor if v in set_vals] + list(set_vals - set(xor))
另一種解決方案是使用有序集
def order_by3(vals, xor):
vals_set = OrderedSet(vals)
return (OrderedSet(xor) & vals_set) | vals_set
有序集合在引擎蓋下使用字典。 我們可以直接使用字典,利用它們是有序的事實:
def order_by4(vals, xor):
""" Order list a by each item's position in list xor, if it is found in xor,
otherwise by its position in list vals """
d = {k: False for k in xor}
for k in vals:
d[k] = True
return [k for k, v in d.items() if v]
實際執行所需操作的唯一其他解決方案是@schwobaseggl 答案中的第一個解決方案。 用 800,000 和 300,000 個隨機短語的列表來計時,其中 150,000 個重疊:
import random
import string
import timeit
def random_phrase():
return "".join(random.choices(string.ascii_letters, k=10))
def generate_lists(M, N, overlap):
a = [random_phrase() for _ in range(M)]
b = ([random_phrase() for _ in range(N - overlap)] +
random.sample(a, k=overlap))
random.shuffle(b)
return a, b
def dreamcrash(vals, xor):
return [v for v in xor if v in vals] + [v for v in vals if v not in xor]
def schwobaseggl(vals, xor):
order = {n: i for i, n in enumerate(xor)}
len_xor = len(xor) # saves some time when sorting
return sorted(vals, key=lambda x: order.get(x, len_xor))
vals, xor = generate_lists(800000, 300000, overlap=150000) # this takes a while
for f in [dreamcrash, order_by1, order_by2, order_by3, order_by4, schwobaseggl]:
print(f, end='...')
print(timeit.timeit(stmt=f"{f.__name__}(vals, xor)", number=1, globals=globals()))
我放棄了計時dreamcrash
,因為它花費了太長時間。 order_by2
似乎最快,其次是order_by4
、 order_by1
和schwobaseggl
,每個在我的計算機上大約需要 5 - 1.5 秒。 有序集解決方案要慢得多。 檢查一個集合是否包含一個項目,並在字典中設置一個項目,在平均情況下都是O(1)
,在最壞的情況下是O(n)
,這解釋了為什么基於字典和集合的版本具有相似的性能。
這也應該是您的一條線解決方案:
vals = [1, 2, 5, 4, 3, 2, 11, 6]
xor = [10, 11]
new_list = xor + [elem for elem in vals if elem not in xor]
(編輯:沒有排序和正確的變量)
排序的另一種方法:
vals.sort(key=set(xor).__contains__, reverse=True)
我懷疑它比其他更快,但不想嘗試創建可能類似於您的實際數據的測試數據。 (部分是因為您的問題仍然不完全清楚。我將使用您的參考實現所做的事情,即兩組都在vals
中保留其順序。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.