簡體   English   中英

比較namedtuple列表中的幾個(但不是全部)元素

[英]Compare several (but not all) elements in a list of namedtuples

我有一個很長的namedtuple列表(目前它可以達到10.000行,但將來可能會更多)。

我需要將每個namedtuple的幾個元素與列表中的所有其他namedtuple進行比較。 我正在尋找一種有效且通用的方法

為了簡單起見,我將用蛋糕做一個比喻,這應該使理解問題更加容易。

有一個namedtuple列表,其中每個namedtuple是一塊蛋糕:

Cake = namedtuple('Cake', 
                       ['cake_id',
                        'ingredient1', 'ingredient2', 'ingredient3',
                        'baking_time', 'cake_price']
                 )

cake_pricebaking_time都很重要。 如果蛋糕的成分相同,我想從列表中刪除不相關的那些。 因此,任何蛋糕(具有相同的成分)都是相同的或更昂貴的,並且烘烤相同或更長的時間都沒有關系(下面有一個詳細的示例)。

最好的方法是什么?


途徑

到目前為止,我所做的是按cake_pricebaking_time對named_tuples列表進行排序:

sorted_cakes = sorted(list_of_cakes, key=lambda c: (c.cake_price, c.baking_time))

然后創建一個新列表,我要添加所有蛋糕,只要以前添加的蛋糕沒有相同的成分,則價格便宜且烘焙速度更快。

list_of_good_cakes = []
    for cake in sorted_cakes:
        if interesting_cake(cake, list_of_good_cakes):
            list_of_good_cakes.append(cake)

def interesting_cake(current_cake, list_of_good_cakes):
    is_interesting = True
    if list_of_good_cakes: #first cake to be directly appended
        for included_cake in list_of_good_cakes:
            if (current_cake.ingredient1 == included_cake.ingredient1 and
                current_cake.ingredient2 == included_cake.ingredient2 and
                current_cake.ingredient3 == included_cake.ingredient3 and
                current_cake.baking_time >= included_cake.baking_time):

                if current_cake.cake_price >= included_cake.cake_price:
                    is_interesting = False

    return is_interesting

(我知道嵌套循環遠非最優,但我想不出其他任何方法...)


例:

list_of_cakes = [cake_1, cake_2, cake_3, cake_4, cake_5]

哪里

cake_1 = Cake('cake_id'=1,
              'ingredient1'='dark chocolate', 
              'ingredient2'='cookies', 
              'ingredient3'='strawberries',
              'baking_time'=60, 'cake_price'=20)

cake_2 = Cake('cake_id'=2,
              'ingredient1'='dark chocolate', 
              'ingredient2'='cookies', 
              'ingredient3'='strawberries',
              'baking_time'=80, 'cake_price'=20)

cake_3 = Cake('cake_id'=3,
              'ingredient1'='white chocolate', 
              'ingredient2'='bananas', 
              'ingredient3'='strawberries',
              'baking_time'=150, 'cake_price'=100)

cake_4 = Cake('cake_id'=4,
              'ingredient1'='dark chocolate', 
              'ingredient2'='cookies', 
              'ingredient3'='strawberries',
              'baking_time'=40, 'cake_price'=30)

cake_5 = Cake('cake_id'=5,
              'ingredient1'='dark chocolate', 
              'ingredient2'='cookies', 
              'ingredient3'='strawberries',
              'baking_time'=10, 'cake_price'=80)

預期結果將是:

list_of_relevant_cakes = [cake_1, cake_3, cake_4, cake_5]
  • cake_1最便宜(也是同一個價格中最快的)-> IN
  • cake_2的價格與cake1相同,並且烘烤時間更長-> OUT
  • cake_3是另一種蛋糕-> IN
  • cake_4比cake_1貴,但烤得更快-> IN
  • cake_5比cake_1和cake_4貴,但烘烤速度更快-> IN

您的方法的運行時間將大致與

len(list_of_cakes) * len(list_of_relevant_cakes)

...如果您有很多蛋糕並且其中很多與蛋糕有關,則可能會變得很大。

我們可以利用以下事實對此加以改進:具有相同成分的每個蛋糕簇都可能小得多。 首先,我們需要一個函數來檢查新蛋糕是否與具有相同成分的現有,已經優化的集群相對應:

from copy import copy

def update_cluster(cakes, new):
    for c in copy(cakes):
        if c.baking_time <= new.baking_time and c.cake_price <= new.cake_price:
            break
        elif c.baking_time >= new.baking_time and c.cake_price >= new.cake_price:
            cakes.discard(c)
    else:
        cakes.add(new)

這樣做是針對new蛋糕,在cakes的副本中對照每個蛋糕c進行檢查,然后:

  1. 如果其烘烤時間和價格都大於或等於現有蛋糕,則立即退出(您可以return而不是break ,但我希望明確說明控制流程)。

  2. 如果其烘烤時間和價格均小於或等於現有蛋糕,則從群集中刪除該現有蛋糕

  3. 如果它超過所有現有的蛋糕(並到達for語句的else子句),則將其添加到集群中。

一旦有了,我們就可以用它來過濾蛋糕了:

def select_from(cakes):
    clusters = {}
    for cake in cakes:
        key = cake.ingredient1, cake.ingredient2, cake.ingredient3
        if key in clusters:
            update_cluster(clusters[key], cake)
        else:
            clusters[key] = {cake}
    return [c for v in clusters.values() for c in v]

它在起作用:

>>> select_from(list_of_cakes)
[Cake(cake_id=1, ingredient1='dark chocolate', ingredient2='cookies', ingredient3='strawberries', baking_time=60, cake_price=20),
 Cake(cake_id=4, ingredient1='dark chocolate', ingredient2='cookies', ingredient3='strawberries', baking_time=40, cake_price=30),
 Cake(cake_id=5, ingredient1='dark chocolate', ingredient2='cookies', ingredient3='strawberries', baking_time=10, cake_price=80),
 Cake(cake_id=3, ingredient1='white chocolate', ingredient2='bananas', ingredient3='strawberries', baking_time=150, cake_price=100)]

該解決方案的運行時間大致與

len(list_of_cakes) * len(typical_cluster_size)

我對隨機蛋糕列表進行了一些測試,每個蛋糕都使用您的五種不同食材,隨機價格和烘烤時間進行選擇,

  1. 這種方法始終產生與您相同的結果(盡管未排序)

  2. 它的運行速度要快得多-在我的機器上運行100,000個隨機蛋糕的時間為0.2秒,而您的機器為3秒。

未經測試的代碼,但應該有助於指出一種更好的方法:

equivalence_fields = operator.attrgetter('ingredient1', 'ingredient2', 'ingrediant3')
relevant_fields = operator.attrgetter('baking_time', 'cake_price')

def irrelevent(cake1, cake2):
    """cake1 is irrelevant if it is both
       more expensive and takes longer to bake.
    """
    return cake1.cake_price > cake2.cake_price and cake1.baking_time > cake2.bake_time

# Group equivalent cakes together
equivalent_cakes = collections.defaultdict(list)
for cake in cakes:
    feature = equivalence_fields(cake)
    equivalent_cakes[feature].append(cake)

# Weed-out irrelevant cakes within an equivalence class
for feature, group equivalent_cakes.items():
    best = min(group, key=relevant_fields)
    group[:] = [cake for cake in group if not irrelevant(cake, best)]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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