Generieren Sie eine Liste der Zahlen und ihrer negativen Gegenstücke in Python

31

Gibt es einen praktischen Einzeiler, um eine Liste von Zahlen und ihren negativen Gegenstücken in Python zu erstellen?

Angenommen, ich möchte eine Liste mit den Nummern 6 bis 9 und -6 bis -9 erstellen.

Mein aktueller Ansatz ist:

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

Ein einfacher "Einzeiler" wäre:

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

Es erscheint jedoch unpraktisch, zwei Listen zu erstellen und diese dann zusammenzufügen.

upe
quelle
3
Sollten die positiven Zahlen vor den negativen stehen?
Erich
2
@Erich Nein, die Reihenfolge spielt in meinem Fall keine Rolle.
upe
6
Wenn die Reihenfolge keine Rolle spielt, muss es dann überhaupt eine Liste sein? Wäre ein Set in Ordnung (was ungeordnet ist) oder ein Generator oder Tupel (beide sind bestellt)?
JG
@JG Vielleicht möchte ich später eine Figur erstellen. Angeblich mit Streuung usw. Also scalar or array-like, shapewäre jede in Ordnung.
upe
2
Die meisten dieser Antworten lesen sich wie einige Anti-Golf-Lösungen. Sortierung, Generatorfunktionen, itertools. Ich würde den von Ihnen angegebenen Code viel lieber beibehalten als die meisten "Antworten".
Peilonrayz

Antworten:

45

Ich bin mir nicht sicher, ob die Reihenfolge wichtig ist, aber Sie können ein Tupel erstellen und es in einem Listenverständnis auspacken.

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

Erstellen Sie eine schöne und lesbare Funktion:

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

Verwendungszweck:

list(range_with_negatives(6, 10))

So erhalten Sie für alles einen praktischen Einzeiler. Vermeiden Sie es, wie ein magischer Profi-Hacker auszusehen.

Derte Trdelnik
quelle
21
Vorurteile gegen Listen- / Diktatverständnisse sind bei SO weit verbreitet und werden normalerweise dadurch gerechtfertigt, dass sie nicht "lesbar" sind. Die inhärente Lesbarkeit eines Konstrukts (und nicht wie es verwendet wird) ist weitgehend subjektiv. Persönlich finde ich Verständnis besser lesbar und diese Lösung viel weniger (Vergleiche: "Schreibe den Blutdruck jedes Mannes über 50 auf" vs. "Gehe durch jede Person. OK, sind sie männlich? Wenn ja, dann ..." ). Ich benutze sie aus diesem Grund, nicht weil ich versuche, wie irgendetwas auszusehen. Lange Verständnisse können über mehrere Zeilen hinweg unterbrochen werden, wenn dies das Problem ist.
Jez
2
Meinetwegen. Aber das ist , wo die Subjektivität kommt: Ich persönlich tun die Liste comp Lösung mindestens so lesbar finden, im Hinblick auf die psychische Belastung, da die Generatorfunktion. Ich würde jedoch verbessert die Lesbarkeit (oder besser gesagt, die Lesbarkeit-for-me) von Datanovice Antwort durch die Umbenennung yzu signedValue. Für mich wäre der wahre Mehrwert des Ansatzes zum Definieren einer Funktion, Ergebnisse in der strengen Reihenfolge zu liefern, in der die Frage gestellt wurde (positiv, dann negativ), während das Problem von Barmars Einzeiler vermieden wird, chainin dem leicht unterschiedliche numerische Argumente vorliegen zweimal fest codiert sein.
Jez
2
Ich stimme der allgemeinen Meinung über das Listenverständnis zu, aber die Definition einer solchen Funktion ist auch eine sehr nützliche Technik. (Es ist besonders leistungsfähig, Ergebnisse eines rekursiven Algorithmus zu sammeln, indem ein rekursiver Generator geschrieben und das Ergebnis der obersten Ebene an listoder was auch immer übergeben wird.) Nach Jahren des Versuchs zu kodifizieren, wie diese Entscheidungen am besten getroffen werden können, ist dies das einzige allgemeine Prinzip, das für mich Sinn macht : Wenn es einen offensichtlichen, guten Namen für etwas im Programm gibt, nutzen Sie die Gelegenheit dazu (und Funktionen sind eine der Möglichkeiten, wie wir dies tun).
Karl Knechtel
43

Ich würde sagen, die einfachste Lösung besteht darin, zwei Bereiche mit dem *Entpackungsoperator in eine Liste zu entpacken :

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

Dies ist nicht nur die kürzeste vorgeschlagene Antwort, sondern auch die performanteste, da nur eine einzige Liste erstellt wird und keine Funktionsaufrufe über die beiden hinausgehen range.

Ich habe dies überprüft, indem ich alle Antworten dieser Frage mit dem timeitModul getestet habe :

Antwort ID Methode timeit Ergebnis
-------------------------------------------------- ------------------------------------------------
(in Frage) [x für x im Bereich (6,10)] + [y für y im Bereich (-9, -5)] 0,843 usec pro Schleife
(diese Antwort) [* Bereich (6, 10), * Bereich (-9, -5)] 0,509 usec pro Schleife
61348876 [y für x im Bereich (6,10) für y in (x, -x)] 0,754 usec pro Schleife
61349149 list (range_with_negatives (6, 10)) 0,795 usec pro Schleife
61348914 Liste (itertools.chain (Bereich (6, 10), Bereich (-9, -5))) 0,709 usec pro Schleife
61366995 [Vorzeichen * x für Vorzeichen, x in itertools.product ((- 1, 1), Bereich (6, 10))] 0,899 usec pro Schleife
61371302 Liste (Bereich (6, 10)) + Liste (Bereich (-9, -5)) 0,729 usec pro Schleife
61367180 Liste (range_with_negs (6, 10)) 1,95 usec pro Schleife

(Timeit-Tests mit Python 3.6.9 auf meinem eigenen Computer durchgeführt (durchschnittliche Spezifikationen))

RoadrunnerWMC
quelle
2
Ich bin nicht besonders daran interessiert anzunehmen, dass Leistung relevant ist, wenn Sie nur ein Beispiel mit <10 Elementen haben, aber dies ist eindeutig die einfachste Lösung.
JollyJoker
Wie können Sie den Anfang und das Ende der negativen Werte ermitteln, ohne sie fest zu codieren?
Aldokkani
1
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
20

Sie können itertools.chain()die beiden Bereiche verketten.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
quelle
1
Ich würde 'Bereich (-6, -10, -1)' verwenden, um die Klarheit der Reihenfolge zu gewährleisten (und die 6 und 10 durch Variablen ersetzen)
Maarten Fabré
7

Sie können verwenden itertools.product, welches das kartesische Produkt ist.

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

Dies ist möglicherweise langsamer, da Sie die Multiplikation verwenden, sollte jedoch leicht zu lesen sein.

Wenn Sie eine rein funktionale Lösung wünschen, können Sie auch importieren itertools.starmapund operator.mul:

from itertools import product, starmap
from operator import mul

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

Dies ist jedoch weniger lesbar.

Frank Vel
quelle
5
Ich finde die Verwendung von product, starmapund opertaor.mulunnötig stumpf im Vergleich zu verschachtelter Liste Comprehensions, aber ich befürworte den Vorschlag der Multiplikation mit. [x * sign for sign in (1, -1) for x in range(6, 10)]ist nur 10% langsamer als [y for x in range(6, 10) for y in (x, -x)]und in Fällen, in denen die Reihenfolge wichtig ist, mehr als dreimal schneller als das Sortieren des tupelbasierten Ansatzes.
Annäherung an
Dies ist eine nette Idee, aber vielleicht etwas zu spezifisch für die Problemdetails. Die von anderen angebotenen Techniken gelten allgemeiner.
Karl Knechtel
@ApproachingDarknessFish Ich stimme dem zu starmapund mulsollte vermieden werden, aber ich denke, es productmacht es besser lesbar, da es die Iteratoren und ihre Elemente getrennt gruppiert. Doppelschleifen im Listenverständnis können aufgrund ihrer unerwarteten Reihenfolge ebenfalls verwirrend sein.
Frank Vel
4

Sie sind wirklich nah dran, wenn Sie zwei rangeObjekte kombinieren . Aber es gibt einen einfacheren Weg:

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

Konvertieren Sie also jedes rangeObjekt in eine Liste und verketten Sie dann die beiden Listen.

Ein anderer Ansatz, der itertools verwendet:

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

itertools.chain()ist wie eine Verallgemeinerung +: Anstatt zwei Listen hinzuzufügen, verkettet es einen Iterator nach dem anderen, um einen "Superiterator" zu erstellen. Übergeben Sie das dann an list()und Sie erhalten eine konkrete Liste mit allen gewünschten Zahlen im Speicher.

Greg Ward
quelle
3
Diese Antwort ist nur für die Einsicht wertvoll, [x for x in ...]die besser geschrieben ist list(...).
Karl Knechtel
2

Mit einer weiteren Möglichkeit abwägen.

Wenn Sie Lesbarkeit wünschen, war Ihr ursprünglicher Einzeiler ziemlich gut, aber ich würde die Bereiche so ändern, dass sie der Meinung sind, dass die negativen Grenzen die Dinge weniger klar machen.

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

IMO ist der Ansatz, itertools.chainder in einigen anderen Antworten vorgestellt wird, definitiv der sauberste unter den bisher angebotenen.

Da in Ihrem Fall jedoch die Reihenfolge der Werte keine Rolle spielt , können Sie vermeiden, zwei explizite rangeObjekte definieren zu müssen , und somit vermeiden, die für die negative rangeIndizierung erforderliche Off-by-One-Mathematik durchzuführen, indem Sie Folgendes verwenden 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 ausführlich, aber lesbar genug.

Eine andere ähnliche Option ist das Entpacken von Tupeln / Argumenten mit plain chain:

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

Prägnanter, aber ich finde es schwieriger, Tupel auszupacken, um einen schnellen Scan durchzuführen.

hBy2Py
quelle
2

Dies ist eine Variation über ein Thema (siehe @Derte Trdelnik ‚s Antwort ) im Anschluss an die Philosophie von itertoolswo

Iteratorbausteine ​​[...] sind für sich oder in Kombination nützlich.

Die Idee ist, dass wir, während wir eine neue Funktion definieren, sie genauso gut generisch machen können:

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

und wenden Sie es auf einen bestimmten rangeIterator an:

list(interleaved_negatives(range(6, 10)))
PiCTo
quelle
0

Wenn Sie die von Ihnen angegebene Reihenfolge beibehalten möchten, können Sie den in Python integrierten Bereichsgenerator mit einer Bedingung verwenden:

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

Welches gibt Ihnen die Ausgabe:

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

Und funktioniert auch mit 0 als Start für den gesamten Bereich.

Jeff
quelle
2
Dies ist für große Werte von sehr ineffizient start.
Solomon Ucko
0

Es gibt verschiedene Möglichkeiten, die Arbeit zu erledigen.

Angegebene Variablen: 1. Start = 6 2. Stopp = 10

Sie können dies auch für verschiedene Ansätze versuchen:

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
quelle
-1

Ich mag Symmetrien.

a = 6 b = 10

nums = [x + y für x in (- (a + b-1), 0) für y im Bereich (a, b)]

Das Ergebnis sollte -9, -8, ..., 8, 9 sein.

Ich glaube, dass der nums-Ausdruck verbessert werden kann. Was auf "in" und "range" folgt, erscheint mir immer noch unausgeglichen.

CSQL
quelle