Створення списку чисел та їх негативних аналогів у Python


31

Чи є зручний однолінійний параметр для створення списку чисел та їх негативних аналогів у Python?

Наприклад, скажіть, що я хочу створити список із числами 6 - 9 та -6 - -9.

Мій сучасний підхід:

l = [x for x in range(6,10)]
l += [-x for x in l]

Простий "однолінійний" буде:

l = [x for x in range(6,10)] + [y for y in range(-9, -5)]

Однак створювати два списки та потім об'єднувати їх разом здається незручним.


3
Чи повинні позитивні числа виходити перед негативними?
Еріх

2
@Erich Ні, порядок не має значення в моєму випадку.
упр

6
Якщо замовлення не має значення, чи потрібно це навіть у списку? Чи набір буде ОК (що не має упорядкованості), або генератор або кортеж (обидва вони замовлені)?
JG

@JG Я, можливо, згодом захочу створити фігуру. Нібито, використовуючи розкидання тощо. Отже, будь-яке scalar or array-like, shapeбуло б добре.
упр

2
Більшість із цих відповідей читаються як деякі рішення проти гольфу; сортування, функції генератора, itertools. Я б швидше підтримував наданий вами код, ніж більшість "відповідей".
Пейлонрайз

Відповіді:


44

Я не впевнений, що замовлення має значення, але ви можете створити кортеж і розпакувати його в списку.

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]

50

Створіть приємну та читану функцію:

def range_with_negatives(start, end):
    for x in range(start, end):
        yield x
        yield -x

Використання:

list(range_with_negatives(6, 10))

Ось так ви отримуєте зручну однолінійку для будь-чого. Уникайте спроб схожих на магічного про-хакера.


20
Упередження щодо розуміння списків / висловлювань є досить поширеним явищем, і це зазвичай виправдано, кажучи, що вони не "читабельні". Притаманна читабельність конструкції (а не того, як вона використовується) значною мірою суб'єктивна. Особисто я вважаю, що розуміння є більш читабельним, і це рішення набагато менше (порівняйте: "запишіть артеріальний тиск кожного чоловіка старше 50 років" проти "Проходьте кожну людину. Добре, чи чоловіки вони? Якщо так, то ..." ). Я використовую їх з цієї причини, а не тому, що намагаюся "схожий на що-небудь". Довгі розуміння можуть бути розбиті на кілька рядків, якщо в цьому проблема.
яд

2
Справедливо. Але ось де суб'єктивність приходить: я особисто робити знайти список акомпанементу рішення , по крайней мере , як для читання, з точки зору психічного напруження, як функції генератора. Однак я б покращив читабельність (точніше, читабельність) відповіді Datanovice шляхом перейменування yна signedValue. Для мене справжньою додатковою вартістю підходу «визначити функцію» було б забезпечити результати в суворому порядку, задане питання (позитивне, а потім негативне), уникаючи при цьому проблеми однолінійного бармеру Бармара, chainв якій мають дещо інші числові аргументи бути жорстко закодованим у два рази.
яд

2
Я погоджуюсь із загальними почуттями щодо розуміння списків, але визначення такої функції також є дуже корисною технікою. (Особливо потужно збирати результати від рекурсивного алгоритму, записуючи рекурсивний генератор і передаючи результат верхнього рівня до listчи іншого.) Після багатьох років намагаючись кодифікувати, як найкраще приймати ці рішення, єдиний загальний принцип, який має для мене сенс : якщо в програмі є очевидне, хороше ім'я, щоб дати щось для цього, скористайтеся можливістю (і функції - це один із способів цього зробити).
Карл Кнечтел

42

Я б сказав, що найпростішим рішенням є розпакування двох діапазонів у список за допомогою *оператора розпакування:

>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]

Це не лише найкоротший запропонований відповідь, але він також є найефективнішим, оскільки він лише створює єдиний список і не передбачає жодних викликів функцій за межами двох ranges.

Я перевірив це, перевіривши всі відповіді на це запитання за допомогою timeitмодуля:

Метод ідентифікатора відповіді timeit результат
-------------------------------------------------- ------------------------------------------------
(питання) [x для x у діапазоні (6,10)] + [y для y у діапазоні (-9, -5)] 0,843 Usec на цикл
(ця відповідь) [* діапазон (6, 10), * діапазон (-9, -5)] 0,509 Usec на цикл
61348876 [y для x у діапазоні (6,10) для y в (x, -x)] 0,754 Usec на цикл
61349149 список (range_with_negatives (6, 10)) 0,795 Usec на цикл
61348914 список (itertools.chain (діапазон (6, 10), діапазон (-9, -5))) 0,709 Usec за цикл
61366995 [знак * x для знака, x у itertools.product ((- 1, 1), діапазон (6, 10))] 0,899 Usec за цикл
61371302 список (діапазон (6, 10)) + список (діапазон (-9, -5)) 0,729 використання в циклі
61367180 список (range_with_negs (6, 10)) 1,95 Usec за цикл

(тестова перевірка, виконана з Python 3.6.9 на моєму комп’ютері (середні характеристики))


2
Я не надто захоплююсь тим, що продуктивність є актуальною, коли все, що у вас є, є прикладом з <10 предметами, але це, очевидно, є найпростішим рішенням.
JollyJoker

Як ви з'ясуєте початок і кінець негативних значень, не жорстко кодуючи їх?
альдокакани

1
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC

20

Ви можете використовувати itertools.chain()для об'єднання двох діапазонів.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))

1
Я б використовував 'range (-6, -10, -1)' для ясності замовлення не має значення (і
замінюю

7

Можна використовувати itertools.product, що є декартовим продуктом.

[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]

Це може бути повільніше, оскільки ви використовуєте множення, але його слід легко читати.

Якщо ви хочете суто функціональне рішення, ви також можете імпортувати itertools.starmapта operator.mul:

from itertools import product, starmap
from operator import mul

list(starmap(mul, product((-1, 1), range(6, 10))))

Однак це менш читабельно.


5
Я вважаю , використання product, starmapі opertaor.mulнадмірно тупим по порівнянні з вкладеним списковим, але я схвалюю пропозицію використовувати множення. [x * sign for sign in (1, -1) for x in range(6, 10)]лише на 10% повільніше, ніж [y for x in range(6, 10) for y in (x, -x)]у випадках, коли порядок має значення, більш ніж у 3 рази швидше, ніж сортування підходу на основі кортежу.
Наближається

Це чітка ідея, але, можливо, трохи надто специфічна для деталей проблеми. Методи, пропоновані іншими, застосовуються більш загально.
Карл Кнечтел

@ApproachingDarknessFish Я згоден , що starmapі mulслід уникати, але я думаю , що productробить його більш читабельним, так як він групує ітератори і їх елементи по окремо. Подвійні петлі в розумінні списку також можуть бути заплутаними через їх несподіваний порядок.
Франк Вель

4

Ви дійсно близькі, поєднуючи два rangeоб’єкти. Але є простіший спосіб зробити це:

>>> list(range(6, 10)) + list(range(-9, -5))
[6, 7, 8, 9, -9, -8, -7, -6]

Тобто перетворіть кожен rangeоб’єкт у список, а потім з'єднайте два списки.

Ще один підхід із використанням itertools:

>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]

itertools.chain()це як узагальнений +: замість того, щоб додавати два списки, він ланцюгує один ітератор за іншим, щоб зробити "супер-ітератор". Потім передайте це, list()і ви отримаєте конкретний список із усіма цифрами, які потрібно в пам'яті.


3
Ця відповідь цінна просто для розуміння, яке [x for x in ...]краще написано list(...).
Карл Кнечтел

2

Зважування є ще однією можливістю.

Якщо ви хочете читати, ваш оригінальний однолінійний лайнер був досить хорошим, але я би змінив діапазони, щоб вони були такими ж, як я думаю, що негативні межі роблять речі менш чіткими.

[x for x in range(6, 10)] + [-x for x in range(6, 10)]

2

ІМО, підхід із застосуванням itertools.chainпредставлених у кількох інших відповідях, безумовно, є найчистішим із запропонованих на сьогоднішній день.

Однак, оскільки у вашому випадку порядок значень не має значення , ви можете уникнути необхідності визначення двох явних rangeоб’єктів і, таким чином, уникнути проведення всієї математики, яка необхідна для негативного rangeіндексування, використовуючи itertools.chain.from_iterable:

>>> import itertools
>>> list(itertools.chain.from_iterable((x, -x) for x in range(6, 10)))
[6, -6, 7, -7, 8, -8, 9, -9]

Тад багатослівний, але досить читабельний.

Іншим подібним варіантом є використання розпакування кортежу / аргументу простою chain:

>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]

Більш лаконічний, але мені здається, що кортеж розпаковується важче, щоб швидко проскочити.


2

Це варіація на тему (див @Derte Trdelnik «s відповідь ) , слідуючи філософії , itertoolsде

Ітераторні будівельні блоки [...] корисні самі по собі або в поєднанні.

Ідея полягає в тому, що, визначаючи нову функцію, ми можемо зробити її загальною:

def interleaved_negatives(it):
    for i in it:
        yield i
        yield -i

і застосувати його до певного rangeітератора:

list(interleaved_negatives(range(6, 10)))

0

Якщо ви хочете зберегти вказаний вами порядок, ви можете скористатися вбудованим генератором діапазону Python із умовними умовами:

def range_with_negs(start, stop):
    for value in range(-(stop-1), stop):      
        if (value <= -start) or (value >= start):
            yield value

Що дає результат:

In [1]: list(range_with_negs(6, 10))
Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]

А також працює з 0 як початок для повного діапазону.


2
Це дуже неефективно для великих значень start.
Соломон Учко

0

Існують різні способи виконання роботи.

Дані змінні: 1. старт = 6 2. стоп = 10

Ви можете спробувати це також для різних підходів:

def mirror_numbers(start,stop):
  if start<stop:
    val=range(start,stop)
    return [j if i < len(val) else -j for i,j in enumerate([x for x in val]*2) ]

mirror_numbers(6,10)

-1

Я просто люблю симетрії.

a = 6 b = 10

nums = [x + y для x in (- (a + b-1), 0) для y в діапазоні (a, b)]

Результат повинен бути -9, -8, ..., 8, 9.

Я вважаю, що вираз nums можна вдосконалити, що далі "in" та "range" мені все ще виглядає незбалансованим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.