Tạo danh sách các số và các đối tác phủ định của chúng trong Python


32

Có một lớp lót thuận tiện để tạo danh sách các số và các đối tác phủ định của chúng trong Python không?

Ví dụ: giả sử tôi muốn tạo một danh sách với các số từ 6 đến 9 và -6 đến -9.

Cách tiếp cận hiện tại của tôi là:

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

Một "lớp lót" đơn giản sẽ là:

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

Tuy nhiên, việc tạo hai danh sách và sau đó nối chúng lại với nhau có vẻ bất tiện.


3
Các số dương có nên đến trước các số âm không?
Erich

2
@Erich Không, thứ tự không quan trọng trong trường hợp của tôi.
upe

6
Nếu thứ tự không quan trọng, nó thậm chí có cần phải là một danh sách không? Một bộ sẽ ổn (không có thứ tự), hay bộ tạo hoặc bộ (cả hai đều được đặt hàng)?
JG

@JG Tôi có thể muốn tạo một hình sau này. Giả sử sử dụng phân tán, vv Vì vậy, bất kỳ scalar or array-like, shapesẽ tốt đẹp.
upe

2
Hầu hết các câu trả lời này đều đọc giống như một số giải pháp chống golf; phân loại, hàm tạo, itertools. Tôi thà duy trì mã bạn cung cấp hơn hầu hết các 'câu trả lời'.
Peilonrayz

Câu trả lời:


45

Tôi không chắc chắn nếu thứ tự có vấn đề, nhưng bạn có thể tạo một tuple và giải nén nó trong một danh sách hiểu.

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

52

Tạo một chức năng đẹp và dễ đọc:

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

Sử dụng:

list(range_with_negatives(6, 10))

Đó là cách bạn có được một lớp lót tiện lợi cho mọi thứ. Tránh cố gắng để trông giống như một hacker chuyên nghiệp ma thuật.


21
Định kiến ​​chống lại sự hiểu biết về danh sách / chính tả là khá phổ biến đối với SO và thường được chứng minh bằng cách nói rằng chúng không "có thể đọc được". Khả năng đọc vốn có của một cấu trúc (chứ không phải là cách nó được sử dụng) chủ yếu là chủ quan. Cá nhân tôi thấy hiểu được dễ đọc hơn , và giải pháp này ít hơn nhiều (So sánh: "ghi lại huyết áp của mọi nam giới trên 50 tuổi" so với "Đi qua mỗi người. OK, họ có phải là nam không? Nếu vậy, thì ..." ). Tôi sử dụng chúng vì lý do này, không phải vì tôi đang cố gắng "trông giống" bất cứ điều gì. Hiểu dài có thể bị phá vỡ trên nhiều dòng nếu đó là vấn đề.
Jez

2
Đủ công bằng. Nhưng đó là nơi mà sự chủ quan xuất hiện: Cá nhân tôi tìm thấy giải pháp comp comp ít nhất là có thể đọc được, về mặt căng thẳng tinh thần, như chức năng của trình tạo. Tuy nhiên, tôi sẽ cải thiện khả năng đọc (hay đúng hơn là khả năng đọc cho tôi) câu trả lời của Datanovice bằng cách đổi tên ythành signedValue. Đối với tôi, giá trị gia tăng thực sự của cách tiếp cận hàm xác định sẽ là cung cấp kết quả theo thứ tự nghiêm ngặt mà câu hỏi yêu cầu (dương, rồi âm) trong khi tránh vấn đề về chainmột lớp lót của Barmar trong đó các đối số số hơi khác nhau có được mã hóa cứng trong hai lần.
Jez

2
Tôi đồng ý với tình cảm chung về việc hiểu danh sách, nhưng xác định một chức năng như thế này cũng là một kỹ thuật rất hữu ích để biết. (Việc thu thập kết quả từ thuật toán đệ quy đặc biệt mạnh mẽ bằng cách viết một trình tạo đệ quy và chuyển kết quả cấp cao nhất listhoặc bất cứ điều gì.) : nếu có một cái tên rõ ràng, tốt để đặt cho một cái gì đó trong chương trình, hãy tận dụng cơ hội để làm điều đó (và các chức năng là một trong những cách chúng tôi làm điều này).
Karl Knechtel

43

Tôi muốn nói rằng giải pháp đơn giản nhất là giải nén hai phạm vi vào danh sách bằng cách sử dụng *toán tử giải nén:

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

Đây không chỉ là câu trả lời ngắn nhất được đề xuất mà còn là câu trả lời hiệu quả nhất, bởi vì nó chỉ xây dựng một danh sách duy nhất và không liên quan đến các cuộc gọi chức năng ngoài hai ranges.

Tôi đã xác minh điều này bằng cách kiểm tra tất cả các câu trả lời của câu hỏi này bằng timeitmô-đun:

Trả lời ID Phương pháp kết quả thời gian
-------------------------------------------------- ------------------------------------------------
(trong câu hỏi) [x cho x trong phạm vi (6,10)] + [y cho y trong phạm vi (-9, -5)] 0,843 usec mỗi vòng
(câu trả lời này) [* phạm vi (6, 10), * phạm vi (-9, -5)] 0,509 usec mỗi vòng lặp
61348876 [y cho x trong phạm vi (6,10) cho y trong (x, -x)] 0,754 usec mỗi vòng lặp
Danh sách 61349149 (phạm vi_with_negative (6, 10)) 0,795 usec mỗi vòng lặp
Danh sách 61336114 (itertools.chain (phạm vi (6, 10), phạm vi (-9, -5))) 0.709 usec mỗi vòng lặp
61366995 [ký * x cho dấu, x trong itertools.product ((- 1, 1), phạm vi (6, 10))] 0.899 usec mỗi vòng
Danh sách 61371302 (phạm vi (6, 10)) + danh sách (phạm vi (-9, -5)) 0,729 usec mỗi vòng lặp
Danh sách 61367180 (phạm vi_with_negs (6, 10)) 1,95 usec mỗi vòng lặp

(kiểm tra thời gian thực hiện với Python 3.6.9 trên máy tính của riêng tôi (thông số kỹ thuật trung bình))


2
Tôi không quá quan tâm đến việc giả định hiệu suất có liên quan khi tất cả những gì bạn có là một ví dụ với <10 mục, nhưng đây rõ ràng là giải pháp đơn giản nhất.
JollyJoker

Làm thế nào để bạn tìm ra sự bắt đầu và kết thúc của các giá trị âm mà không mã hóa chúng?
aldokkani

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

20

Bạn có thể sử dụng itertools.chain()để nối hai phạm vi.

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

1
Tôi sẽ sử dụng 'phạm vi (-6, -10, -1)' để rõ ràng về trật tự không thành vấn đề (và thay thế 6 và 10 bằng các biến)
Maarten Fabré

7

Bạn có thể sử dụng itertools.product, đó là sản phẩm cartesian.

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

Điều này có thể chậm hơn vì bạn sử dụng phép nhân, nhưng nên dễ đọc.

Nếu bạn muốn một giải pháp hoàn toàn chức năng, bạn cũng có thể nhập itertools.starmapoperator.mul:

from itertools import product, starmap
from operator import mul

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

Tuy nhiên, điều này là ít đọc hơn.


5
Tôi thấy việc sử dụng product, starmapopertaor.mulkhông cần thiết tù so với comprehensions danh sách lồng nhau, nhưng tôi chấp nhận đề nghị của việc sử dụng phép nhân. [x * sign for sign in (1, -1) for x in range(6, 10)]chỉ chậm hơn 10% so với [y for x in range(6, 10) for y in (x, -x)]và trong trường hợp có vấn đề về trật tự, nhanh hơn gấp 3 lần so với cách sắp xếp phương pháp dựa trên bộ dữ liệu.
Tiếp

Đây là một ý tưởng gọn gàng, nhưng có lẽ hơi quá cụ thể với các chi tiết vấn đề. Các kỹ thuật được cung cấp bởi những người khác áp dụng chung hơn.
Karl Knechtel

@ApproachingDarknessFish Tôi đồng ý rằng starmapmulnên tránh, nhưng tôi nghĩ productlàm cho nó dễ đọc hơn, vì nó nhóm các trình vòng lặp và các thành phần của chúng một cách riêng biệt. Các vòng lặp kép trong việc hiểu danh sách cũng có thể gây nhầm lẫn, do thứ tự bất ngờ của chúng.
Frank Vel

4

Bạn thật sự rất thân thiết, với sự kết hợp của hai rangeđối tượng. Nhưng có một cách dễ dàng hơn để làm điều đó:

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

Đó là, chuyển đổi từng rangeđối tượng thành một danh sách, và sau đó nối hai danh sách.

Một cách tiếp cận khác, sử dụng itertools:

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

itertools.chain()giống như một tổng quát +: thay vì thêm hai danh sách, nó xâu chuỗi một vòng lặp khác để tạo ra một "siêu vòng lặp". Sau đó chuyển nó đến list()và bạn có được một danh sách cụ thể, với tất cả các số bạn muốn trong bộ nhớ.


3
Câu trả lời này có giá trị đơn giản cho cái nhìn sâu sắc [x for x in ...]được đánh vần tốt hơn list(...).
Karl Knechtel

2

Cân nhắc với một khả năng khác.

Nếu bạn muốn khả năng đọc thì lớp lót ban đầu của bạn khá tốt, nhưng tôi sẽ thay đổi phạm vi thành giống như tôi nghĩ giới hạn tiêu cực làm cho mọi thứ trở nên rõ ràng hơn.

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

2

IMO cách tiếp cận sử dụng itertools.chainđược trình bày trong một vài câu trả lời khác chắc chắn là cách rõ ràng nhất trong số những câu trả lời được cung cấp cho đến nay.

Tuy nhiên, vì trong trường hợp của bạn , thứ tự của các giá trị không thành vấn đề , bạn có thể tránh phải xác định hai rangeđối tượng rõ ràng và do đó tránh thực hiện tất cả các phép toán ngoài luồng cần thiết cho rangelập chỉ mục phủ định , bằng cách sử dụng 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, nhưng đủ đọc.

Một tùy chọn tương tự khác là sử dụng giải nén tuple / argument với plain chain:

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

Ngắn gọn hơn, nhưng tôi thấy tuple giải nén khó khăn hơn để tìm kiếm trong một lần quét nhanh.


2

Đây là một biến thể về một chủ đề (xem câu trả lời của @Derte Trdelnik ) theo triết lý về nơiitertools

khối xây dựng vòng lặp [...] là hữu ích cho chính họ hoặc kết hợp.

Ý tưởng là trong khi chúng ta xác định một chức năng mới, chúng ta cũng có thể làm cho nó chung chung:

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

và áp dụng nó cho một rangetrình vòng lặp cụ thể :

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

0

Nếu bạn muốn giữ thứ tự bạn đã chỉ định, bạn có thể sử dụng trình tạo phạm vi tích hợp của Python với một điều kiện:

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

Cung cấp cho bạn đầu ra:

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

Và cũng hoạt động với 0 là bắt đầu cho phạm vi đầy đủ.


2
Điều này là rất không hiệu quả cho các giá trị lớn của start.
Solomon Ucko

0

Có thể có nhiều cách khác nhau để hoàn thành công việc.

Các biến đã cho: 1. start = 6 2. stop = 10

Bạn cũng có thể thử điều này, cho cách tiếp cận khác nhau:

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ôi chỉ thích đối xứng.

a = 6 b = 10

nums = [x + y cho x in (- (a + b-1), 0) cho y trong phạm vi (a, b)]

Kết quả phải là -9, -8, ..., 8, 9.

Tôi tin rằng biểu thức nums có thể được cải thiện, những gì sau "trong" và "phạm vi" vẫn trông không cân đối với tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.