Hasilkan daftar angka dan pasangan negatifnya dengan Python

32

Apakah ada satu-liner yang nyaman untuk menghasilkan daftar angka dan rekan negatifnya di Python?

Misalnya, saya ingin membuat daftar dengan angka 6 hingga 9 dan -6 hingga -9.

Pendekatan saya saat ini adalah:

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

"Satu garis" sederhana adalah:

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

Namun, menghasilkan dua daftar dan kemudian bergabung bersama tampaknya tidak nyaman.

upe
sumber
3
Haruskah angka positif datang sebelum yang negatif?
Erich
2
@Erich Tidak, pesanan tidak masalah dalam kasus saya.
sampai
6
Jika pesanan tidak masalah, apakah perlu daftar? Apakah satu set OK (yang tidak berurutan), atau generator atau tuple (keduanya dipesan)?
JG
@ JG Saya mungkin ingin membuat angka nanti. Seharusnya menggunakan sebar dll. Jadi apapun scalar or array-like, shapeakan baik-baik saja.
upe
2
Sebagian besar jawaban ini dibaca seperti beberapa solusi anti-golf; penyortiran, fungsi generator, itertools. Saya lebih suka mempertahankan kode yang Anda berikan daripada sebagian besar 'jawaban'.
Peilonrayz

Jawaban:

46

Saya tidak yakin jika pesanan itu penting, tetapi Anda bisa membuat tuple dan membukanya dalam pemahaman daftar.

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

Buat fungsi yang bagus dan mudah dibaca:

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

Pemakaian:

list(range_with_negatives(6, 10))

Itulah cara Anda mendapatkan satu-liner yang nyaman untuk apa pun. Hindari berusaha terlihat seperti hacker pro sihir.

Derte Trdelnik
sumber
21
Prasangka terhadap pemahaman daftar / dikt adalah cukup umum pada SO, dan itu biasanya dibenarkan dengan mengatakan bahwa mereka tidak "terbaca". Keterbacaan yang inheren dari sebuah konstruksi (daripada bagaimana itu digunakan) sebagian besar bersifat subjektif. Secara pribadi saya menemukan pemahaman lebih mudah dibaca, dan solusi ini jauh lebih sedikit (Bandingkan: "tuliskan tekanan darah setiap laki-laki di atas 50" vs "Periksa setiap orang. OK, apakah mereka laki-laki? Jika demikian, maka ..." ). Saya menggunakannya untuk alasan ini, bukan karena saya mencoba untuk "terlihat seperti" apa pun. Pemahaman yang panjang dapat dipecah menjadi beberapa baris jika itu masalahnya.
Juli
2
Cukup adil. Tapi di situlah subjektivitas datang dalam: Saya pribadi melakukan menemukan solusi daftar comp setidaknya dibaca, dalam hal ketegangan mental, sebagai generator fungsi. Namun saya akan meningkatkan keterbacaan (atau lebih tepatnya, keterbacaan untuk saya) dari jawaban Datanovice dengan mengganti nama ymenjadi signedValue. Bagi saya nilai tambah nyata dari pendekatan define-a-function adalah untuk memberikan hasil dalam urutan ketat pertanyaan yang diajukan (positif, kemudian negatif) sambil menghindari masalah chainone-liner Barmar di mana argumen numerik yang sedikit berbeda memiliki menjadi hard-kode dua kali.
Juli
2
Saya setuju dengan sentimen umum tentang pemahaman daftar, tetapi mendefinisikan fungsi seperti ini juga merupakan teknik yang sangat berguna untuk diketahui. (Terutama sangat kuat untuk mengumpulkan hasil dari algoritma rekursif dengan menulis generator rekursif dan meneruskan hasil tingkat atas ke listatau apa pun.) Setelah bertahun-tahun mencoba mengodifikasi cara terbaik untuk membuat keputusan ini, satu-satunya prinsip umum yang masuk akal bagi saya : jika ada nama yang jelas dan bagus untuk diberikan kepada sesuatu dalam program, ambil kesempatan untuk melakukannya (dan fungsi adalah salah satu cara kami melakukan ini).
Karl Knechtel
44

Saya akan mengatakan solusi paling sederhana adalah membongkar dua rentang ke dalam daftar menggunakan *operator membongkar:

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

Tidak hanya ini jawaban terpendek yang diajukan, tetapi juga yang paling berkinerja, karena hanya membangun satu daftar dan tidak melibatkan pemanggilan fungsi selain keduanya. range .

Saya memverifikasi ini dengan menguji semua jawaban pertanyaan ini menggunakan timeit modul:

Jawaban ID Metode timeit hasilnya
-------------------------------------------------- ------------------------------------------------
(dalam pertanyaan) [x untuk x dalam kisaran (6,10)] + [y untuk y dalam kisaran (-9, -5)] 0,843 usec per loop
(jawaban ini) [* range (6, 10), * range (-9, -5)] 0,509 usec per loop
61348876 [y untuk x dalam kisaran (6,10) untuk y dalam (x, -x)] 0,754 usec per loop
61349149 daftar (range_with_negatives (6, 10)) 0,795 usec per loop
61348914 daftar (itertools.chain (kisaran (6, 10), kisaran (-9, -5))) 0,709 usec per loop
61366995 [tanda * x untuk tanda, x di itertools.product ((- 1, 1), range (6, 10))] 0,899 usec per loop
61371302 daftar (kisaran (6, 10)) + daftar (kisaran (-9, -5)) 0,729 usec per loop
61367180 daftar (range_with_negs (6, 10)) 1.95 usec per loop

(pengujian timeit dilakukan dengan Python 3.6.9 di komputer saya sendiri (spesifikasi rata-rata))

RoadrunnerWMC
sumber
2
Saya tidak terlalu tertarik untuk menganggap kinerja relevan ketika semua yang Anda miliki adalah contoh dengan <10 item, tetapi ini jelas merupakan solusi paling sederhana.
JollyJoker
Bagaimana Anda mengetahui awal dan akhir dari nilai-nilai negatif tanpa hardcoding?
aldokkani
2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
21

Anda dapat menggunakan itertools.chain()untuk menggabungkan dua rentang.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
sumber
1
Saya akan menggunakan 'range (-6, -10, -1)' untuk kejelasan urutan tidak masalah (dan ganti 6 dan 10 dengan variabel)
Maarten Fabré
8

Anda dapat menggunakan itertools.product, yang merupakan produk kartesius.

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

Ini mungkin lebih lambat karena Anda menggunakan perkalian, tetapi harus mudah dibaca.

Jika Anda menginginkan solusi yang berfungsi murni, Anda juga dapat mengimpor itertools.starmapdan operator.mul:

from itertools import product, starmap
from operator import mul

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

Namun, ini kurang dapat dibaca.

Frank Vel
sumber
6
Saya menemukan penggunaan product, starmapdan opertaor.multidak perlu tumpul dibandingkan dengan daftar bersarang comprehensions, tapi saya menyetujui saran menggunakan perkalian. [x * sign for sign in (1, -1) for x in range(6, 10)]hanya 10% lebih lambat dari pada [y for x in range(6, 10) for y in (x, -x)], dan dalam kasus di mana urutan penting, lebih dari 3x lebih cepat daripada menyortir pendekatan berbasis tuple.
ApproachingDarknessFish
Ini adalah ide yang rapi, tetapi mungkin agak terlalu spesifik untuk detail masalah. Teknik yang ditawarkan oleh orang lain berlaku lebih umum.
Karl Knechtel
@ApproachingDarknessFish Saya setuju starmapdan mulharus dihindari, tapi saya pikir productmembuatnya lebih mudah dibaca, karena mengelompokkan iterator dan elemen mereka secara terpisah. Loop ganda dalam pemahaman daftar juga dapat membingungkan, karena urutannya yang tidak terduga.
Frank Vel
5

Anda benar-benar dekat, dengan menggabungkan dua rangeobjek. Tetapi ada cara yang lebih mudah untuk melakukannya:

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

Artinya, konversikan setiap rangeobjek ke daftar, dan kemudian gabungkan dua daftar.

Pendekatan lain, menggunakan itertools:

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

itertools.chain()seperti generalisasi +: alih-alih menambahkan dua daftar, ia mengaitkan satu iterator demi satu untuk membuat "super-iterator". Kemudian berikan itu ke list()dan Anda mendapatkan daftar konkret, dengan semua angka yang Anda inginkan dalam memori.

Greg Ward
sumber
4
Jawaban ini berharga hanya untuk wawasan yang [x for x in ...]dieja lebih baik list(...).
Karl Knechtel
3

Menimbang dengan kemungkinan lain.

Jika Anda ingin keterbacaan satu-liner asli Anda cukup bagus, tapi saya akan mengubah rentang menjadi sama karena saya pikir batas negatif membuat segalanya menjadi kurang jelas.

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

IMO pendekatan menggunakan itertools.chain disajikan dalam beberapa jawaban lain jelas merupakan yang terbersih dari yang disediakan sejauh ini.

Namun, karena dalam kasus Anda urutan nilai tidak masalah , Anda dapat menghindari keharusan mendefinisikan dua rangeobjek eksplisit , dan dengan demikian menghindari melakukan semua off-by-one matematika yang diperlukan untuk rangepengindeksan negatif , dengan menggunakan 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 verbose, tetapi cukup mudah dibaca.

Pilihan lain yang serupa adalah menggunakan tuple / argumen membongkar dengan polos chain:

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

Lebih ringkas, tetapi saya menemukan tuple membongkar lebih sulit untuk grok dalam pemindaian cepat.

hBy2Py
sumber
3

Ini adalah variasi pada tema (lihat @Derte Trdelnik 's jawaban ) mengikuti filosofi itertoolsdi mana

blok bangunan iterator [...] berguna sendiri atau dalam kombinasi.

Idenya adalah bahwa, sementara kita mendefinisikan fungsi baru, kita mungkin membuatnya secara umum:

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

dan menerapkannya pada rangeiterator tertentu :

list(interleaved_negatives(range(6, 10)))
PiCTo
sumber
1

Jika Anda ingin menjaga urutan yang Anda tentukan, Anda dapat menggunakan generator rentang built-in Python dengan syarat:

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

Yang memberi Anda output:

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

Dan juga bekerja dengan 0 sebagai awal untuk rentang penuh.

Jeff
sumber
2
Ini sangat tidak efisien untuk nilai besar start.
Solomon Ucko
1

Mungkin ada berbagai cara untuk menyelesaikan pekerjaan.

Variabel yang diberikan: 1. mulai = 6 2. berhenti = 10

Anda dapat mencoba ini juga, untuk pendekatan yang berbeda:

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

Aku suka simetri.

a = 6 b = 10

nums = [x + y untuk x in (- (a + b-1), 0) untuk y dalam rentang (a, b)]

Hasilnya harus -9, -8, ..., 8, 9.

Saya percaya bahwa ekspresi nums dapat ditingkatkan, yang mengikuti "dalam" dan "kisaran" masih terlihat tidak seimbang bagi saya.

CSQL
sumber