Самый элегантный способ рассчитать время завершения для списка заданий в Python

#python #list #python-3.x #list-comprehension

#python #Список #python-3.x #понимание списка

Вопрос:

У меня есть список заданий в виде [(weight, length)] , например

 [(99, 1), (100, 3), (100, 3), (99, 2), (99, 2)]
  

Но намного больше.

И я написал функцию, которая планирует их в соответствии с разными ключами, которые я передаю в качестве параметра. Это означает, что для каждого задания я вычисляю время его завершения как сумму всех предыдущих заданий. Конечная цель — рассчитать взвешенное время завершения: weight[i] * completion_time[i]

В настоящее время я не вижу элегантного способа сделать это без разделения всех длин в отдельном списке, что мне кажется не очень питоническим.

Вот код

 def schedule(jobs_list, sort_key):
     sorted_jobs = sorted(jobs_list, key=sort_key, reverse=True)
     lengths = [job[1] for job in sorted_jobs]
     weighted_completion_times = [sum(lengths[:i   1]) * sorted_jobs[i][0] for i in range(len(sorted_jobs))]
     return sum(weighted_completion_times)
  

и вот пример использования:

 schedule(jobs, lambda t: (t[0] - t[1], t[0]))
  

В идеале я хотел бы, чтобы решение было как удобочитаемым, так и эффективным с точки зрения памяти (т. Е. Без создания другого списка длин)

Комментарии:

1. ... for i in range(len(...)) ... в Python часто присутствует запах кода, в котором предпочтительной идиомой является итерация непосредственно по последовательности значений, а не итерация по индексируемому, а затем индексирование последовательности.

Ответ №1:

Вы хотите использовать itertools.accumulate() итерацию для получения суммарного веса ваших длин:

 from itertools import accumulate

def schedule(jobs_list, sort_key):
     sorted_jobs = sorted(jobs_list, key=sort_key, reverse=True)
     acc_lengths = accumulate(job[1] for job in sorted_jobs)
     weighted_completion_times = (al * job[0] for al, job in zip(acc_lengths, sorted_jobs))
     return sum(weighted_completion_times)
  

Обратите внимание, что это ни в коем случае не создает новые списки, отличные от отсортированного списка. Как за счет того, что вы избегаете создания промежуточных списков, так и за счет того, что вы избегаете повторного суммирования все более и более длинных подсписков (делая это O (N) по сравнению с вашим подходом O (N ^ 2)), вышеупомянутое также намного эффективнее; только в вашем коротком примере время улучшается на 25%:

 >>> from itertools import accumulate
>>> from timeit import timeit
>>> def schedule_lists(jobs_list, sort_key):
...      sorted_jobs = sorted(jobs_list, key=sort_key, reverse=True)
...      lengths = [job[1] for job in sorted_jobs]
...      weighted_completion_times = [sum(lengths[:i   1]) * sorted_jobs[i][0] for i in range(len(sorted_jobs))]
...      return sum(weighted_completion_times)
...
>>> def schedule_acc(jobs_list, sort_key):
...      sorted_jobs = sorted(jobs_list, key=sort_key, reverse=True)
...      acc_lengths = accumulate(job[1] for job in sorted_jobs)
...      weighted_completion_times = (al * job[0] for al, job in zip(acc_lengths, sorted_jobs))
...      return sum(weighted_completion_times)
...
>>> jobs = [(99, 1), (100, 3), (100, 3), (99, 2), (99, 2)]
>>> timeit('schedule(jobs, lambda t: (t[0] - t[1], t[0]))',
...        'from __main__ import jobs, schedule_lists as schedule',
...         number=100000)
0.6098654230008833
>>> timeit('schedule(jobs, lambda t: (t[0] - t[1], t[0]))',
           'from __main__ import jobs, schedule_acc as schedule',
...        number=100000)
0.4608557689934969
  

Однако разница становится гораздо более заметной, когда вы увеличиваете размер списка заданий до 1000:

 >>> import random
>>> jobs = [(random.randrange(80, 150), random.randrange(1, 10)) for _ in range(1000)]
>>> timeit('schedule(jobs, lambda t: (t[0] - t[1], t[0]))',
...        'from __main__ import jobs, schedule_lists as schedule',
...         number=1000)
5.421368871000595
>>> timeit('schedule(jobs, lambda t: (t[0] - t[1], t[0]))',
...        'from __main__ import jobs, schedule_acc as schedule',
...         number=1000)
0.7538741750176996
  

Комментарии:

1. Вау! Действительно, в длинных списках 20000 ваше решение почти в 10 раз быстрее!