Python'da sayıların ve negatif karşılıklarının listesini oluşturun


31

Python'da bir sayı listesi ve negatif muadilleri oluşturmak için uygun bir tek astar var mı?

Örneğin, 6 ile 9 ve -6 ile -9 arasındaki sayıları içeren bir liste oluşturmak istediğimi varsayalım.

Mevcut yaklaşımım:

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

Basit bir "tek astarlı":

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

Ancak, iki liste oluşturmak ve sonra bunları birleştirmek sakıncalı görünüyor.


3
Pozitif sayılar negatif olanlardan önce mi gelmeli?
Erich

2
@Erich No, sipariş benim durumumda önemli değil.
upe

6
Siparişin önemi yoksa, bir liste olması bile gerekiyor mu? Bir set (sıralanmamış) TAMAM mı yoksa bir jeneratör veya tuple mi (her ikisi de sipariş edilir) olur mu?
JG

@JG Daha sonra bir figür oluşturmak isteyebilirim. Sözde saçılma vb kullanarak . Yani herhangi bir iyi scalar or array-like, shapeolurdu.
upe

2
Bu cevapların çoğu bazı golf karşıtı çözümler gibi okuyor; sıralama, jeneratör fonksiyonları, itertools. Sağladığınız kodu 'cevapların' çoğundan çok sürdürmeyi tercih ederim.
Peilonrayz

Yanıtlar:


45

Siparişin önemli olup olmadığından emin değilim, ancak bir demet oluşturabilir ve liste kavrayışında paketini açabilirsiniz.

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

51

Güzel ve okunabilir bir işlev oluşturun:

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

Kullanımı:

list(range_with_negatives(6, 10))

Bu şekilde her şey için uygun bir astar elde edersiniz. Sihirli bir profesyonel hacker gibi görünmekten kaçının.


21
Liste / diksiyon kavrayışlarına karşı önyargı SO'da oldukça yaygındır ve genellikle "okunabilir olmadıklarını" söyleyerek haklı çıkar. Bir yapının doğal olarak okunabilirliği ( nasıl kullanıldığı yerine ) büyük ölçüde özneldir. Şahsen anlamayı daha okunaklı buluyorum ve bu çözüm çok daha az (Karşılaştırma: "50 yaşın üzerindeki her erkeğin kan basıncını yazın" ve "Her kişiden geçin. Tamam, erkek mi? Öyleyse, o zaman ..." ). Onları bu nedenle kullanıyorum, bir şey "gibi görünmeye" çalıştığım için değil. Eğer sorun buysa, uzun kavrayışlar birden fazla çizgiye bölünebilir.
jez

2
Yeterince adil. Öznellik devreye giriyor Ama bu: ben şahsen yapmak jeneratör fonksiyonu olarak, zihinsel zorlanma açısından, okunabilir şekilde en azından liste comp çözüm bulmak. Ben ancak yeniden adlandırarak (yerine okunabilirliği-için-bana veya) Datanovice cevabı okunabilirliği artıracak yiçin signedValue. Bana göre, bir tanımla-fonksiyonu yaklaşımının gerçek katma değeri, Barmar'ın chainbiraz farklı sayısal argümanların bulunduğu tek satırlık probleminden kaçınırken, sorulan soruya (pozitif, sonra negatif) kesin sırayla sonuç vermek olacaktır. iki kere sabit kodlanmalı.
jez

2
Liste anlama hakkındaki genel düşünceye katılıyorum, ancak böyle bir işlevi tanımlamak da bilmek çok yararlı bir teknik. (Özyinelemeli bir jeneratör yazıp üst düzey sonucu listveya herhangi bir şekilde aktararak özyinelemeli bir algoritmadan sonuç toplamak özellikle güçlüdür .) Yıllarca bu kararların en iyi nasıl verileceğini kodlamaya çalıştıktan sonra, bana mantıklı olan tek genel ilke : programdaki bir şeye vermek için açık ve iyi bir ad varsa, bunu yapma fırsatını kullanın (ve işlevler bunu yapmanın yollarından biridir).
Karl Knechtel

43

En basit çözümün iki aralığı *paketten çıkarma operatörünü kullanarak bir listeye açmak olduğunu söyleyebilirim :

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

Bu henüz önerilen en kısa cevap değil, aynı zamanda en yüksek performanstır, çünkü sadece tek bir liste oluşturur ve iki ranges'nin ötesinde hiçbir işlev çağrısı içermez .

timeitModülü kullanarak bu sorunun tüm cevaplarını test ederek bunu doğruladım :

Yanıt Kimliği Yöntemi timeit sonucu
-------------------------------------------------- ------------------------------------------------
(söz konusu) [aralıktaki x için x (6,10)] + [aralıktaki y için y (-9, -5)] Döngü başına 0,843 usec
(bu cevap) [* aralık (6, 10), * aralık (-9, -5)] Döngü başına 0,509 usec
61348876 [y için x aralığında (6,10) y için (x, -x)] döngü başına 0,754 usec
61349149 listesi (range_with_negatives (6, 10)) Döngü başına 0,795 usec
61348914 listesi (itertools.chain (aralık (6, 10), aralık (-9, -5))) döngü başına 0,709 usec
61366995 [işaret * x işaret için, itertools.product içindeki x ((- 1, 1), aralık (6, 10))] döngü başına 0,899 usec
61371302 liste (aralık (6, 10)) + liste (aralık (-9, -5)) döngü başına 0,729 usec
61367180 liste (range_with_negs (6, 10)) Döngü başına 1,95 usec

(kendi bilgisayarımda Python 3.6.9 ile yapılan timeit testi (ortalama özellikler))


2
Sahip olduğunuz tek şey <10 öğe ile bir örnek olduğunda performansın alakalı olduğunu varsaymaya çok hevesli değilim, ancak bu açıkça en basit çözümdür.
JollyJoker

Negatif değerlerin sabit kodlamasını yapmadan başlangıcını ve sonunu nasıl anlarsınız?
aldokkani

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

20

itertools.chain()İki aralığı birleştirmek için kullanabilirsiniz .

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

1
'Aralık (-6, -10, -1)' değerini sipariş netliği için önemli değil (ve 6 ve 10'u değişkenlerle değiştiririm)
Maarten Fabré

7

itertools.productKartezyen ürün olan kullanabilirsiniz .

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

Çarpma kullandığınız için bu daha yavaş olabilir, ancak okunması kolay olmalıdır.

Tamamen işlevsel bir çözüm istiyorsanız, şunları da içe aktarabilir itertools.starmapve operator.mul:

from itertools import product, starmap
from operator import mul

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

Ancak, bu daha az okunabilir.


5
Ben kullanımını bulmak product, starmapve opertaor.mulgereksiz yere geniş iç içe liste comprehensions kıyasla, ancak çarpma işlemi kullanılarak önerisi onaylıyor. [x * sign for sign in (1, -1) for x in range(6, 10)]yalnızca [y for x in range(6, 10) for y in (x, -x)]siparişten % 10 daha yavaştır ve siparişin önemli olduğu durumlarda, grup tabanlı yaklaşımı sıralamaktan 3 kat daha hızlıdır.
ApproachingDarknessFish

Bu düzgün bir fikir, ama belki de sorun ayrıntılarına biraz fazla özgüdür. Başkaları tarafından sunulan teknikler daha genel olarak uygulanır.
Karl Knechtel

@ApproachingDarknessFish Kabul ediyorum starmapve mulkaçınılması gerekiyor, ancak productyineleyicileri ve öğelerini ayrı ayrı grupladığı için daha okunabilir hale getirdiğini düşünüyorum . Liste kavrayışındaki çift döngüler de beklenmedik sıraları nedeniyle kafa karıştırıcı olabilir.
Frank Vel

4

İki rangenesneyi birleştirmekle çok yakınsınız . Ancak bunu yapmanın daha kolay bir yolu var:

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

Yani, her birini dönüştürün range nesneyi bir listeye ve sonra iki listeyi birleştirin.

Itertools kullanarak başka bir yaklaşım:

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

itertools.chain()genelleştirilmiş gibidir +: iki liste eklemek yerine, bir "süper-yineleyici" yapmak için bir yineleyiciyi birbiri ardına zincirler. Daha sonra list()bunu iletin ve hafızada istediğiniz tüm numaralarla birlikte somut bir liste alın.


3
Bu cevap, [x for x in ...]daha iyi yazılmış olan içgörü için değerlidir list(...).
Karl Knechtel

2

Başka bir olasılıkla tartım.

Okunabilirlik istiyorsanız, orijinal tek astarınız oldukça iyiydi, ancak negatif sınırların işleri daha az netleştirdiğini düşündüğüm aralıklarla aynı olacak şekilde değiştiririm.

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

2

IMO yaklaşımı itertools.chaindiğer birkaç cevapta sunulan yaklaşımı kesinlikle şimdiye kadar sağlananların en temizidir.

Ancak, sizin durumunuzda değerlerin sırası önemli olmadığından , iki açık rangenesne tanımlamaktan kaçınabilirsiniz ve böylece aşağıdakileri rangekullanarak negatif indeksleme için gerekli tüm tek tek matematiği yapmaktan kaçınabilirsiniz 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 ayrıntılı, ama yeterince okunabilir.

Bir başka benzer seçenek, düz ile argüman / açma bağımsız değişkenini kullanmaktır chain:

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

Daha özlü, ama hızlı bir taramada tuple açmak daha zor buluyorum.


2

Bu bir tema üzerinde bir varyasyonu (bkz @Derte Trdelnik 'ın cevabı felsefesini takip)itertools

yineleyici yapı taşları [...] kendi başlarına veya kombinasyon halinde faydalıdır.

Fikir şu ki, yeni bir işlev tanımlarken, onu genel hale getirebiliriz:

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

ve belirli bir rangeyineleyiciye uygulayın:

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

0

Belirttiğiniz siparişi korumak istiyorsanız, Python'un yerleşik aralık üretecini bir koşulla kullanabilirsiniz:

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

Hangi çıktı verir:

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

Ve tüm aralık için başlangıç ​​olarak 0 ile çalışır.


2
Bu, büyük değerleri için çok verimsizdir start.
Solomon Ucko

0

İşi yapmanın farklı yolları olabilir.

Verilen değişkenler: 1. start = 6 2. stop = 10

Bunu farklı yaklaşımlar için de deneyebilirsiniz:

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

Tıpkı simetriler gibi.

a = 6 b = 10

nums = x inç için [x + y (- (a + b-1), 0) (a, b) aralığında y için]

Sonuç -9, -8, ..., 8, 9 olmalıdır.

Num ifadesinin geliştirilebileceğine inanıyorum, "in" ve "range" den sonra gelenler hala dengesiz görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.