Wygeneruj listę liczb i ich ujemnych odpowiedników w Pythonie

31

Czy istnieje wygodny jednowierszowy program do generowania listy liczb i ich ujemnych odpowiedników w Pythonie?

Powiedzmy na przykład, że chcę wygenerować listę o numerach od 6 do 9 i od -6 do -9.

Moje obecne podejście to:

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

Prostym „jednowierszowym” byłoby:

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

Jednak generowanie dwóch list, a następnie łączenie ich ze sobą wydaje się niewygodne.

upe
źródło
3
Czy liczby dodatnie powinny znajdować się przed liczbami ujemnymi?
Erich
2
@Erich Nie, kolejność nie ma znaczenia w moim przypadku.
upe
6
Jeśli kolejność nie ma znaczenia, czy w ogóle musi to być lista? Czy zestaw byłby OK (który jest nieuporządkowany), czy generator lub krotka (oba są zamawiane)?
JG
@JG Może później chcę utworzyć figurę. Podobno używając rozproszenia itp. Tak więc każdy scalar or array-like, shapebyłby w porządku.
upe
2
Większość tych odpowiedzi brzmi jak niektóre rozwiązania przeciw golfowi; sortowanie, funkcje generatora, narzędzia itertools. Wolałbym zachować kod, który podałeś, niż większość „odpowiedzi”.
Peilonrayz

Odpowiedzi:

44

Nie jestem pewien, czy kolejność ma znaczenie, ale możesz utworzyć krotkę i rozpakować ją w liście.

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
Datanovice
źródło
50

Utwórz miłą i czytelną funkcję:

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

Stosowanie:

list(range_with_negatives(6, 10))

W ten sposób otrzymujesz wygodną jednowarstwową na wszystko. Unikaj próby wyglądania jak magiczny haker pro.

Derte Trdelnik
źródło
20
Uprzedzenia w stosunku do list / dyktów są dość powszechne na SO i zwykle są uzasadnione stwierdzeniem, że nie są „czytelne”. Nieodłączna czytelność konstruktu (a nie sposób jego użycia) jest w dużej mierze subiektywna. Osobiście uważam, że wyrozumiałość jest bardziej czytelna, a to rozwiązanie znacznie mniej (porównaj: „zanotuj ciśnienie krwi każdego mężczyzny powyżej 50 roku życia” vs. „Przejdź przez każdą osobę. OK, czy to mężczyźni? Jeśli tak, to ...” ). Używam ich z tego powodu, a nie dlatego, że próbuję „wyglądać” na cokolwiek. Długie zrozumienie może być podzielone na wiele linii, jeśli to jest problem.
jez
2
Słusznie. Ale to gdzie subiektywność jest w: Osobiście zrobić znaleźć rozwiązanie lista zarys przynajmniej tak czytelny, pod względem psychicznym, jak funkcja generatora. Jednak poprawiłbym czytelność (a raczej czytelność dla mnie) odpowiedzi Datanovice poprzez zmianę nazwy yna signedValue. Dla mnie prawdziwą wartością dodaną podejścia „definiuj funkcję” byłoby dostarczanie wyników w ścisłej kolejności zadawanego pytania (pozytywna, a następnie negatywna), przy jednoczesnym unikaniu problemu jednowierszowego Barmara, chainw którym nieco inne argumenty liczbowe mają zostać zakodowanym dwukrotnie.
jez
2
Zgadzam się z ogólnym sentymentem do zrozumienia list, ale zdefiniowanie takiej funkcji jest również bardzo przydatną techniką do poznania. (Szczególnie wydajne jest zbieranie wyników z algorytmu rekurencyjnego przez napisanie generatora rekurencyjnego i przekazanie wyniku najwyższego poziomu do listczegokolwiek.) Po latach prób kodyfikowania najlepszego sposobu podejmowania tych decyzji, jedyna ogólna zasada, która ma dla mnie sens : jeśli w programie jest coś oczywistego, dobrego imienia, skorzystaj z okazji (a funkcje to jeden ze sposobów, w jaki to robimy).
Karl Knechtel,
42

Powiedziałbym, że najprostszym rozwiązaniem jest rozpakowanie dwóch zakresów na listę za pomocą *operatora rozpakowywania:

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

Jest to nie tylko najkrótsza jak dotąd zaproponowana odpowiedź, ale także najbardziej wydajna, ponieważ tworzy ona tylko jedną listę i nie wymaga żadnych wywołań funkcji poza tymi dwoma range.

Zweryfikowałem to, testując wszystkie odpowiedzi na to pytanie za pomocą timeitmodułu:

Identyfikator odpowiedzi Metoda timeit wynik
-------------------------------------------------- ------------------------------------------------
(w pytaniu) [x dla xw zakresie (6,10)] + [y dla yw zakresie (-9, -5)] 0,843 usec na pętlę
(ta odpowiedź) [* zakres (6, 10), * zakres (-9, -5)] 0,509 usec na pętlę
61348876 [y dla xw zakresie (6,10) dla yw (x, -x)] 0,754 usec na pętlę
61349149 lista (zakres_z ujemnymi (6, 10)) 0,795 usec na pętlę
61348914 lista (itertools.chain (zakres (6, 10), zakres (-9, -5))) 0,709 usec na pętlę
61366995 [znak * x dla znaku, x w itertools.product ((- 1, 1), zakres (6, 10))] 0,899 usec na pętlę
61371302 lista (zakres (6, 10)) + lista (zakres (-9, -5)) 0,729 usec na pętlę
61367180 list (range_with_negs (6, 10)) 1,95 usec na pętlę

(testy czasu przeprowadzane za pomocą Pythona 3.6.9 na moim komputerze (średnie specyfikacje))

RoadrunnerWMC
źródło
2
Nie przepadam za założeniem, że wydajność jest istotna, gdy wszystko, co masz, to przykład z <10 elementami, ale jest to oczywiście najprostsze rozwiązanie.
JollyJoker
Jak obliczyć początek i koniec wartości ujemnych bez ich twardego kodowania?
aldokkani
1
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
20

Możesz użyć itertools.chain()do połączenia dwóch zakresów.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
źródło
1
Użyłbym
7

Możesz użyć itertools.product, który jest produktem kartezjańskim.

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

Może to być wolniejsze, ponieważ używasz mnożenia, ale powinno być łatwe do odczytania.

Jeśli potrzebujesz czysto funkcjonalnego rozwiązania, możesz również zaimportować itertools.starmapi operator.mul:

from itertools import product, starmap
from operator import mul

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

Jest to jednak mniej czytelne.

Frank Vel
źródło
5
Znaleźć zastosowanie product, starmapi opertaor.mulniepotrzebnie rozwarty porównaniu do zagnieżdżonych listowych, ale ja popieram sugestię używając mnożenia. [x * sign for sign in (1, -1) for x in range(6, 10)]jest tylko 10% wolniejszy niż [y for x in range(6, 10) for y in (x, -x)], aw przypadkach, gdy liczy się kolejność, ponad 3 razy szybszy niż sortowanie podejścia opartego na krotkach.
ApproachingDarknessFish
To fajny pomysł, ale być może zbyt specyficzny dla szczegółów problemu. Techniki oferowane przez innych mają zastosowanie bardziej ogólnie.
Karl Knechtel,
@ApproachingDarknessFish Zgadzam się z tym starmapi mulnależy go unikać, ale myślę, że productczyni go bardziej czytelnym, ponieważ grupuje iteratory i ich elementy osobno. Podwójne pętle w rozumieniu listy mogą być również mylące ze względu na ich nieoczekiwaną kolejność.
Frank Vel
4

Jesteś naprawdę blisko dzięki połączeniu dwóch rangeobiektów. Ale jest na to łatwiejszy sposób:

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

Oznacza to, że przekonwertuj każdy rangeobiekt na listę, a następnie połącz dwie listy.

Inne podejście przy użyciu itertools:

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

itertools.chain()jest jak uogólniony +: zamiast dodawać dwie listy, łączy jeden iterator po drugim, tworząc „super-iterator”. Następnie przekaż to do, list()a otrzymasz konkretną listę ze wszystkimi liczbami, które chcesz w pamięci.

Greg Ward
źródło
3
Ta odpowiedź jest cenna tylko ze względu na [x for x in ...]lepszą pisownię list(...).
Karl Knechtel,
2

Ważenie z jeszcze jedną możliwością.

Jeśli chcesz czytelności, twój oryginalny linijka była całkiem dobra, ale zmieniłbym zakresy, aby były takie same, ponieważ uważam, że ujemne granice sprawiają, że wszystko jest mniej wyraźne.

[x for x in range(6, 10)] + [-x for x in range(6, 10)]
KyleL
źródło
2

IMO podejście wykorzystujące itertools.chainprzedstawione w kilku innych odpowiedziach jest zdecydowanie najczystsze z podanych do tej pory.

Ponieważ jednak w twoim przypadku kolejność wartości nie ma znaczenia , możesz uniknąć konieczności definiowania dwóch jawnych rangeobiektów, a tym samym unikania wykonywania wszystkich obliczeń matematycznych potrzebnych do rangeindeksowania ujemnego , używając 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]

Tad gadatliwy, ale wystarczająco czytelny.

Inną podobną opcją jest użycie rozpakowywania krotek / argumentów za pomocą zwykłego chain:

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

Bardziej zwięzłe, ale uważam, że rozpakowywanie krotki jest trudniejsze w szybkim skanowaniu.

hBy2Py
źródło
2

Jest to wariacja na temat (patrz @Derte trdelník „s odpowiedź ) po filozofię itertools, gdzie

bloki konstrukcyjne iteratora [...] są użyteczne same w sobie lub w kombinacji.

Chodzi o to, że podczas definiowania nowej funkcji równie dobrze możemy uczynić ją ogólną:

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

i zastosuj do konkretnego rangeiteratora:

list(interleaved_negatives(range(6, 10)))
PiCTo
źródło
0

Jeśli chcesz zachować ustaloną kolejność, możesz skorzystać z wbudowanego generatora zasięgu Pythona z warunkiem:

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

Co daje wynik:

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

Działa również z 0 jako początkiem pełnego zakresu.

Jeff
źródło
2
Jest to bardzo nieefektywne w przypadku dużych wartości start.
Solomon Ucko
0

Istnieją różne sposoby wykonania pracy.

Podane zmienne: 1. start = 6 2. stop = 10

Możesz spróbować również w przypadku innego podejścia:

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
źródło
-1

Po prostu lubię symetrie.

a = 6 b = 10

nums = [x + y dla x w (- (a + b-1), 0) dla y w zakresie (a, b)]

Wynik powinien wynosić -9, -8, ..., 8, 9.

Uważam, że można poprawić ekspresję nums, co po „in” i „range” nadal wydaje mi się niezrównoważone.

CSQL
źródło