Làm cách nào để tìm giá trị còn thiếu một cách ngắn gọn hơn?


76

Kiểm tra mã sau nếu xylà những giá trị riêng biệt (các biến x, y, zchỉ có thể có giá trị a, bhoặc c) và nếu như vậy, bộ zvới nhân vật thứ ba:

if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'

Có thể làm điều này theo cách ngắn gọn, dễ đọc và hiệu quả hơn không?


6
Câu trả lời ngắn gọn là có!" Các bộ của Python rất tuyệt vời để kiểm tra tính khác biệt và tính toán các phần tử không sử dụng.
Raymond Hettinger

1
Cảm ơn tất cả các câu trả lời, tôi đoán tôi sẽ sử dụng giải pháp bằng cách sử dụng set vì nó khá nhanh và dễ đọc. Câu trả lời dựa trên bảng tra cứu của Óscar López cũng rất thú vị.
Bunny Rabbit,

Câu trả lời:


62
z = (set(("a", "b", "c")) - set((x, y))).pop()

Tôi giả định rằng một trong ba trường hợp trong mã của bạn được giữ nguyên. Nếu đúng như vậy, tập hợp set(("a", "b", "c")) - set((x, y))sẽ bao gồm một phần tử duy nhất, được trả về bởi pop().

Chỉnh sửa: Theo đề xuất của Raymond Hettinger trong các nhận xét, bạn cũng có thể sử dụng bộ giải nén để trích xuất phần tử duy nhất từ ​​tập hợp:

z, = set(("a", "b", "c")) - set((x, y))

26
Nếu bạn đang sử dụng Python 2.7 / 3.1 hoặc mới hơn, bạn có thể viết nó thậm chí nhiều hơn chính xác sử dụng bộ chữ, như vậy:z = ({'a', 'b', 'c'} - {x, y}).pop()
Taymon

7
Các cửa sổ pop () là không cần thiết và chậm chạp. Thay vào đó, hãy sử dụng bộ giải nén. Ngoài ra, giá trị set(("a", "b", "c"))là bất biến nên nó có thể được tính toán trước một lần, chỉ để lại tập hợp sai lệch được sử dụng trong một vòng lặp (nếu nó không được sử dụng trong một vòng lặp, thì chúng ta không quan tâm nhiều đến tốc độ).
Raymond Hettinger

3
@Ed: Tôi biết, nhưng OP không chỉ định phải làm gì khi nào x == y, vì vậy tôi đã bỏ qua bài kiểm tra. Nó đủ dễ dàng để thêm if x != y:nếu cần.
Sven Marnach

4
Cá nhân tôi sẽ chọn cái đầu tiên vì nó rõ ràng hơn là một dấu phẩy ngẫu nhiên.
John

1
Bạn có thể muốn thay thế set(("a", "b", "c"))bằng set("abc").
kasyc

47

Các stripphương pháp là một tùy chọn chạy một cách nhanh chóng cho tôi:

z = 'abc'.strip(x+y) if x!=y else None

2
+1 Nó cũng rất minh bạch và không giống như hầu hết các câu trả lời, nó đề cập đến x == y.
Ed Staub

1
Ý tưởng hay, +1; mặc dù tôi thực sự nghĩ rằng "a", "b""c"trong bài đăng ban đầu chỉ là trình giữ chỗ cho các giá trị thực. Giải pháp này không khái quát cho bất kỳ loại giá trị khác hơn là chuỗi có độ dài 1.
Sven Marnach

@chepner thật sáng tạo! Cảm ơn vì đã trả lời chepner.
Bunny Rabbit,

27

Đoạn mã tuyệt vời của Sven đã làm quá nhiều việc và đáng lẽ phải sử dụng bộ giải nén tuple thay vì pop () . Ngoài ra, nó có thể có thêm một bộ phận bảo vệ if x != yđể kiểm tra xem xy có khác biệt hay không. Đây là câu trả lời được cải thiện trông như thế nào:

# create the set just once
choices = {'a', 'b', 'c'}

x = 'a'
y = 'b'

# the main code can be used in a loop
if x != y:
    z, = choices - {x, y}

Dưới đây là thời gian so sánh với bộ thời gian để hiển thị hiệu suất tương đối:

import timeit, itertools

setup_template = '''
x = %r
y = %r
choices = {'a', 'b', 'c'}
'''

new_version = '''
if x != y:
    z, = choices - {x, y}
'''

original_version = '''
if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'
'''

for x, y in itertools.product('abc', repeat=2):
    print '\nTesting with x=%r and y=%r' % (x, y)
    setup = setup_template % (x, y)
    for stmt, name in zip([original_version, new_version], ['if', 'set']):
        print min(timeit.Timer(stmt, setup).repeat(7, 100000)),
        print '\t%s_version' % name

Đây là kết quả của thời gian:

Testing with x='a' and y='a'
0.0410830974579     original_version
0.00535297393799    new_version

Testing with x='a' and y='b'
0.0112571716309     original_version
0.0524711608887     new_version

Testing with x='a' and y='c'
0.0383319854736     original_version
0.048309803009      new_version

Testing with x='b' and y='a'
0.0175108909607     original_version
0.0508949756622     new_version

Testing with x='b' and y='b'
0.0386209487915     original_version
0.00529098510742    new_version

Testing with x='b' and y='c'
0.0259420871735     original_version
0.0472128391266     new_version

Testing with x='c' and y='a'
0.0423510074615     original_version
0.0481910705566     new_version

Testing with x='c' and y='b'
0.0295209884644     original_version
0.0478219985962     new_version

Testing with x='c' and y='c'
0.0383579730988     original_version
0.00530385971069    new_version

Các mốc thời gian này cho thấy rằng hiệu suất của phiên bản gốc thay đổi khá nhiều tùy thuộc vào việc câu lệnh if nào được kích hoạt bởi các giá trị đầu vào khác nhau.


2
Thử nghiệm của bạn có vẻ sai lệch. Cái gọi là "set_version" chỉ đôi khi nhanh hơn vì nó được bảo vệ bởi một ifcâu lệnh bổ sung .
ekhumoro

2
@ekhumoro Đó là những gì đặc tả vấn đề yêu cầu: "kiểm tra xem xy có phải là các giá trị riêng biệt hay không và nếu có, hãy đặt z thành ký tự thứ ba". Cách nhanh nhất (và đơn giản nhất) để kiểm tra xem các giá trị có khác biệt hay không là x != y. Chỉ khi nào họ là khác biệt làm chúng ta tập-sự khác biệt để xác định ký tự thứ ba :-)
Raymond hettinger

2
Điểm mà tôi đang thực hiện là các bài kiểm tra của bạn không cho thấy rằng cái hoạt động set_versiontốt hơn vì nó dựa trên các bộ ; nó chỉ hoạt động tốt hơn nhờ iftuyên bố bảo vệ .
ekhumoro

1
@ekhumoro Đọc kết quả kiểm tra thật kỳ lạ. Mã thực hiện những gì OP yêu cầu. Thời gian cho thấy hiệu suất so sánh với tất cả các nhóm đầu vào có thể có. Bạn muốn giải thích chúng như thế nào là tùy thuộc vào bạn. Thời gian cho phiên bản sử dụng if x != y: z, = choices - {x, y}giá vé hợp lý so với mã gốc của OP. Tôi không biết khái niệm thiên vị của bạn xuất phát từ đâu - thời gian là như thế nào, và AFAICT, đây vẫn là câu trả lời hay nhất trong số các câu trả lời đã được đăng. Nó vừa sạch sẽ vừa nhanh chóng.
Raymond Hettinger

2
Một số tối ưu hóa đã được thêm vào "phiên bản định sẵn" của Sven, nhưng điều tương tự không được thực hiện đối với "phiên bản nếu". Việc thêm một biện pháp if x != ybảo vệ vào "if-version" có thể sẽ làm cho nó nhất quán hơn và hoạt động tốt hơn so với tất cả các giải pháp khác đã được cung cấp cho đến nay (mặc dù rõ ràng là không dễ đọc và ngắn gọn). "Set_version" của bạn là một giải pháp rất tốt - nó chỉ là không khá tốt như các bài kiểm tra làm cho nó có vẻ ;-)
ekhumoro

18
z = (set('abc') - set(x + y)).pop()

Dưới đây là tất cả các tình huống để cho thấy rằng nó hoạt động:

>>> (set('abc') - set('ab')).pop()   # x is a/b and y is b/a
'c'
>>> (set('abc') - set('bc')).pop()   # x is b/c and y is c/b
'a'
>>> (set('abc') - set('ac')).pop()   # x is a/c and y is c/a
'b'

15

Nếu ba mục trong câu hỏi không phải là "a", "b""c", mà đúng hơn 1, 23, bạn cũng có thể sử dụng một XOR nhị phân:

z = x ^ y

Tổng quát hơn, nếu bạn muốn thiết lập zvới một còn lại của ba số a, bcđưa hai con số xytừ bộ này, bạn có thể sử dụng

z = x ^ y ^ a ^ b ^ c

Tất nhiên bạn có thể tính toán trước a ^ b ^ cnếu con số được cố định.

Cách tiếp cận này cũng có thể được sử dụng với các chữ cái gốc:

z = chr(ord(x) ^ ord(y) ^ 96)

Thí dụ:

>>> chr(ord("a") ^ ord("c") ^ 96)
'b'

Đừng mong đợi bất cứ ai đọc mã này ngay lập tức tìm ra ý nghĩa của nó :)


+1 Giải pháp này có vẻ đẹp và trang nhã; và nếu bạn đặt nó thành chức năng riêng của nó và đặt tên cho số ma thuật 96 thì logic khá dễ theo dõi / duy trì ( xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96). Tuy nhiên, về tốc độ thô thì tốc độ này chậm hơn khoảng 33% so với chuỗi dài của if / elifs; nhưng nhanh hơn 500% so với setphương pháp này.
dr jimbob

@sven Cám ơn giới thiệu các nhà điều hành XOR với tôi, giải pháp của bạn được sạch sẽ và thanh lịch, tôi nghĩ rằng ví dụ này sẽ làm cho nó dính vào bộ não của tôi, Cảm ơn bạn một lần nữa :)
Bunny Rabbit

13

Tôi nghĩ giải pháp của Sven Marnach và FJ là tuyệt vời, nhưng nó không nhanh hơn trong thử nghiệm nhỏ của tôi. Đây là phiên bản được tối ưu hóa của Raymond sử dụng tính toán trước set:

$ python -m timeit -s "choices = set('abc')" \
                   -s "x = 'c'" \
                   -s "y = 'a'" \
                      "z, = choices - set(x + y)"
1000000 loops, best of 3: 0.689 usec per loop

Đây là giải pháp ban đầu:

$ python -m timeit -s "x = 'c'" \
                   -s "y = 'a'" \
                      "if x == 'a' and y == 'b' or x == 'b' and y == 'a':" \
                      "    z = 'c'" \
                      "elif x == 'b' and y == 'c' or x == 'c' and y == 'b':" \
                      "    z = 'a'" \
                      "elif x == 'a' and y == 'c' or x == 'c' and y == 'a':" \
                      "    z = 'b'"
10000000 loops, best of 3: 0.310 usec per loop

Lưu ý rằng đây là đầu vào tồi tệ nhất có thể cho if-statements vì tất cả sáu phép so sánh sẽ phải được thử. Thử nghiệm với tất cả các giá trị cho xycho:

x = 'a', y = 'b': 0.084 usec per loop
x = 'a', y = 'c': 0.254 usec per loop
x = 'b', y = 'a': 0.133 usec per loop
x = 'b', y = 'c': 0.186 usec per loop
x = 'c', y = 'a': 0.310 usec per loop
x = 'c', y = 'b': 0.204 usec per loop

Các setbiến thể dựa trên cho thấy việc thực hiện tương tự cho đầu vào khác nhau, nhưng nó là một cách nhất quán giữa 2 và 8 lần chậm hơn . Lý do là ifbiến thể dựa trên chạy mã đơn giản hơn nhiều: kiểm tra bình đẳng so với băm.

Tôi nghĩ rằng cả hai loại giải pháp đều có giá trị: điều quan trọng là phải biết rằng việc tạo cấu trúc dữ liệu "phức tạp" như tập hợp sẽ khiến bạn tốn kém hiệu suất - trong khi chúng mang lại cho bạn rất nhiều về khả năng đọc và tốc độ phát triển . Các kiểu dữ liệu phức tạp cũng tốt hơn nhiều khi mã thay đổi: thật dễ dàng để mở rộng giải pháp dựa trên tập hợp thành bốn, năm, ... biến trong khi các câu lệnh if nhanh chóng biến thành cơn ác mộng bảo trì.


1
@martinGeisler cảm ơn rất nhiều vì câu trả lời của bạn, tôi không biết rằng chúng ta có thể tính giờ những thứ như thế này trong python. Tôi có cảm giác rằng giải pháp Chessmasters sẽ hoạt động tốt và hiệu quả. các câu trả lời khác và cho bạn biết.
Bunny Rabbit

1
Giải pháp dựa trên tập hợp tối ưu hóa tính ngắn gọnkhả năng đọc (và sự sang trọng ). Nhưng hiệu quả cũng đã được đề cập, vì vậy tôi đã đi và điều tra hiệu suất của các giải pháp được đề xuất.
Martin Geisler

1
@MartinGeisler: Vâng, khi tôi nhận thấy điều này, tôi đã xóa nhận xét của mình. Và tôi thường ít nhất cũng thấy thú vị khi biết cái gì nhanh hơn.
Sven Marnach

1
@BunnyRabbit: mô-đun thời gian rất tốt cho các điểm chuẩn vi mô như thế này. Tất nhiên, bạn nên lập hồ sơ chương trình tổng thể của mình trước để xác định vị trí các nút thắt cổ chai, nhưng khi chúng được xác định, thì thời gian có thể là một cách tuyệt vời để nhanh chóng thử các cách triển khai khác nhau.
Martin Geisler

1
+1 - kiểm tra điểm chuẩn chứng minh chuỗi so sánh đơn giản là hợp lý và nhanh chóng.
Jeff Ferland

8

Hãy thử tùy chọn này, sử dụng từ điển:

z = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}[x+y]

Tất nhiên, nếu x+ychìa khóa không có trong bản đồ, nó sẽ tạo ra một chìa khóa KeyErrormà bạn sẽ phải xử lý.

Nếu từ điển được tính toán trước một lần và được lưu trữ để sử dụng trong tương lai, việc truy cập sẽ nhanh hơn nhiều, vì không phải tạo cấu trúc dữ liệu mới cho mỗi lần đánh giá, chỉ cần nối chuỗi và tra cứu từ điển:

lookup_table = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}
z = lookup_table[x+y]

2
Chỉ cho vui thôi, đây là một tùy chọn dict khác: {1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2]mặc dù vậy, độ phức tạp thêm có lẽ không đáng để lưu lại.
Andrew Clark

2
@FJ: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)] đây là niềm vui ...
Chessmaster

Nó có nhanh hơn mã gốc OP không? nếu vậy, tại sao? Làm cách nào để tính toán giá trị băm nhanh hơn so với so sánh đơn giản?
tối đa

@max nó một phép tính đơn băm, không phải là một bó toàn bộ sự so sánh và biểu thức điều kiện
Óscar López

Thật tuyệt .. Không nhận ra hàm băm nhanh như thế nào!
tối đa

8
z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'

hoặc ít hackish hơn và sử dụng Chuyển nhượng có điều kiện

z = 'a' if ('a' not in x+y) else 'b' if ('b' not in x+y) else 'c'

nhưng có lẽ giải pháp dict nhanh hơn ... bạn phải tính thời gian.


2

Tôi nghĩ nó sẽ như thế này:

z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None

12
len(set((x, y))) == 2là cách đọc nhất để viết x != ymà tôi từng thấy :)
Sven Marnach

Vâng, Sven))) Cảm ơn về bình luận của bạn. Kịch bản này có một ý tưởng cơ bản khác khi tôi bắt đầu viết nó)) Cuối cùng tôi đã quên chỉnh sửa điều đó.
tên là

1

Sử dụng khả năng hiểu danh sách, giả sử giống như những người khác rằng một trong ba trường hợp trong mã của bạn giữ nguyên:

l = ['a', 'b', 'c']
z = [n for n in l if n not in [x,y]].pop()

Hoặc, giống như trong câu trả lời được chấp nhận, tận dụng tuple để giải nén nó,

z, = [n for n in l if n not in [x,y]]

0

Xem nếu điều này hoạt động

if a not in xy
    z= 'a'
if b not in xy
    z='b'
if c not in xy
    z='c'
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.