簡體   English   中英

如果列表項在另一個列表中,如何在保持順序的情況下將它們移動到前面

[英]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中的值都將在后面結束。 這當然假設,您希望xorxor順序進行排序(使過程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_by4order_by1schwobaseggl ,每個在我的計算機上大約需要 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM