У меня есть вложенный список, например:

[[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'], 
 [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
 [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]

Я хотел бы вернуть измененный вложенный список, который содержит одну запись для каждого из уникальных идентификаторов в каждом элементе (первое целое число в каждом). Запись для сохранения должна соответствовать максимуму второго значения.

Например, я хотел бы вернуть:

[[5117, 1556659500, u'29.0'], [67097, 1556659500, u'28.44'],[69370, 1556659500, u'29.94']]

Есть ли эффективный способ сделать это с itertools или иным образом?

Максимальное значение второго значения может не всегда соответствовать последней записи для группы идентификаторов.

-1
PJW 1 Май 2019 в 02:56

5 ответов

Лучший ответ

Желаемый результат будет в d.values().

d = {}
for r in rows:
    if r[1] > d.get(r[1], (0, 0, 0))[1]:
        d[r[0]] = r

Или так:

# Not sure why anyone would worry about imports from the standard library.
from itertools import groupby
from operator import itemgetter

maxes = [
    max(g, key = itemgetter(1))
    for k, g in groupby(sorted(rows), key = itemgetter(0))
]
2
FMc 1 Май 2019 в 03:11

Я бы предпочел простое и понятное решение в этом случае, хотя оно не самое эффективное:

from random import randint

# data = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'],
#         [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
#         [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]


data = [[randint(0, 1000), randint(0, 10000), str(randint(0, 100))] for _ in range(1000000)]

def max_recs(recs, identifier, value):
    results = {}
    for rec in recs:
        if rec[identifier] not in results or rec[value] > results[rec[identifier]][value]:
            results[rec[identifier]] = rec
    return list(results.values())


def max_recs_fixed(recs):
    results = {}
    for rec in recs:
        if rec[0] not in results or rec[1] > results[rec[0]][1]:
            results[rec[0]] = rec
    return list(results.values())


print(max_recs(data, 0, 1))
print(max_recs_fixed(data))

Хотя при этом создается много временных ссылок на элементы в исходном списке, которые затем перезаписываются ссылками на элементы с более высоким значением, я считаю, что это не является проблемой эффективности.

Основная стоимость будет заключаться во многих повторных словарных поисках, которые трудно избежать, учитывая природу проблемы. Но вы пользуетесь преимуществами очень эффективной реализации этого в самом Python.

Если вам не нужна возможность сообщить функции, какие индексы использовать для identifier и value, вы обнаружите, что max_recs_fixed немного быстрее, чем max_recs , Используя профили для миллиона случайно сгенерированных записей, это было в среднем на 5% быстрее.

Поскольку OP предпочитает простоту, это будет минимальным:

data = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'],
        [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
        [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]


results = {}
for rec in data:
    if rec[0] not in results or rec[1] > results[rec[0]][1]:
        results[rec[0]] = rec
print(list(results.values()))

Поскольку есть ряд очень разных результатов и некоторые требования были сделаны относительно скорости, вот некоторый код, который имеет все из них в честном сравнении, вы можете использовать cProfile или аналогичные инструменты для сравнения производительности:

from itertools import groupby
from operator import itemgetter
from random import randint
from collections import defaultdict

# generate a random set of 1000000 items matching the example format
data = [[randint(0, 1000), randint(0, 10000), str(randint(0, 100))] for _ in range(1000000)]


def max_recs(recs):
    results = {}
    for rec in recs:
        if rec[0] not in results or rec[1] > results[rec[0]][1]:
            results[rec[0]] = rec
    return list(results.values())


def convert(lst):
    biggest = defaultdict(int)
    for ident, value, _ in lst:
        if value > biggest[ident]:
            biggest[ident] = value
    return list(filter(lambda l: l[1] == biggest[l[0]], lst))


def process_list(l):
    d = {}
    for item in l:
        key = item[0]
        if key in d:
            if item[1] > d[key][1]:
                d[key] = item
        else:
            d[key] = item
    return list(d.values())


def naive(l):
    temp = []
    temp2 = []
    li = sorted(l, key=lambda x: x[1], reverse=True)
    for i in li:
        if i[0] not in temp:
            temp2.append(i)
            temp.append(i[0])

    return temp2


def another(recs):
    return [
        max(g, key=itemgetter(1))
        for k, g in groupby(sorted(recs), key=itemgetter(0))
    ]


max_recs_res = max_recs(data)
convert_res = convert(data)
process_list_res = process_list(data)
naive_res = naive(data)
another_res = another(data)


def cs(result):
    # return a set of id, value combinations of a result, for comparison
    return {(i, v) for i, v, _ in result}


# check that all results have the same id, value combinations (they do)
assert cs(max_recs_res) == cs(convert_res) == cs(process_list_res) == cs(naive_res) == cs(another_res)
# check that all results have the same number of solutions (convert_res includes *duplicate* id, val combinations!)
assert len(max_recs_res) == len(process_list_res) == len(naive_res) == len(another_res)  # == len(convert_res)

Запустив cProfile, я получил эти результаты.

+-----------------------------------------------------------------------------------+
| Records   | max_recs | convert | process_list | naive  | another | notes          |
| 1000x100  | 14ms     | 49ms    | 13ms         | 110ms  | 83ms    |                |
| 1x10000   | 2ms      | 4ms     | 2ms          | 80ms   | 9ms     | 3x rounded avg |
| 1x1000000 | 216ms    | 400ms   | 258ms        | 7234ms | 2416ms  | 3x rounded avg |
+-----------------------------------------------------------------------------------+

Выполнение менее 1000 записей просто не дает значимых значений - результаты сильно различаются, если вы рассчитываете их по отдельности, поэтому было объединено время выполнения 1000 на 100 записей. Но в целом они одинаково быстры, выполняются практически сразу.

Для больших наборов данных результаты довольно ясны, и большинство алгоритмов масштабируются линейно с размером набора данных, за исключением naive и another, которые масштабируются экспоненциально. (если кто-то хочет проанализировать и предоставить точный заказ, будьте моим гостем)

3
Grismar 2 Май 2019 в 00:51

Мой подход состоит в том, чтобы сделать сопоставление каждого идентификатора с его наибольшим значением, а затем отфильтровать ваш список, используя это сопоставление

from collections import defaultdict


def convert(lst):
    biggest = defaultdict(int)
    for ident, value, _ in lst:
        if value > biggest[ident]:
            biggest[ident] = value
    return [l for l in lst if l[1] == biggest[l[0]]]


lst = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'], 
 [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
 [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]
print(convert(lst)) 
# output: [[5117, 1556659500, '29.0'], [67097, 1556659500, '28.44'], [69370, 1556659500, '29.94']]

После некоторых размышлений я переписал свой код выше:

lst = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'], 
[67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
[69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]

new_list = [max(g, key=lambda l: l[1]) for _, g in groupby(lst, key=lambda l: l[0])]
1
Christian Dean 1 Май 2019 в 01:42

Вот решение, которое повторяется один раз по всему списку. Вы можете использовать словарь, чтобы отслеживать самую высокую запись для каждого идентификатора.

def process_list(l):
    dict = {}
    for item in l:
        key = item[0]
        if key in dict:
            if item[1] > dict[key][1]:
                dict[key] = item
        else:
            dict[key] = item
    return dict.values()

l = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'],
 [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
 [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]
print(process_list(l))
1
Alassane Ndiaye 1 Май 2019 в 00:10

Это может быть наивно

li = [[5117, 1556658900, u'29.3'], [5117, 1556659200, u'29.2'], [5117, 1556659500, u'29.0'],
 [67097, 1556658900, u'28.61'], [67097, 1556659200, u'28.5'], [67097, 1556659500, u'28.44'],
 [69370, 1556658900, u'30.0'], [69370, 1556659200, u'29.90'], [69370, 1556659500, u'29.94']]

temp = []
temp2 = []
li = sorted(li, key=lambda x: x[1], reverse=True)
for i in li:
    if i[0] not in temp:
        temp2.append(i)
        temp.append(i[0])

print(temp2)

Но вы можете изменить его, чтобы сделать его эффективным.

Редактировать:

Я бы сказал, что это не самый лучший ответ, поэтому оп должен выбирать других (так, чтобы в будущем кому-то еще помогали).

0
R4444 1 Май 2019 в 03:06