【问题标题】:From a list of sets find how many other (unique) elements an item is paired with, find the most commonly paired one从集合列表中找到一个项目与多少其他(唯一)元素配对,找到最常配对的元素
【发布时间】:2021-03-11 21:01:45
【问题描述】:

我有一个集合列表,我试图在集合中找到与大多数项目一起使用的项目——不是最常见的一个或出现在大多数集合中的那个,而是那个“相互作用”的大多数项目。

集合列表示例:

food = [{'avocado', 'banana', 'coffee'}, {'avocado', 'donuts'}, {'coffee', 'licorice'}]

应该返回交互次数最多的项目,如果有更多具有相同值的项目,它应该返回按字母顺序排在第一位的项目,在这种情况下:avocado

我已经成功创建了一个字典,其中项目作为键,它们对应的“交互”作为值。按最大值对字典进行排序(嗯,对列表进行排序),最后通过索引所述列表中的第一个元素来获得具有最多“交互”的项目。

def goes_with_most(food):
    food_type = {}
    for mixture in food:
        for stuff in mixture:
            if stuff not in food_type:
                food_type[stuff] = len(mixture) - 1     #the number of interactions 
            else: food_type[stuff] += (len(mixture) - 1)
    max_value_ls = sorted(food_type.items(), key=lambda stuff: (-stuff [1], stuff[0]))
    return max_value_ls[0][0]                            #returns the first item with the highest value 

当然有更清洁的解决方案,非常感谢提示!

【问题讨论】:

  • “互动”是什么意思? “x 与 y 相互作用”是否应该意味着“x 和 y 一起出现在一个集合中”?
  • @user2357112supportsMonica 这是 x 满足任何给定变量的时间。
  • 如果 x 和 y 一起出现在 two 集中,这算两次,还是只算一次互动?
  • 确实,我假设您的意思是,如果您有 {'advocado', 'coffee', 'banana'}{'advocado', 'banana', 'chocolate'},则 'advocado' 与其他 3 种成分“相互作用”:'banana''chocolate' 和 @987654328 @,所以三种成分。您的代码将'banana' 成分计数两次
  • 无关:你有一个错字错误,其中 lambda 的参数拼写错误 sutff。这意味着 lambda 中正确拼写的 stuff 将使用外部范围内该变量的绑定,这是内部 for stuff in mixture 循环的最后一次迭代遗留下来的。

标签: python python-3.x list dictionary set


【解决方案1】:

您还没有非常清楚地指定“交互”是什么。我看到了两种可能的解释:

  1. 与某物配对的独特成分的数量。在包含{'advocado', 'coffee', 'banana'}{'advocado', 'banana', 'chocolate'} 的列表中,'advocado'三种独特成分配对:'banana''chocolate''coffee'
  2. 对于每个集合,该集合中其他成分的数量,无论它们是否存在于其他集合中。在{'advocado', 'coffee', 'banana'}{'advocado', 'banana', 'chocolate'} 的列表中,'advocado' 在第一个中与 2 个成分配对,在第二个中与 2 个成分配对,因此还有 4 个其他成分配对。 'banana' 出现在这两个集合中并不重要。

您的代码坚持第二种解释,并且您声明该代码已经有效。我给你两个代码。

无论哪种方式,您都已经有了正确的想法。您必须

  1. 找到所有独特的元素
  2. 找出与 1. 中的任何项目配对的其他(唯一)元素的数量。

要获得 1。您无法避免循环遍历所有输入集,并且对于每个集,所有值。

但首先:你只需要找到一个获胜的元素,所以你可以使用max()min(),而不需要需要种类!仅当您需要了解所有食物的确切排名时,排序才有用。

为了实现第一种解释,我只需使用集合创建联合,这样就不必手动循环和测试是否包含。您最终会得到具有特定成分作为键和集合的集合,但是由于这适用于所有成分,因此您仍然会得到正确的答案:

def goes_with_most(food):
    ingredients = {}
    for mixture in food:
        for ingredient in mixture:
            ingredients.setdefault(ingredient, set()).update(mixture)
    return min(ingredients, key=lambda ing: (-len(ingredients[ing]), ing))

注意这里的排序键:我们不是在寻找最大的,而是使用负集大小,食物名称作为决胜局。对于advocado,这将返回(-4, 'advocado'),它在(-4, 'coffee') 之前排序,因此保持名称按字母顺序排序。

你已经使用了这个技巧,但它需要明确,它适用于min(),就像它对排序一样。

演示:

>>> food = [{'avocado', 'banana', 'coffee'}, {'avocado', 'donuts'}, {'coffee', 'licorice'}]
>>> goes_with_most(food)
'avocado'

第二种解释,也就是你实现的,只需要用整数替换集合:

def goes_with_most(food):
    counts = {}
    for mixture in food:
        for ingredient in mixture:
            counts[ingredient] = counts.get(ingredient, 0) + len(mixture) - 1
    return min(counts, key=lambda ing: (-counts[ing], ing))

我们可以通过使用defaultdict instance 来稍微缩短(不需要dict.get(key, 0)):

from collections import defaultdict

def goes_with_most(food):
    counts = defaultdict(int)
    for mixture in food:
        for ingredient in mixture:
            counts[ingredient] += len(mixture) - 1
    return min(counts, key=lambda ing: (-counts[ing], ing))

【讨论】:

  • 确实,它是关于一个项目与多少其他(独特)元素配对!因此,您的解释是正确的。
【解决方案2】:

以下代码应该可以工作:

def goes_with_most(food):
    max_counter = -1
    food_type = {}
    for mixture in food:
        for item in mixture:
            if item not in food_type:
                food_type[item] = 0
            food_type[item] += 1
            max_counter = max(food_type[item], max_counter)
    for item in sorted(food_type.keys()):
        if food_type[item] == max_counter:
            return item

或更短

def goes_with_most(food):
    items = []
    for f in food:
        items.extend(list(f))
    counter = collections.Counter(items)
    return sorted([i[0] for i in counter.items() if i[1] == max(counter.values())])[0]

【讨论】:

  • 我的代码已经可以工作了,我正在寻找一个更短的解决方案。不过谢谢!
  • 添加了一个较短的版本:)
  • 您的较短函数增加了二次运行时性能(随着输入大小的增加,所用时间呈指数增长)。为什么要排序?最多使用一次max(),然后在最大计数相同的Ames子集上使用min()
【解决方案3】:

如果我正确理解了这个问题,我认为这是一种优雅而简单的方法。

首先:有哪些食物?我们只需将所有这些“餐点”组合在一起,然后将它们混合成一个组合——毕竟,重复项将被删除:

menu = [{'avocado', 'banana', 'coffee'}, {'avocado', 'donuts'}, {'coffee', 'licorice'}]

foods = set.union(*menu)

现在,让我们编写一个函数来找出与给定食物兼容的食物数量。我们将为此计算食物本身;这意味着比问题描述中的项目多一项,但始终每个项多一项,因此不会影响排序。我们只需抓取所有包含食物的“餐食”,然后用它们制作一个子菜单,然后看看它有多少食物:

def compatibility(menu, food):
    meals = [meal for meal in menu if food in meal]
    return len(set.union(*meals))

现在我们可以根据他们的compatibility 和名称简单地利用foods。棘手的部分是我们想要获取最高兼容性,但在该兼容性级别上是“最低”的字符串。我们不能“否定”字符串,所以我否定兼容性,然后寻找负兼容性的最小值。

所以:

min(foods, key=lambda f: (-compatibility(menu, f), f)) # 'avocado'

【讨论】:

  • OP 有一个 O(NlogN) 实现,如果没有排序,它可能是一个简单的 O(N) 操作。为什么要将其扩展为 N**2?
  • 请注意,您选择了两种可能的解释中的第一种(见我的回答),而 OP 实现了第二种解释并声明他们的代码可以正常工作。
【解决方案4】:

根据我对您所说的“交互”的理解,这是一种通过列表推导解决此问题的方法。

food = [{'avocado', 'banana', 'coffee'}, 
        {'avocado', 'donuts'}, 
        {'coffee', 'licorice'}]


def f(j): return sum([len(i)-1 for i in food if j in i])

unique_stuff = set.union(*food) #Borrowed from @Karl, as his is more efficient
goes_with_most = max([(i, f(i)) for i in unique_stuff], key=lambda x:x[1])[0]
goes_with_most
'avocado'

这里的目标只是获得一个“更干净”更易读的解决方案,而不是最有效的解决方案。

如果您对最高效的基准测试感兴趣 -

  1. @Akshay Sehgal - 每个循环 5.02 µs ± 489 ns(7 次运行的平均值 ± 标准偏差,每次 100000 个循环)
  2. @Karl Knechtel - 每个循环 5.83 µs ± 953 ns(7 次运行的平均值 ± 标准偏差,每次 100000 个循环)
  3. @Daniel Meltzer - 每个循环 4.75 µs ± 609 ns(平均值 ± 标准偏差,7 次运行,每次 100000 次循环)
  4. @Martijn Pieters♦ - 每个循环 3.73 µs ± 537 ns(平均值 ± 标准偏差,7 次运行,每次 100000 次循环)

【讨论】:

  • 这是超级低效的,只需要N个步骤就完成了N**2个步骤。
  • 你的意思是排序?我已经解决了。
  • 不,排序只有 O(NlogN)。对于您调用f() 的每种食物,它会针对每种成分完整地迭代food。尝试扩大配料和套装的数量;生成 100、1000 等。随着输入的增长,您的解决方案将变得非常缓慢。
  • 是的,我说得很清楚,我只是提供cleaner的解决方案,如果OP需要一个高效的解决方案,他可以参考其他人。
猜你喜欢
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-11
  • 1970-01-01
相关资源
最近更新 更多