Câu hỏi Làm thế nào tôi có thể tìm thấy giá trị còn thiếu chính xác hơn?


Mã sau sẽ kiểm tra nếu x và y là các giá trị riêng biệt (các biến x, y, z chỉ có thể có giá trị a, b, hoặc là c) và nếu có, bộ z với ký 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ể thực hiện điều này theo cách ngắn gọn, dễ đọc hơn và hiệu quả hơn không?


76
2018-01-09 17:21


gốc


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
Cảm ơn tất cả các câu trả lời, tôi đoán tôi sẽ sử dụng các giải pháp bằng cách sử dụng thiết lập như là hợp lý của nó nhanh chóng và dễ đọc, Bảng tra cứu dựa trên câu trả lời của Óscar López cũng intresting. - Bunny Rabbit


Các câu trả lời:


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 nắm giữ. Nếu đây là trường hợp, 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 ý kiến, bạn cũng có thể sử dụng tuple giải nén để trích xuất các phần tử đơn từ bộ:

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

62
2018-01-09 17:23



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ó chính xác hơn bằng cách sử dụng các bộ chữ, như sau: z = ({'a', 'b', 'c'} - {x, y}).pop() - Taymon
Các pop () là không cần thiết và chậm. Thay vào đó, hãy sử dụng giải nén tuple. Ngoài ra, set(("a", "b", "c")) là bất biến vì vậy nó có thể được precomputed một thời gian, chỉ để lại differencing thiết lập đượ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, sau đó chúng tôi không quan tâm nhiều về tốc độ). - Raymond Hettinger
@ Ed: Tôi biết, nhưng OP đã không chỉ định phải làm gì khi x == ynên tôi bỏ qua bài kiểm tra. Thật dễ dàng để thêm if x != y: Nếu cần thiết. - Sven Marnach
Cá nhân, tôi sẽ lựa chọn cho người đầu tiên vì nó rõ ràng hơn một dấu phẩy ngẫu nhiên. - John
Bạn có thể muốn thay thế set(("a", "b", "c")) bởi set("abc"). - kasyc


Các strip là một tùy chọn khác chạy nhanh cho tôi:

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

47
2018-01-09 19:15



+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
Ý tưởng hay, +1; mặc dù tôi thực sự nghĩ rằng "a", "b" và "c" trong bài đăng gốc chỉ là phần giữ chỗ cho các giá trị thực. Giải pháp này không tổng quát với bất kỳ loại giá trị nào khác so với các chuỗi có độ dài 1. - Sven Marnach
@chepner thats sáng tạo! Cảm ơn bạn đã trả lời chepner. - Bunny Rabbit


Mã tuyệt vời của Sven đã làm quá nhiều công việc và đã sử dụng giải nén tuple thay vì pop (). Ngoài ra, nó có thể đã thêm một bảo vệ if x != y để kiểm tra x và y là khác biệt. Đây là câu trả lời được cải thiện 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à các thời gian so sánh với bộ định thời để 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

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


28
2018-01-09 19:06



Thử nghiệm của bạn có vẻ thiên vị. Cái gọi là "set_version" chỉ đôi khi nhanh hơn vì nó được bảo vệ bởi một bổ sung if tuyên bố. - ekhumoro
@ekhumoro Đó là những gì các đặc điểm kỹ thuật vấn đề được gọi là: "kiểm tra nếu x và y là các giá trị khác biệt và nếu có, z đến ký tự thứ ba "Cách nhanh nhất (và thẳng tiến nhất) để kiểm tra xem các giá trị có khác biệt hay không x != y. Chỉ khi chúng khác biệt thì chúng ta có sự khác biệt thiết lập để xác định ký tự thứ ba :-) - Raymond Hettinger
Điểm tôi đã làm là các xét nghiệm của bạn không cho thấy rằng set_version thực hiện tốt hơn bởi vì nó dựa trên bộ; nó chỉ hoạt động tốt hơn vì sự bảo vệ if tuyên bố. - ekhumoro
@ekhumoro Đó là một đọc lạ của kết quả kiểm tra. Mật mã làm những gì OP yêu cầu. Thời gian hiển thị hiệu suất so sánh với tất cả các nhóm đầu vào có thể có. Đó là vào bạn như thế nào bạn muốn giải thích những người. Thời gian cho phiên bản sử dụng if x != y: z, = choices - {x, y}giá vé hợp lý tốt so với mã ban đầu của OP. Tôi không biết khái niệm thiên vị của bạn xuất hiện ở đâu - thời gian là những gì họ đang có, và AFAICT, đây vẫn là câu trả lời hay nhất đã được đăng. Nó vừa sạch vừa nhanh. - Raymond Hettinger
Một số tối ưu hóa đã được thêm vào "set-version" của Sven, nhưng điều tương tự không được thực hiện cho "if-version". Thêm một if x != y bảo vệ cho "phiên bản if" có thể sẽ làm cho nó phù hợp 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 thể đọc được và súc tích). "Set_version" của bạn là một giải pháp rất tốt - nó chỉ không khá tốt như các bài kiểm tra làm cho nó có vẻ ;-) - ekhumoro


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'

18
2018-01-09 17:24





Nếu ba mục được đề cập không "a", "b" và "c"mà đúng hơn là 1, 2 và 3, bạn cũng có thể sử dụng một XOR nhị phân:

z = x ^ y

Nói chung, nếu bạn muốn đặt z đến một trong ba số còn lại a, b và c đưa ra hai số x và y từ 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 ^ c nếu các 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ó :)


15
2018-01-09 18:02



1 Giải pháp này có vẻ tốt đẹp và thanh lịch; và nếu bạn biến nó thành hàm riêng của nó và cung cấp cho số ma thuật 96 một cái tên 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, xét về tốc độ thô, tốc độ này chậm hơn khoảng 33% so với chuỗi dài if / elifS; nhưng nhanh hơn 500% so với set phương pháp. - dr jimbob
@sven Cảm ơn bạn đã giới thiệu nhà điều hành XOR với tôi, Giải pháp của bạn là 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 một lần nữa :) - Bunny Rabbit


Tôi nghĩ rằng giải pháp của Sven Marnach và F.J là đẹp, nhưng nó không nhanh hơn trong bài kiểm tra nhỏ của tôi. Đây là phiên bản tối ưu hóa của Raymond sử dụng một 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-Báo cáo vì tất cả sáu so sánh sẽ phải được thử. Thử nghiệm với tất cả các giá trị cho x và y cho:

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 setdựa trên biến thể cho thấy hiệu suất tương tự cho các đầu vào khác nhau, nhưng nó luôn nằm giữa Chậm hơn 2 và 8 lần. Lý do là vì ifdựa trên biến thể 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ư bộ chi phí cho bạn điều gì đó về hiệu suất - trong khi chúng cung cấp cho bạn rất nhiều 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 thay đổi mã: thật dễ dàng để mở rộng giải pháp dựa trên thiết lậ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ì.


13
2018-01-09 17:39



@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 tôi có thể làm những việc như thế này trong python.I có cảm giác ruột rằng giải pháp Chessmasters sẽ hoạt động tốt và hiệu quả, tôi sẽ thử kiểm tra nó như bạn đã làm các câu trả lời khác và cho bạn biết. - Bunny Rabbit
Giải pháp dựa trên tập hợp tối ưu hóa sự phù hợp và khả năng đọc (và 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
@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ìm thấy nó thú vị ít nhất là để biết những gì là nhanh hơn. - Sven Marnach
@BunnyRabbit: mô-đun timeit rất tốt cho các điểm chuẩn vi mô như thế này. Bạn nên tất nhiên hồ sơ chương trình tổng thể của bạn trước tiên để xác định nơi các nút cổ chai là, 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 triển khai khác nhau với nhau. - Martin Geisler
+1 - kiểm tra điểm chuẩn chứng minh hàng loạt so sánh đơn giản là hợp lý và nhanh chóng. - Jeff Ferland


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+y không có trong bản đồ, nó sẽ tạo ra KeyError mà bạn sẽ phải xử lý.

Nếu từ điển được precomputed một lần duy nhất và được lưu trữ để sử dụng trong tương lai, truy cập sẽ nhanh hơn nhiều, vì không có cấu trúc dữ liệu mới sẽ được tạo ra cho mỗi đánh giá, chỉ cần một chuỗi nố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]

8
2018-01-09 17:45



Chỉ để cho vui, đây là một tùy chọn dict khác: {1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2], phức tạp hơn có lẽ không đáng để lưu không gian. - Andrew Clark
@ F.J: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)]  đây là niềm vui ... - ChessMaster
1 để sáng tạo và có mã nhanh. - Raymond Hettinger
Wohoo nhanh quá! - Bunny Rabbit
Nó có nhanh hơn mã OP ban đầu không? nếu vậy, tại sao? Làm cách nào để tính giá trị băm nhanh hơn so sánh đơn giản? - max


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

hoặc ít bị tấn công và sử dụng Bài tập 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 có thời gian.


8
2018-01-09 17:44





Tôi nghĩ rằng nó sẽ giống như thế:

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

2
2018-01-09 17:38



len(set((x, y))) == 2 là cách dễ đọc nhất để viết x != y 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 nó. - selfnamed


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

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

Hoặc, như trong câu trả lời được chấp nhận, tận dụng lợi thế của bộ dữ liệu để giải nén nó,

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

1
2018-01-09 18:57