从键值对的元组列表中获取具有最少计数的项的键 - Python

时间:2018-02-12 11:16:05

标签: python list dictionary counter

输入是未排序的元组列表:

x = [('herr', 1),
     ('dapao', 1),
     ('cino', 1),
     ('o', 38),
     ('tiao', 2),
     ('tut', 1),
     ('poh', 6),
     ('micheal', 1),
     ('orh', 1),
     ('horlick', 3),
     ('si', 1),
     ('tai', 1),
     ('titlo', 1),
     ('siew', 17),
     ('da', 1),
     ('halia', 2)]

目标是找到计数最少的最后n个键,即所需的输出:

['orh', 'si', 'tai', 'titlo', 'da']

我试过这样做:

  • 首先将元组列表转换为字典
  • 将字典投入计数器
  • 然后从[-n:]
  • 中找到Counter.most_common()元组列表
  • 将元组列表从[-n:]转换为dict
  • 获取密钥,然后将其转换为列表

即。

n = 5
list(dict(Counter(dict(x)).most_common()[-n:]).keys())

是否有一种不那么复杂的方法来获得相同的输出?

我也可以这样做:

from operator import itemgetter
output, *_ = zip(*sorted(x, key=itemgetter(1))[n:])
list(output)

但现在我只是用Counter.most_commonsorted替换了itemgetter。然后我仍然需要zip(*list)通过从zip后的每个元组列表中解压缩第一个值来提取密钥。

必须有一种更简单的方法。

请注意,问题不在于要求排序,而是在原始列表中提取列表中的第一个元素。并且提取的标准基于第二个元素中具有最低值的最后第n个项目。

answers from the possible duplicate linked仍需要解压缩已排序元组列表的步骤,并提取第一个元素列表的前n个。

12 个答案:

答案 0 :(得分:5)

  

目标是找到计数最少的最后n个键

鉴于这个目标的定义,你的两个解决方案都不合适。在使用Counter的{​​{1}}中,您将使用dict,这将使键的顺序未定义,您将无法获得最后一个键,但一些n键的值最小。第二个解决方案有不正确的切片,如果它已修复,它将返回值最小的第一个n个键。

考虑到sorted实施是stable,可以像这样重写以符合目标:

def author_2():
    output, *_ = zip(*sorted(reversed(l), key=lambda v: v[1])[:n])
    return list(reversed(output))

但最好使用heapq,这是stdlib工具,用于解答“来自可迭代的n个最小/最大值”的问题(正如Martijn Pieters指出的那样,nlargest和{{1也是稳定的,文档真的这么说,但是以隐式的方式)。特别是如果您必须处理的真实列表很大(对于小nsmallest,它应该比n docs describe更快。

sorted

您可以进一步提高性能,但代价是订单(排序稳定性),即一些def prop_1(): rev_result = heapq.nsmallest(n, reversed(l), key=lambda v: v[1]) return [item[0] for item in rev_result][::-1] 个键值最小而不是最后值n的{​​{1}}个键。要做到这一点,你需要保留一个“堆化”列表并将其用作内部数据结构而不是普通n(如果你不更改列表并且只需要一次底部n,它就不会给出绩效福利)。您可以从列表中进行推送和弹出,例如:

list

以下是可用于对解决方案进行基准测试的完整模块。

_p2_heap = None

def prop_2():
    global _p2_heap
    if not _p2_heap:
        _p2_heap = []
        for item in l:
            heapq.heappush(_p2_heap, item[::-1])

    return [item[1] for item in heapq.nsmallest(n, _p2_heap)]

以下是import heapq from collections import Counter l = [ ('herr', 1), ('dapao', 1), ('cino', 1), ('o', 38), ('tiao', 2), ('tut', 1), ('poh', 6), ('micheal', 1), ('orh', 1), ('horlick', 3), ('si', 1), ('tai', 1), ('titlo', 1), ('siew', 17), ('da', 1), ('halia', 2) ] n = 5 def author_1(): return list(dict(Counter(dict(l)).most_common()[-n:]).keys()) def author_2(): output, *_ = zip(*sorted(reversed(l), key=lambda v: v[1])[:n]) return list(reversed(output)) def prop_1(): rev_result = heapq.nsmallest(n, reversed(l), key=lambda v: v[1]) return [item[0] for item in rev_result][::-1] _p2_heap = None def prop_2(): global _p2_heap if not _p2_heap: _p2_heap = [] for item in l: heapq.heappush(_p2_heap, item[::-1]) return [item[1] for item in heapq.nsmallest(n, _p2_heap)][::-1] 结果:

timeit

但如果我们制作$ python -m timeit -s "import tst" "tst.author_1()" 100000 loops, best of 3: 7.72 usec per loop $ python -m timeit -s "import tst" "tst.author_2()" 100000 loops, best of 3: 3.7 usec per loop $ python -m timeit -s "import tst" "tst.prop_1()" 100000 loops, best of 3: 5.51 usec per loop $ python -m timeit -s "import tst" "tst.prop_2()" 100000 loops, best of 3: 3.96 usec per loop ,差异就会变得明显:

l = l * 1000

答案 1 :(得分:2)

只需使用堆,它就会为您提供所需的输出。

import heapq

x = [('herr', 1),
('dapao', 1),
('cino', 1),
('o', 38),
('tiao', 2),
('tut', 1),
('poh', 6),
('micheal', 1),
('orh', 1),
('horlick', 3),
('si', 1),
('tai', 1),
('titlo', 1),
('siew', 17),
('da', 1),
('halia', 2)]

heap = [(item[1],-index,item[0]) for index, item in enumerate(x)]
heapq.heapify(heap)

print(list(map(lambda item : item[2], heapq.nsmallest(5, heap))))

heapq.nsmallest(n, iterable, key=None)有一个关键参数,您可以像我一样使用-index

答案 2 :(得分:1)

编辑 @alvas:

mi = min(x, key =lambda x:x[1])[1]
r = [a[0] for a in x if a[1] == mi][-5:]

将生成您想要的输出

您可以使用:

sorted(x, key=lambda x: x[1])

请参阅此(可能重复)

Sort a list of tuples by 2nd item (integer value)

答案 3 :(得分:1)

这是我的建议:

n = 5
output=[]

# Search and store the n least numbers
leastNbs = [a[1] for a in sorted(x, key=lambda x: x[1])[:n]]

# Iterate over the list of tuples starting from the end
# in order to find the tuples including one of the n least numbers
for x,nb in reversed(x):
    if nb in leastNbs:
        output.append(x)  # Store the string in output
        print(x)

# Keep only the n last strings (starting from the end)
output = list(reversed(output[:n]))

print(output)

答案 4 :(得分:1)

您不需要为此任务执行任何导入,您也可以通过以下方式执行此操作:

x = [('herr', 1),
     ('dapao', 1),
     ('cino', 1),
     ('o', 38),
     ('tiao', 2),
     ('tut', 1),
     ('poh', 6),
     ('micheal', 1),
     ('orh', 1),
     ('horlick', 3),
     ('si', 1),
     ('tai', 1),
     ('titlo', 1),
     ('siew', 17),
     ('da', 1),
     ('halia', 2)]

n = 5
result = [name[0] for name in sorted(x, key=lambda i: i[1], reverse=True)[-n:]]
print(result)

输出:

['orh', 'si', 'tai', 'titlo', 'da']

答案 5 :(得分:1)

如果您不想重新发明轮子,可以使用pandas。性能应该很好,因为它基于NumPy,它使用C而不是纯Python。

简短回答

df = pd.DataFrame(x, columns=['name', 'count'])
df = df.sort_values(by='count', kind='mergesort', ascending=False).tail(n)
print df['name'].tolist()

结果

['orh', 'si', 'tai', 'titlo', 'da']

扩展,带注释的工作示例

import pandas as pd

n = 5
x = [('herr', 1),
     ('dapao', 1),
     ('cino', 1),
     ('o', 38),
     ('tiao', 2),
     ('tut', 1),
     ('poh', 6),
     ('micheal', 1),
     ('orh', 1),
     ('horlick', 3),
     ('si', 1),
     ('tai', 1),
     ('titlo', 1),
     ('siew', 17),
     ('da', 1),
     ('halia', 2)]

# Put the data in a dataframe.
df = pd.DataFrame(x, columns=['name', 'count'])

# Get the last n rows having the smallest 'count'.
# Mergesort is used instead of quicksort (default) since a stable sort is needed
# to get the *last* n smallest items instead of just *any* n smallest items.
df = df.sort_values(by='count', kind='mergesort', ascending=False).tail(n)

# Print the 'name' column as a list (since a list is what you asked for).
print df['name'].tolist()

答案 6 :(得分:1)

这是一种干净,简单的方法,不使用python习语:

m = x[0][1]
l = []

for elem in x:
    if m > elem[1]:
        l = [elem[0]]
        m = elem[1]
    elif m == elem[1]:
        l.append(elem[0])

print(l[-5:])

有点像最小值搜索和过滤的融合。 m存储最小值到目前为止,l存储具有该最小计数的元素列表。当您找到较低的值时重置它们。

这可以修改为只容纳5个元素,因此最后不需要拼接。

答案 7 :(得分:1)

[k for k,v in sorted(x, key=lambda x: x[1])[:n]]

其中x是键列表,计数元组和n是所需的键数。

您也可以调整排序条件以包含密钥本身 - 如果他们的订单很重要

[k for k,v in sorted(x, key=lambda x: (x[1], x[0]))[:n]]

答案 8 :(得分:1)

  

[i [0] for i in sorted(x .__ reverse __(),key = lambda x:x [1])[:n]]

与@Stacksonstacks答案差不多完全一样,只是这实际上给你'所需的输出'(如果你把n = 5)

答案 9 :(得分:0)

纯Python解决方案

由于我们试图按照从最小到最大的顺序找到n元素,我们不能简单地过滤掉那些没有最小第二元素的元素。我们还有第二个尝试维护顺序的目的 - 这只消除了每个元组的第二个元素上的排序

我的解决方案具有复杂性O(n) - 这是您在此处可以做的最好的,因为我们正在创建一个依赖于预先存在的列表的新列表。

它的工作原理是在set转换n之后,在x中创建每个元组的第一个x元素[::-1](无序)(set )然后根据第二个元素进行排序。这有一个巧妙的技巧,因为我们在转换为集合之前进行切片,这些元组中仍然存在具有等效第二元素的顺序。

现在,使用O(1)的整洁性是查找是hashes(即时),因为元素按其__contains__的顺序存储,因此调用list是比使用list-comprehension要快得多。

我们最后需要使用x来对>>> n = 5 >>> s = {i[0] for i in sorted(x[::-1], key=lambda t: t[1])[:n]} >>> [i for i, _ in x if i in s] ['orh', 'si', 'tai', 'titlo', 'da'] 进行最终过滤:

n = 11

也是一项测试,表明它适用于['herr', 'dapao', 'cino', 'tut', 'micheal', 'orh', 'si', 'tai', 'titlo', 'da', 'halia']

py::vectorize()

答案 10 :(得分:0)

使用list comprehensionsorted

[key for key,value in sorted(x, key=lambda y: y[1], reverse=True)][-n:]

[key for key,value in sorted(reversed(x), key=lambda y: y[1])][:n][::-1]

其中n是结果中所需的键数。请注意,使用后者[::-1]会更加昂贵,因为它会再次对列表进行切片以将其反转。

from timeit import default_timer

def timeit(method, *args, **kwargs):
    start = default_timer()
    result = method(*args, **kwargs)
    end = default_timer()
    print('%s:\n(timing: %fs)\n%s\n' % (method.__name__, (end - start), result))

def with_copy(x, n):
    return [key for key,value in sorted(reversed(x), key=lambda y: y[1])][:n][::-1]

def without_copy(x, n):
    return [key for key,value in sorted(x, key=lambda y: y[1], reverse=True)][-n:]

x = [('herr', 1), ('dapao', 1), ('cino', 1), ('o', 38), ('tiao', 2),
     ('tut', 1), ('poh', 6), ('micheal', 1), ('orh', 1), ('horlick', 3),
     ('si', 1), ('tai', 1), ('titlo', 1), ('siew', 17), ('da', 1),
     ('halia', 2)]
n = 5
timeit(with_copy, x, n)
timeit(without_copy, x, n)
n = 11
timeit(with_copy, x, n)
timeit(without_copy, x, n)

n = 5的结果:

with_copy:
(timing: 0.000026s)
['orh', 'si', 'tai', 'titlo', 'da']

without_copy:
(timing: 0.000018s)
['orh', 'si', 'tai', 'titlo', 'da']

n = 11的结果:

with_copy:
(timing: 0.000019s)
['halia', 'herr', 'dapao', 'cino', 'tut', 'micheal', 'orh', 'si', 'tai', 'titlo', 'da']

without_copy:
(timing: 0.000013s)
['halia', 'herr', 'dapao', 'cino', 'tut', 'micheal', 'orh', 'si', 'tai', 'titlo', 'da']

答案 11 :(得分:0)

  • 此解决方案无需排序
  • 小解决方案:

    <!DOCTYPE html>
    <html lang="">
    
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title></title>
    </head>
    
    <body>
        <div ng-app="myApp" ng-controller="myCtrl">
        <form name="TestList" id="InsertForm" ng-submit="insert(Test);">
    
            <div>
                <input ng-model="Test.Pack" >
            </div>
            <div>
                <input ng-model="Test.LM" ng-required="{{Test.Pack.substring(0,2) != 'RR'}}">
            </div>
            <div>
                <button ng-disabled="TestList.$invalid">Test Button</button>
            </div>
        </form>
    
        </div>
    </body>
    
    </html>
    <script src="angular.min.js"></script>
    <script>
        var app = angular.module('myApp', []);
    
        //myApp.directive('myDirective', function() {});
        //myApp.factory('myService', function() {});
    
        app.controller('myCtrl', function($scope) {
    
        });
    
    </script>
    
  • 上述解决方案的输出:

    import numpy as np 
    n = 5
    x = [('herr', 1),
         ('dapao', 1),
         ('cino', 1),
         ('o', 38),
         ('tiao', 2),
         ('tut', 1),
         ('poh', 6),
         ('micheal', 1),
         ('orh', 1),
         ('horlick', 3),
         ('si', 1),
         ('tai', 1),
         ('titlo', 1),
         ('siew', 17),
         ('da', 1),
         ('halia', 2)]
    
    x = np.array(x)  # make the list a numpy array
    names = x[:, 0]   
    numbers = x[:, 1].astype(int)
    least_count = np.take(names, np.where(numbers == np.min(numbers)))[0][-n:]
    print(least_count)
    
  • 带注释的解决方案说明

    ['orh', 'si', 'tai', 'titlo', 'da']
    
  • 以上解释的输出:

    import numpy as np 
    
    x = [('herr', 1),
     ('dapao', 1),
     ('cino', 1),
     ('o', 38),
     ('tiao', 2),
     ('tut', 1),
     ('poh', 6),
     ('micheal', 1),
     ('orh', 1),
     ('horlick', 3),
     ('si', 1),
     ('tai', 1),
     ('titlo', 1),
     ('siew', 17),
     ('da', 1),
     ('halia', 2)]
    
    x = np.array(x)  # make the list a numpy array
    # ==========================================
    # split the array into names and numbers
    # ==========================================
    names = x[:, 0]   
    numbers = x[:, 1].astype(int)
    
    mini = np.min(numbers)  # find the minimum in the numbers array
    idx = np.where(numbers == mini)   # Find the indices where minimum occurs in the numbers array
    least_count = np.take(names, idx)[0] # Use the indices found from numbers array in the above line to access names array
    print(least_count)
    least_count = least_count.tolist()  # to convert the numpy array to list
    n = 5   # say n is 5
    print(least_count[-n:]) # now you can do simple slicing to extract the last n element