Создать список чисел и их отрицательных аналогов в Python

32

Есть ли в 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 Я могу захотеть создать фигуру позже. Предположительно с помощью Scatter и т. Д. Так что любой scalar or array-like, shapeбудет в порядке.
Уп
2
Большинство из этих ответов читаются как некоторые решения против гольфа; сортировка, генераторные функции, itertools. Я бы предпочел сохранить предоставленный вами код, чем большинство «ответов».
Пейлонрайз

Ответы:

47

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

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
Datanovice
источник
53

Создайте красивую и читаемую функцию:

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

Применение:

list(range_with_negatives(6, 10))

Так вы получаете удобную однострочку для чего угодно. Старайтесь не выглядеть как волшебный профессиональный хакер.

Дерте Трдельник
источник
21
Предубеждение против понимания списков / диктов довольно распространено в SO, и обычно это оправдывается тем, что они не «читабельны». Врожденная читаемость конструкции (а не то, как она используется) в значительной степени субъективна. Лично я нахожу понимание более читабельным, и это решение гораздо менее понятно (сравните: «запишите артериальное давление каждого мужчины старше 50 лет» и «Пройдите через каждого человека. Хорошо, они мужчины? Если так, то ...» ). Я использую их по этой причине, а не потому, что я пытаюсь «выглядеть» как угодно. Длинные понимания могут быть разбиты на несколько строк, если в этом проблема.
Еж
2
Справедливо. Но в этом и заключается субъективность: лично я нахожу решение для компиляции списков, по крайней мере, таким же читаемым с точки зрения умственного напряжения, как и функция генератора. Однако я бы улучшил читабельность (точнее, читабельность для меня) ответа Датановице, переименовав yв signedValue. Для меня реальная добавленная ценность подхода «определить функцию» состояла бы в том, чтобы доставлять результаты в строгом порядке, который задавался вопрос (положительный, затем отрицательный), избегая при этом проблемы однострочника Бармара, chainв котором немного отличающиеся числовые аргументы имеют быть жестко запрограммированным в два раза.
Еж
2
Я согласен с общим мнением о понимании списков, но определение такой функции также очень полезно знать. (Особенно эффективно собирать результаты с помощью рекурсивного алгоритма, написав рекурсивный генератор и передавая результат верхнего уровня listкакому-либо другому.) После многих лет попыток систематизировать, как лучше принимать эти решения, единственный общий принцип, который имеет смысл для меня : если есть очевидное, доброе имя, которое можно дать в программе, воспользуйтесь возможностью (и функции - один из способов сделать это).
Карл Кнехтель
44

Я бы сказал, что самое простое решение - распаковать два диапазона в список с помощью *оператора распаковки:

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

Это не только самый короткий предложенный ответ, но и самый эффективный, потому что он создает только один список и не включает никаких вызовов функций, кроме двух range.

Я проверил это, протестировав ответы на все вопросы с помощью timeitмодуля:

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

(тестирование timeit, выполненное с Python 3.6.9 на моем собственном компьютере (средние характеристики))

RoadrunnerWMC
источник
2
Я не слишком увлекаюсь предположением, что производительность важна, когда у вас есть только пример с <10 ​​элементами, но это, очевидно, самое простое решение.
JollyJoker
Как вы определяете начало и конец отрицательных значений без их жесткого кодирования?
aldokkani
2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
21

Вы можете использовать itertools.chain()для объединения двух диапазонов.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
источник
1
Я бы использовал 'диапазон (-6, -10, -1)', чтобы ясность порядка не имела значения (и заменил 6 и 10 на переменные)
Мартен Фабре,
8

Вы можете использовать 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))))

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

Фрэнк Вел
источник
6
Я считаю , использование 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 раза быстрее, чем сортировка по кортежу.
ApproachDarknessFish
Это хорошая идея, но, возможно, она слишком специфична для деталей проблемы. Методы, предлагаемые другими, применяются более широко.
Карл Кнехтель
@ApproachingDarknessFish Я согласен с этим, starmapи его mulследует избегать, но я думаю, productчто он делает его более читабельным, поскольку он группирует итераторы и их элементы по отдельности. Двойные циклы в понимании списка также могут сбивать с толку из-за их неожиданного порядка.
Фрэнк Вел
5

Вы действительно близки, комбинируя два 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()и вы получите конкретный список со всеми нужными числами в памяти.

Грег Уорд
источник
4
Этот ответ ценен просто для понимания, [x for x in ...]которое лучше написано list(...).
Карл Кнехтель
3

Взвешивание с еще одной возможностью.

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

[x for x in range(6, 10)] + [-x for x in range(6, 10)]
KyleL
источник
3

ИМО подход, использующий 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]

Немного многословно, но достаточно читабельно.

Другой подобный вариант - использовать распаковку кортежей / аргументов с помощью plain chain:

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

Более кратко, но я обнаружил, что распаковка кортежа сложнее в быстром сканировании.

hBy2Py
источник
3

Это вариация на тему (см @Derte Trdelnik «s ответ ) , следуя философии , itertoolsгде

Строительные блоки итератора [...] полезны сами по себе или в сочетании.

Идея состоит в том, что, хотя мы определяем новую функцию, мы могли бы также сделать ее общей:

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

и применить его к конкретному rangeитератору:

list(interleaved_negatives(range(6, 10)))
Пиктограмма
источник
1

Если вы хотите сохранить указанный вами порядок, вы можете использовать встроенный в 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.
Соломон Уко
1

Там могут быть разные способы сделать работу.

Заданные переменные: 1. start = 6 2. stop = 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)
hp_elite
источник
-1

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

а = 6 б = 10

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

Результат должен быть -9, -8, ..., 8, 9.

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

CSQL
источник