Cách đánh lừa người dùng thử một số trường hợp thử nghiệm heuristic: Các thuật toán có vẻ đúng, nhưng thực sự không chính xác


105

Để thử kiểm tra xem thuật toán cho một số vấn đề có chính xác hay không, điểm khởi đầu thông thường là thử chạy thuật toán bằng tay trên một số trường hợp thử nghiệm đơn giản - thử một vài trường hợp ví dụ, bao gồm một vài trường hợp "góc đơn giản" ". Đây là một heuristic tuyệt vời: đó là một cách tuyệt vời để nhanh chóng loại bỏ nhiều nỗ lực không chính xác đối với một thuật toán và để hiểu được lý do tại sao thuật toán không hoạt động.

Tuy nhiên, khi học thuật toán, một số sinh viên muốn dừng lại ở đó: nếu thuật toán của họ hoạt động chính xác trên một số ví dụ, bao gồm tất cả các trường hợp góc mà họ có thể nghĩ để thử, thì họ kết luận rằng thuật toán phải chính xác. Luôn có một sinh viên hỏi: "Tại sao tôi cần chứng minh thuật toán của mình đúng, nếu tôi có thể thử nó trong một vài trường hợp thử nghiệm?"

Vì vậy, làm thế nào để bạn đánh lừa "thử một loạt các trường hợp thử nghiệm" heuristic? Tôi đang tìm kiếm một số ví dụ tốt để cho thấy rằng heuristic này là không đủ. Nói cách khác, tôi đang tìm kiếm một hoặc nhiều ví dụ về thuật toán nhìn bề ngoài có vẻ đúng và nó đưa ra câu trả lời đúng cho tất cả các đầu vào nhỏ mà bất kỳ ai cũng có thể đưa ra, nhưng thực sự thuật toán không hoạt động. Có thể thuật toán chỉ hoạt động chính xác trên tất cả các đầu vào nhỏ và chỉ thất bại đối với các đầu vào lớn hoặc chỉ thất bại đối với các đầu vào có mẫu bất thường.

Cụ thể, tôi đang tìm kiếm:

  1. Một thuật toán. Lỗ hổng phải ở cấp độ thuật toán. Tôi không tìm kiếm lỗi thực hiện. (Ví dụ, ở mức tối thiểu, ví dụ phải là bất khả tri về ngôn ngữ và lỗ hổng sẽ liên quan đến các mối quan tâm về thuật toán hơn là các vấn đề về kỹ thuật hoặc triển khai phần mềm.)

  2. Một thuật toán mà ai đó có thể đưa ra một cách hợp lý. Mã giả nên nhìn ít nhất là chính xác (ví dụ, mã bị che khuất hoặc rõ ràng không rõ ràng không phải là một ví dụ tốt). Điểm thưởng nếu đó là một thuật toán mà một số học sinh thực sự nghĩ ra khi cố gắng giải bài tập về nhà hoặc bài kiểm tra.

  3. Một thuật toán sẽ vượt qua một chiến lược kiểm tra thủ công hợp lý với xác suất cao. Một số người thử một vài trường hợp thử nghiệm nhỏ bằng tay sẽ không thể phát hiện ra lỗ hổng. Ví dụ: "mô phỏng QuickCheck bằng tay trên hàng tá trường hợp thử nghiệm nhỏ" sẽ không thể tiết lộ rằng thuật toán không chính xác.

  4. Tốt hơn là, một thuật toán xác định. Tôi đã thấy nhiều sinh viên nghĩ rằng "thử một số trường hợp kiểm tra bằng tay" là một cách hợp lý để kiểm tra xem thuật toán xác định có đúng hay không, nhưng tôi nghi ngờ hầu hết các sinh viên sẽ không cho rằng thử một vài trường hợp kiểm tra là một cách tốt để xác minh xác suất thuật toán. Đối với các thuật toán xác suất, thường không có cách nào để biết liệu bất kỳ đầu ra cụ thể nào là chính xác hay không; và bạn không thể quay tay đủ các ví dụ để thực hiện bất kỳ kiểm tra thống kê hữu ích nào về phân phối đầu ra. Vì vậy, tôi muốn tập trung vào các thuật toán xác định, vì chúng trở nên rõ ràng hơn đối với những quan niệm sai lầm của sinh viên.

Tôi muốn dạy tầm quan trọng của việc chứng minh thuật toán của bạn chính xác và tôi hy vọng sẽ sử dụng một vài ví dụ như thế này để giúp thúc đẩy bằng chứng về tính chính xác. Tôi thích các ví dụ tương đối đơn giản và dễ tiếp cận với sinh viên đại học; các ví dụ yêu cầu máy móc hạng nặng hoặc một tấn nền toán học / thuật toán ít hữu ích hơn. Ngoài ra, tôi không muốn các thuật toán "không tự nhiên"; Mặc dù có thể dễ dàng xây dựng một số thuật toán nhân tạo kỳ lạ để đánh lừa heuristic, nếu nó trông rất không tự nhiên hoặc có một cửa hậu rõ ràng được xây dựng chỉ để đánh lừa heuristic này, nó có thể sẽ không thuyết phục được sinh viên. Bất kỳ ví dụ tốt?


2
Tôi thích câu hỏi của bạn, nó cũng liên quan đến một câu hỏi rất thú vị mà tôi đã thấy trên Toán học vào ngày khác liên quan đến việc từ chối các phỏng đoán với các hằng số lớn. Bạn có thể tìm thấy nó ở đây
ZeroUltimax

1
Một số đào hơn và tôi thấy những hai thuật toán hình học.
ZeroUltimax

@ZeroUltimax Bạn nói đúng, pt trung tâm của 3 pts không colinear không được đảm bảo ở bên trong. Biện pháp khắc phục nhanh chóng là lấy pt trên đường giữa bên trái xa nhất và bên phải xa nhất. Có vấn đề gì khác không?
Được thông báo vào

Tiền đề của câu hỏi này có vẻ kỳ lạ đối với tôi theo cách mà tôi đang gặp khó khăn trong đầu, nhưng tôi nghĩ rằng đó là quá trình thiết kế thuật toán như mô tả là một vấn đề cơ bản bị phá vỡ. Ngay cả đối với những sinh viên không 'dừng lại ở đó' thì nó cũng bị tiêu diệt. 1> viết thuật toán, 2> nghĩ về / chạy các trường hợp thử nghiệm, 3a> dừng hoặc 3b> chứng minh đúng. Bước đầu tiên khá nhiều đã được xác định các lớp đầu vào cho miền vấn đề. Trường hợp góc và thuật toán tự phát sinh từ những trường hợp. (tiếp)
Mr.Mindor

1
Làm thế nào để bạn chính thức phân biệt một lỗi thực hiện với một thuật toán thiếu sót? Tôi đã quan tâm đến câu hỏi của bạn, nhưng đồng thời tôi cảm thấy phiền vì thực tế là tình huống bạn mô tả dường như là quy tắc nhiều hơn là ngoại lệ. Nhiều người kiểm tra những gì họ thực hiện, nhưng họ vẫn thường có lỗi. Ví dụ thứ hai của câu trả lời được đánh giá cao nhất chính xác là một lỗi như vậy.
babou

Câu trả lời:


70

Một lỗi phổ biến tôi nghĩ là sử dụng các thuật toán tham lam, không phải lúc nào cũng là phương pháp đúng, nhưng có thể hoạt động trong hầu hết các trường hợp thử nghiệm.

Ví dụ: Coin mệnh giá, và một số n , nhanh n như một khoản d i : s với càng ít tiền càng tốt.d1,,dknndi

Một cách tiếp cận ngây thơ là sử dụng đồng tiền lớn nhất có thể trước tiên và tham lam tạo ra một khoản tiền như vậy.

Chẳng hạn, các đồng tiền có giá trị , 51 sẽ đưa ra câu trả lời đúng với sự tham lam cho tất cả các số từ 1 đến 14 ngoại trừ số 10 = 6 + 1 + 1 + 1 + 1 = 5 + 5 .65111410=6+1+1+1+1=5+5


10
Đây thực sự là một ví dụ tốt, đặc biệt là một sinh viên thường xuyên bị sai. Bạn không chỉ cần chọn các bộ tiền cụ thể mà cả các giá trị cụ thể để xem thuật toán thất bại.
Raphael

2
Ngoài ra, để tôi nói rằng các sinh viên cũng sẽ thường có bằng chứng sai trong ví dụ này (đưa ra một số lập luận ngây thơ không kiểm tra kỹ hơn), vì vậy có thể học được nhiều hơn một bài học ở đây.
Raphael

2
Hệ thống tiền xu kiểu Anh cũ (trước số thập phân năm 1971) đã có một ví dụ thực tế về điều này. Một thuật toán tham lam để tính ra bốn shilling sẽ sử dụng một nửa vương miện (2½ shilling), một đồng xu một shilling và sáu mươi (shilling). Nhưng giải pháp tối ưu sử dụng hai florin (mỗi lần 2 shilling).
Mark Dominus

1
Thật vậy, trong rất nhiều trường hợp, các thuật toán tham lam có vẻ hợp lý, nhưng không hoạt động - một ví dụ khác là kết hợp lưỡng cực tối đa. Mặt khác, cũng có những ví dụ mà nó có vẻ như là một thuật toán tham lam không nên hoạt động, nhưng nó có: cây bao trùm tối đa.
jkff

62

Tôi ngay lập tức nhớ lại một ví dụ từ R. Backhouse (điều này có thể có trong một trong những cuốn sách của anh ấy). Rõ ràng, ông đã chỉ định một bài tập lập trình trong đó các sinh viên phải viết một chương trình Pascal để kiểm tra sự bằng nhau của hai chuỗi. Một trong những chương trình được bật bởi một sinh viên là:

issame := (string1.length = string2.length);

if issame then
  for i := 1 to string1.length do
    issame := string1.char[i] = string2.char[i];

write(issame);

Bây giờ chúng ta có thể kiểm tra chương trình với các đầu vào sau:

"đại học" "đại học" Đúng; đồng ý

"Khóa học" "khóa học" Đúng; đồng ý

"" "" True; đồng ý

"Đại học" "khóa học" Sai; đồng ý

"Bài giảng" "khóa học" Sai; đồng ý

Tất cả điều này có vẻ rất hứa hẹn: có thể chương trình thực sự hoạt động. Nhưng một thử nghiệm cẩn thận hơn với nói "thuần túy" và "đúng" cho thấy đầu ra bị lỗi. Trong thực tế, chương trình nói "Đúng" nếu các chuỗi có cùng độ dài và cùng ký tự cuối cùng!

Tuy nhiên, thử nghiệm đã khá kỹ lưỡng: chúng tôi có các chuỗi có độ dài khác nhau, các chuỗi có độ dài bằng nhau nhưng nội dung khác nhau và thậm chí các chuỗi bằng nhau. Hơn nữa, sinh viên thậm chí đã kiểm tra và thực hiện mọi chi nhánh. Bạn thực sự không thể tranh luận việc kiểm tra đã bất cẩn ở đây - vì chương trình này thực sự rất đơn giản, thật khó để tìm thấy động lực và năng lượng để kiểm tra nó đủ kỹ lưỡng.


Một ví dụ dễ thương khác là tìm kiếm nhị phân. Trong TAOCP, Knuth nói rằng "mặc dù ý tưởng cơ bản về tìm kiếm nhị phân tương đối đơn giản, nhưng các chi tiết có thể rất khó hiểu". Rõ ràng, một lỗi trong triển khai tìm kiếm nhị phân của Java đã không được chú ý trong một thập kỷ. Đó là một lỗi tràn số nguyên và chỉ được biểu hiện với đầu vào đủ lớn. Chi tiết rắc rối về việc triển khai tìm kiếm nhị phân cũng được Bentley trình bày trong cuốn sách Lập trình Ngọc trai .

Điểm mấu chốt: có thể rất khó để chắc chắn một thuật toán tìm kiếm nhị phân là chính xác bằng cách chỉ kiểm tra nó.


9
Tất nhiên, lỗ hổng khá rõ ràng từ nguồn (nếu bạn đã tự viết một điều tương tự trước đó).
Raphael

3
Ngay cả khi lỗi đơn giản trong chương trình ví dụ được sửa, các chuỗi đưa ra khá nhiều vấn đề thú vị! Đảo ngược chuỗi là một cách cổ điển - cách thực hiện "cơ bản" chỉ bằng cách đảo ngược các byte. Sau đó, mã hóa đi vào chơi. Sau đó thay thế (thường là hai lần). Tất nhiên, vấn đề là không có cách nào dễ dàng chính thức chứng minh phương pháp của bạn là chính xác.
Thông thường

6
Có thể tôi hoàn toàn hiểu sai câu hỏi, nhưng đây dường như là một lỗ hổng trong việc thực hiện chứ không phải là một lỗ hổng trong chính thuật toán .
Mr.Mindor

8
@ Mr.Mindor: làm thế nào bạn có thể biết liệu lập trình viên đã viết ra một thuật toán chính xác và sau đó thực hiện nó không chính xác, hoặc viết ra một thuật toán không chính xác và sau đó thực hiện nó một cách trung thực (tôi ngần ngại nói "chính xác"!)
Steve Jessop

1
@wợi Điều đó gây tranh cãi. Điều rõ ràng với bạn có thể không rõ ràng đối với sinh viên năm thứ nhất.
Juho

30

Ví dụ tốt nhất tôi từng gặp là thử nghiệm nguyên thủy:

đầu vào: số tự nhiên p, p! = 2
đầu ra: là pa nguyên tố hay không?
thuật toán: tính 2 ** (p-1) mod p. Nếu kết quả = 1 thì p là số nguyên tố khác p không.

Điều này hoạt động cho (hầu hết) mọi số, ngoại trừ một vài ví dụ truy cập và người ta thực sự cần một cỗ máy để tìm một mẫu phản trong một khoảng thời gian thực tế. Mẫu phản ứng đầu tiên là 341 và mật độ của mẫu phản ứng thực sự giảm khi tăng p, mặc dù chỉ là về logarit.

Thay vì chỉ sử dụng 2 làm cơ sở của sức mạnh, người ta có thể cải thiện thuật toán bằng cách sử dụng bổ sung, tăng các số nguyên tố nhỏ làm cơ sở trong trường hợp số nguyên tố trước trả về 1. Tuy nhiên, vẫn có ví dụ cho sơ đồ này, cụ thể là các số của Carmichael, mặc dù khá hiếm


Kiểm tra tính nguyên thủy của Fermat là một thử nghiệm xác suất, do đó, tình trạng sau của bạn không chính xác.
Femaref

5
Tất nhiên, đây là một thử nghiệm xác suất nhưng câu trả lời độc đáo cho thấy (nói chung hơn) làm thế nào các thuật toán xác suất nhầm với các thuật toán chính xác có thể là một nguồn gây ra lỗi. nhiều hơn về các số của Carmichael
vzn

2
Đó là một ví dụ hay, với một hạn chế: đối với việc sử dụng thực tế kiểm tra tính nguyên thủy mà tôi quen thuộc, cụ thể là tạo khóa mật mã bất đối xứng, chúng tôi sử dụng thuật toán xác suất! Các con số quá lớn đối với các thử nghiệm chính xác (nếu chúng không phù hợp với tiền điện tử vì các khóa có thể được tìm thấy bởi lực lượng vũ phu trong thời gian thực).
Gilles

1
giới hạn mà bạn đề cập là thực tế, không phải lý thuyết và các thử nghiệm cơ bản trong các hệ thống tiền điện tử, ví dụ RSA phải chịu những thất bại hiếm gặp / rất khó khả thi vì chính xác những lý do này, một lần nữa nhấn mạnh tầm quan trọng của ví dụ. tức là trong thực tế đôi khi giới hạn này được chấp nhận là không thể tránh khỏi. có các thuật toán thời gian P để kiểm tra tính nguyên thủy, ví dụ AKS nhưng chúng mất quá nhiều thời gian cho các số "nhỏ hơn" được sử dụng trong thực tế.
vzn

Nếu bạn kiểm tra không chỉ với 2 p, mà với p cho 50 giá trị ngẫu nhiên khác nhau 2 ≤ a <p, thì hầu hết mọi người sẽ biết đó là xác suất, nhưng với những thất bại rất có thể là do máy tính của bạn tạo ra sự cố. Câu trả lời sai. Với 2 p, 3 p, 5 p và 7 p, thất bại đã rất hiếm.
gnasher729

21

Đây là một cái được ném vào tôi bởi đại diện google tại một hội nghị mà tôi đã tham dự. Nó được mã hóa bằng C, nhưng nó hoạt động trong các ngôn ngữ khác sử dụng tài liệu tham khảo. Xin lỗi vì phải viết mã trên [cs.se], nhưng đó là cách duy nhất để minh họa nó.

swap(int& X, int& Y){
    X := X ^ Y
    Y := X ^ Y
    X := X ^ Y
}

Thuật toán này sẽ hoạt động cho bất kỳ giá trị nào được cung cấp cho x và y, ngay cả khi chúng có cùng giá trị. Tuy nhiên, nó sẽ không hoạt động nếu nó được gọi là hoán đổi (x, x). Trong tình huống đó, x kết thúc bằng 0. Bây giờ, điều này có thể không thỏa mãn bạn, vì bằng cách nào đó bạn có thể chứng minh thao tác này là chính xác về mặt toán học, nhưng vẫn quên trường hợp cạnh này.


1
Thủ thuật đó đã được sử dụng trong cuộc thi C ngầm để tạo ra một triển khai RC4 thiếu sót . Đọc bài viết đó một lần nữa, tôi nhận thấy rằng bản hack này có thể được gửi bởi @DW
CodeInChaos

7
Lỗ hổng này thực sự rất tinh tế - tuy nhiên lỗ hổng này là ngôn ngữ cụ thể, vì vậy nó không thực sự là một lỗ hổng trong thuật toán; đó là một lỗ hổng trong việc thực hiện. Người ta có thể đưa ra các ví dụ khác về sự kỳ quặc về ngôn ngữ giúp dễ dàng che giấu những sai sót tinh tế, nhưng đó không thực sự là thứ tôi đang tìm kiếm (tôi đang tìm kiếm thứ gì đó ở mức độ trừu tượng của thuật toán). Trong mọi trường hợp, lỗ hổng này không phải là một minh chứng lý tưởng cho giá trị của bằng chứng; trừ khi bạn đã suy nghĩ về răng cưa, cuối cùng bạn có thể xem xét cùng một vấn đề khi bạn viết ra "bằng chứng" chính xác của mình.
DW

Đó là lý do tại sao tôi ngạc nhiên điều này đã được bình chọn rất cao.
ZeroUltimax

2
@DW Đó là vấn đề làm thế nào mô hình bạn xác định thuật toán. Nếu bạn đi xuống một mức độ mà các tham chiếu bộ nhớ là rõ ràng (chứ không phải là mô hình phổ biến giả định không có chia sẻ), đây là một lỗ hổng thuật toán. Lỗ hổng không thực sự là ngôn ngữ cụ thể, nó xuất hiện trong bất kỳ ngôn ngữ nào hỗ trợ chia sẻ các tham chiếu bộ nhớ.
Gilles

16

Có cả một loại thuật toán vốn đã khó kiểm tra: bộ tạo số giả ngẫu nhiên . Bạn không thể kiểm tra một đầu ra duy nhất mà phải điều tra (nhiều) loạt đầu ra bằng các phương tiện thống kê. Tùy thuộc vào những gì và cách bạn kiểm tra, bạn cũng có thể bỏ lỡ các đặc điểm không ngẫu nhiên.

Một trường hợp nổi tiếng mà mọi thứ trở nên sai lầm khủng khiếp là RANDU . Nó đã vượt qua sự xem xét có sẵn tại thời điểm đó - mà không xem xét hành vi của các bộ dữ liệu đầu ra tiếp theo. Bộ ba đã cho thấy rất nhiều cấu trúc:

Về cơ bản, các thử nghiệm không bao gồm tất cả các trường hợp sử dụng: trong khi sử dụng RANDU một chiều là (hầu hết là tốt), nó không hỗ trợ sử dụng nó để lấy mẫu các điểm ba chiều (theo cách này).

Lấy mẫu giả ngẫu nhiên thích hợp là một công việc khó khăn. May mắn thay, có những bộ thử nghiệm mạnh mẽ có những ngày, ví dụ như dieharder chuyên ném tất cả các số liệu thống kê mà chúng ta biết vào một máy phát được đề xuất. Đủ chưa?

Công bằng mà nói, tôi không biết bạn có thể chứng minh điều gì cho PRNGs một cách khả thi.


2
ví dụ đẹp tuy nhiên thực tế nói chung không có cách nào để chứng minh bất kỳ PRNG nào không có lỗ hổng, chỉ có một hệ thống phân cấp vô hạn của các bài kiểm tra yếu hơn và mạnh hơn. thực sự chứng minh một cái là "ngẫu nhiên" theo bất kỳ ý nghĩa nghiêm ngặt nào có lẽ là không thể giải quyết được (dù không thấy rằng đã được chứng minh).
vzn

1
Đó là một ý tưởng tốt về một cái gì đó khó kiểm tra, nhưng RNG cũng khó để chứng minh. PRNG không dễ bị lỗi thực thi đến mức bị chỉ định xấu. Các thử nghiệm như diehard tốt cho một số mục đích sử dụng, nhưng đối với tiền điện tử, bạn có thể vượt qua diehard và vẫn bị cười ra khỏi phòng. Không có CSPRNG nào được chứng minh an toàn của CBSRNG, điều tốt nhất bạn có thể hy vọng là chứng minh rằng nếu CSPRNG của bạn bị hỏng thì AES cũng vậy.
Gilles

@Gilles Tôi đã không cố gắng đi vào tiền điện tử, chỉ là sự ngẫu nhiên thống kê (tôi nghĩ hai người có khá nhiều yêu cầu trực giao). Tôi có nên làm rõ điều đó trong câu trả lời?
Raphael

1
Tính ngẫu nhiên của tiền điện tử ngụ ý tính ngẫu nhiên thống kê. Không có định nghĩa chính thức về mặt toán học, theo như tôi biết, ngoài lý tưởng (và mâu thuẫn với khái niệm PRNG được thực hiện trên máy Turing xác định) khái niệm về tính ngẫu nhiên lý thuyết thông tin. Có phải tính ngẫu nhiên thống kê có một định nghĩa chính thức ngoài phạm vi phải độc lập với các bản phân phối mà chúng tôi sẽ kiểm tra nó với chống lại không?
Gilles

1
@vzn: ý nghĩa của một chuỗi số ngẫu nhiên có thể được định nghĩa theo nhiều cách có thể, nhưng một cách đơn giản là "độ phức tạp Komolgorov lớn". Trong trường hợp đó, thật dễ dàng để chỉ ra rằng việc xác định tính ngẫu nhiên là không thể giải quyết được.
cody

9

2D tối đa cục bộ

n×nA

(i,j)A[i,j]

A[i,j+1],A[i,j1],A[i1,j],A[i+1,j]A

0134323125014013

sau đó mỗi ô được in đậm là một cực đại cục bộ. Mỗi mảng không trống có ít nhất một tối đa cục bộ.

O(n2)

AXXA(i,j)X(i,j)(i,j)

AXAX(i,j)A

AA

(i,j)AA(i,j)

n2×n2A(i,j)

T(n)n×nT(n)=T(n/2)+O(n)T(n)=O(n)

Do đó, chúng tôi đã chứng minh định lý sau:

O(n)n×n

Hay là chúng ta?


T(n)=O(nlogn)T(n)=T(n/2)+O(n)

2
Đây là một ví dụ đẹp ! Tôi thích nó. Cảm ơn bạn. (Cuối cùng tôi đã tìm ra lỗ hổng trong thuật toán này. Từ dấu thời gian bạn có thể bị ràng buộc thấp hơn về thời gian tôi mất. Tôi rất xấu hổ khi tiết lộ thời gian thực tế. :-)
DW

1
O(n)

8

Đây là những ví dụ nguyên thủy, bởi vì chúng phổ biến.

(1) Tính nguyên thủy trong SymPy. Số 1789 . Có một bài kiểm tra không chính xác được đưa lên một trang web nổi tiếng đã không thất bại cho đến sau 10 ^ 14. Trong khi sửa chữa là chính xác, nó chỉ là vá lỗ hổng chứ không phải xem xét lại vấn đề.

(2) Tính nguyên thủy trong Perl 6. Perl6 đã thêm is-Prime sử dụng một số thử nghiệm MR với các bazơ cố định. Có những mẫu được biết đến, nhưng chúng khá lớn vì số lượng thử nghiệm mặc định là rất lớn (về cơ bản che giấu vấn đề thực sự bằng cách làm giảm hiệu suất). Điều này sẽ được giải quyết sớm.

(3) Tính nguyên thủy trong FLINT. n_isprime () trả về true cho vật liệu tổng hợp , kể từ khi cố định. Về cơ bản vấn đề tương tự như SymPy. Sử dụng cơ sở dữ liệu Feitsma / Galway của các giả danh SPRP-2 đến 2 ^ 64 bây giờ chúng ta có thể kiểm tra những điều này.

(4) Toán học của Perl :: Tính nguyên thủy. is_aks_prime bị hỏng . Trình tự này có vẻ giống với rất nhiều triển khai AKS - rất nhiều mã hoạt động do tai nạn (ví dụ như bị mất ở bước 1 và cuối cùng đã thực hiện toàn bộ bằng cách phân chia thử nghiệm) hoặc không hoạt động cho các ví dụ lớn hơn. Thật không may, AKS chậm đến mức khó kiểm tra.

(5) Par-pre-2.2 is_prime. Toán :: vé Pari . Nó đã sử dụng 10 cơ sở ngẫu nhiên cho các thử nghiệm MR (với hạt giống cố định khi khởi động, thay vì hạt giống cố định của GMP mỗi cuộc gọi). Nó sẽ cho bạn biết 9 là số nguyên tố khoảng 1 trên mỗi cuộc gọi 1M. Nếu bạn chọn đúng số, bạn có thể khiến nó thất bại tương đối thường xuyên, nhưng các số đó trở nên thưa hơn, vì vậy nó không hiển thị nhiều trong thực tế. Họ đã thay đổi thuật toán và API.

Điều này không sai nhưng đây là một bài kiểm tra xác suất kinh điển: Bạn cho bao nhiêu vòng, mpz_probab_prime_p? Nếu chúng tôi cho nó 5 vòng, có vẻ như nó hoạt động tốt - các con số phải vượt qua bài kiểm tra Fermat cơ sở-210 và sau đó là 5 bài kiểm tra Miller-Rabin cơ sở được chọn trước. Bạn sẽ không tìm thấy một ví dụ cho đến 3892757297131 (với GMP 5.0.1 hoặc 6.0.0a), vì vậy bạn phải thực hiện nhiều thử nghiệm để tìm thấy nó. Nhưng có hàng ngàn mẫu phản dưới 2 ^ 64. Vì vậy, bạn tiếp tục nâng số lượng. Bao xa? Có một kẻ thù? Làm thế nào quan trọng là một câu trả lời đúng? Bạn có nhầm lẫn giữa các cơ sở ngẫu nhiên với các cơ sở cố định? Bạn có biết kích thước đầu vào nào bạn sẽ được cung cấp?

1016

Đây là khá khó để kiểm tra chính xác. Chiến lược của tôi bao gồm các thử nghiệm đơn vị rõ ràng, cộng với các trường hợp cạnh, cộng với các ví dụ về các lỗi đã thấy trước hoặc trong các gói khác, thử nghiệm so với các cơ sở dữ liệu đã biết nếu có thể (ví dụ: nếu bạn thực hiện một thử nghiệm MR cơ sở 2, thì bạn đã giảm được tính toán không khả thi nhiệm vụ kiểm tra 2 ^ 64 số để kiểm tra khoảng 32 triệu số) và cuối cùng, rất nhiều thử nghiệm ngẫu nhiên sử dụng gói khác làm tiêu chuẩn. Điểm cuối cùng hoạt động cho các chức năng như nguyên thủy, nơi có đầu vào khá đơn giản và đầu ra đã biết, nhưng khá nhiều nhiệm vụ như thế này. Tôi đã sử dụng điều này để tìm lỗi trong cả mã phát triển của riêng mình cũng như các vấn đề không thường xuyên trong các gói so sánh. Nhưng với không gian đầu vào vô hạn, chúng tôi không thể kiểm tra mọi thứ.

Để chứng minh tính đúng đắn, đây là một ví dụ nguyên thủy khác. Các phương thức BLS75 và ECPP có khái niệm về chứng chỉ nguyên thủy. Về cơ bản sau khi họ từ bỏ các tìm kiếm để tìm các giá trị hoạt động cho bằng chứng của mình, họ có thể xuất chúng theo định dạng đã biết. Sau đó, người ta có thể viết một trình xác minh hoặc nhờ người khác viết nó. Chúng chạy rất nhanh so với việc tạo và bây giờ (1) cả hai đoạn mã đều không chính xác (do đó tại sao bạn thích các lập trình viên khác cho trình xác minh) hoặc (2) toán học đằng sau ý tưởng chứng minh là sai. # 2 luôn luôn có thể, nhưng những thứ này thường được xuất bản và xem xét bởi nhiều người (và trong một số trường hợp đủ dễ để bạn tự đi qua).

So sánh, các phương pháp như AKS, APR-CL, phân chia thử nghiệm hoặc thử nghiệm Rabin xác định, tất cả đều không tạo ra đầu ra nào ngoài "số nguyên tố" hoặc "hỗn hợp". Trong trường hợp sau, chúng ta có thể có một yếu tố do đó có thể xác minh, nhưng trong trường hợp trước, chúng ta không còn gì ngoài một bit đầu ra này. Chương trình có hoạt động chính xác không? Không biết.

Điều quan trọng là kiểm tra phần mềm trên nhiều hơn một vài ví dụ về đồ chơi, và cũng trải qua một số ví dụ ở mỗi bước của thuật toán và nói "đưa ra đầu vào này, có nghĩa là tôi ở đây với trạng thái này không?"


1
Nhiều trong số này trông giống như (1) lỗi thực hiện (thuật toán cơ bản là đúng nhưng nó không được thực hiện chính xác), điều này rất thú vị nhưng không phải là điểm của câu hỏi này, hoặc (2) một lựa chọn có chủ ý, có chủ ý để chọn một cái gì đó là nhanh và chủ yếu hoạt động nhưng có thể thất bại với xác suất rất nhỏ (đối với mã đang thử nghiệm với một cơ sở ngẫu nhiên hoặc một vài cơ sở cố định / ngẫu nhiên, tôi hy vọng rằng bất cứ ai chọn làm điều đó đều biết rằng họ đang thực hiện một sự đánh đổi hiệu suất).
DW

Bạn đã đúng ở điểm đầu tiên - thuật toán chính xác + lỗi không phải là điểm, mặc dù cuộc thảo luận và các ví dụ khác cũng đang kết hợp chúng. Trường đã chín muồi với các phỏng đoán hoạt động với số lượng nhỏ nhưng không chính xác. Đối với điểm (2) đúng với một số người, nhưng ví dụ # 1 và # 3 của tôi không phải là trường hợp này - người ta tin rằng thuật toán là chính xác (5 cơ sở này cho kết quả đã được chứng minh cho các số dưới 10 ^ 16), sau đó phát hiện ra rằng nó không phải là.
DanaJ

Đây không phải là một vấn đề cơ bản với các bài kiểm tra giả nguyên thủy sao?
asmeker

asmeker, có trong số 2 của tôi và cuộc thảo luận sau đó về họ. Nhưng # 1 và # 3 đều là những trường hợp sử dụng Miller-Rabin với các cơ sở đã biết để đưa ra kết quả chính xác xác định dưới ngưỡng. Vì vậy, trong trường hợp này, "thuật toán" (sử dụng thuật ngữ lỏng lẻo để khớp với OP) là không chính xác. # 4 không phải là một thử nghiệm cơ bản có thể xảy ra, nhưng như DW đã chỉ ra, thuật toán hoạt động tốt, đó chỉ là việc thực hiện rất khó khăn. Tôi bao gồm nó bởi vì nó dẫn đến một tình huống tương tự: kiểm tra là cần thiết, và bạn vượt xa các ví dụ đơn giản trước khi bạn nói nó hoạt động như thế nào?
DanaJ

Một số bài đăng của bạn có vẻ phù hợp với câu hỏi trong khi một số thì không (nhận xét của cf @ DW). Vui lòng xóa các ví dụ (và nội dung khác) không trả lời câu hỏi.
Raphael

7

Thuật toán xáo trộn của Fisher-Yates-Knuth là một ví dụ (thực tế) và là một trong những tác giả của trang web này đã bình luận về nó .

Thuật toán tạo ra một hoán vị ngẫu nhiên của một mảng nhất định là:

 // To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ i
       exchange a[j] and a[i]

ij0ji

Một thuật toán "ngây thơ" có thể là:

 // To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ n-1
       exchange a[j] and a[i]

Trong đó trong vòng lặp, phần tử được hoán đổi được chọn từ tất cả các phần tử có sẵn. Tuy nhiên, điều này tạo ra sự lấy mẫu sai lệch của các hoán vị (một số được biểu thị quá mức, v.v.)

Trên thực tế, người ta có thể đến với việc xáo trộn câu cá bằng cách sử dụng một phân tích đếm đơn giản (hoặc ngây thơ) .

nn!=n×n1×n2..nn1

Vấn đề chính với việc xác minh xem thuật toán xáo trộn có chính xác hay không ( sai lệch hay không ) là do số liệu thống kê, cần một số lượng lớn mẫu. Các bài viết codinghorror tôi liên kết ở trên giải thích chính xác điều đó (và với các bài kiểm tra thực tế).


1
Xem ở đây để có một bằng chứng chính xác cho một thuật toán xáo trộn.
Raphael

5

Ví dụ tốt nhất (đọc: điều tôi đau mông nhất) tôi từng thấy phải làm với phỏng đoán collatz. Tôi đã tham gia một cuộc thi lập trình (với giải thưởng 500 đô la ở vị trí đầu tiên), trong đó một trong những vấn đề là tìm ra số bước tối thiểu cần thiết để hai số đạt được cùng một số. Giải pháp tất nhiên là luân phiên bước từng người cho đến khi cả hai đạt được điều gì đó đã thấy trước đó. Chúng tôi đã đưa ra một loạt các số (tôi nghĩ rằng nó nằm trong khoảng từ 1 đến 1000000) và nói rằng phỏng đoán collatz đã được xác minh lên đến 2 ^ 64 nên tất cả các số chúng tôi đưa ra cuối cùng sẽ hội tụ ở 1. Tôi đã sử dụng 32 bit số nguyên để làm các bước với tuy nhiên. Nó chỉ ra rằng có một số tối nghĩa trong khoảng từ 1 đến 1000000 (170 nghìn thứ gì đó) sẽ khiến số nguyên 32 bit bị tràn trong thời gian đáo hạn. Trên thực tế, những con số này cực kỳ hiếm dưới 2 ^ 31. Chúng tôi đã thử nghiệm hệ thống của chúng tôi để tìm số HUGE lớn hơn 1000000 để "đảm bảo" rằng việc tràn không xảy ra. Hóa ra một con số nhỏ hơn nhiều mà chúng tôi không kiểm tra đã gây ra tràn. Becuase tôi đã sử dụng "int" thay vì "long" Tôi chỉ nhận được giải thưởng 300 đô la thay vì giải thưởng 500 đô la.


5

Các Knapsack 0/1 vấn đề là một trong đó hầu hết các sinh viên nghĩ là có thể giải quyết bằng một thuật toán tham lam. Điều đó xảy ra thường xuyên hơn nếu trước đây bạn hiển thị một số giải pháp tham lam như phiên bản vấn đề của Knapsack nơi thuật toán tham lam hoạt động .

Đối với những vấn đề đó, trong lớp , tôi nên đưa ra bằng chứng cho Knapsack 0/1 ( lập trình động ) để loại bỏ mọi nghi ngờ và cho phiên bản vấn đề tham lam quá. Trên thực tế, cả hai bằng chứng đều không tầm thường và các sinh viên có thể thấy chúng rất hữu ích. Ngoài ra, có một nhận xét về điều này trong CLRS 3ed , Chương 16, Trang 425-427 .

Vấn đề: kẻ trộm đã cướp một cửa hàng và có thể mang trọng lượng tối đa của W vào chiếc ba lô của họ. Có n mục và mục thứ i có trọng lượng wi và trị giá vi đô la. Những tên trộm nên lấy? để tối đa hóa lợi ích của mình ?

Vấn đề Knapsack 0/1 : Thiết lập giống nhau, nhưng các mục có thể không bị vỡ thành các phần nhỏ hơn , do đó, kẻ trộm có thể quyết định lấy một vật phẩm hoặc để lại nó (lựa chọn nhị phân), nhưng có thể không lấy một phần của vật phẩm .

Và bạn có thể nhận được từ sinh viên một số ý tưởng hoặc thuật toán theo cùng ý tưởng như vấn đề phiên bản tham lam, đó là:

  • Lấy tổng dung lượng của túi và đặt càng nhiều càng tốt đối tượng có giá trị nhất và lặp lại phương pháp này cho đến khi bạn không thể đặt nhiều vật hơn vì túi đầy hoặc không có vật nào có trọng lượng nhỏ hơn bằng nhau để đặt vào trong túi.
  • Một cách sai lầm khác là suy nghĩ: đặt các mặt hàng nhẹ hơn và đặt những thứ sau cao nhất đến giá thấp nhất.
  • ...

Nó có hữu ích cho bạn không? Trên thực tế, chúng ta biết vấn đề tiền xu là một phiên bản vấn đề về chiếc ba lô. Nhưng, có nhiều ví dụ hơn trong rừng các vấn đề của chiếc ba lô, ví dụ, về Knapsack 2D (điều đó thực sự hữu ích khi bạn muốn cắt gỗ để làm đồ nội thất , tôi thấy ở một địa phương từ thành phố của tôi), tôi nghĩ rất phổ biến rằng tham lam làm việc ở đây, quá, nhưng không.


Tham lam đã được đề cập trong câu trả lời được chấp nhận , nhưng vấn đề Knapsack nói riêng rất phù hợp để đặt một số bẫy.
Raphael

3

Một lỗi phổ biến là thực hiện các thuật toán xáo trộn sai. Xem thảo luận trên wikipedia .

n!nn(n1)n


1
Đó là một lỗi tốt, nhưng không phải là một minh họa tốt để đánh lừa các trường hợp thử nghiệm heuristic, vì thử nghiệm không thực sự áp dụng cho các thuật toán xáo trộn (nó ngẫu nhiên, vậy bạn sẽ kiểm tra nó như thế nào? Làm thế nào bạn phát hiện ra điều đó khi nhìn vào đầu ra?)
DW

Bạn kiểm tra nó thống kê tất nhiên. Tính ngẫu nhiên thống nhất khác xa với "bất cứ điều gì có thể xảy ra trong đầu ra". Bạn sẽ không nghi ngờ nếu một chương trình được cho là mô phỏng một con xúc xắc đã cho bạn 100 3 giây liên tiếp?
Per Alexandersson

Một lần nữa, tôi đang nói về sinh viên heuristic "thử một số trường hợp kiểm tra bằng tay". Tôi đã thấy nhiều sinh viên nghĩ rằng đây là một cách hợp lý để kiểm tra xem thuật toán xác định có đúng hay không, nhưng tôi nghi ngờ họ sẽ không cho rằng đó là cách tốt để kiểm tra xem thuật toán xáo trộn có đúng không (vì thuật toán xáo trộn là ngẫu nhiên, có không có cách nào để biết liệu bất kỳ đầu ra cụ thể nào là chính xác hay không, trong mọi trường hợp, bạn không thể quay tay đủ các ví dụ bằng tay để thực hiện bất kỳ kiểm tra thống kê hữu ích nào). Vì vậy, tôi không hy vọng các thuật toán xáo trộn sẽ giúp ích nhiều để làm sáng tỏ quan niệm sai lầm phổ biến.
DW

1
@PerAlexandersson: Ngay cả khi bạn chỉ tạo một shuffle, nó không thể thực sự ngẫu nhiên khi sử dụng MT với n> 2080. Bây giờ độ lệch so với dự kiến ​​sẽ rất nhỏ, vì vậy bạn có thể không quan tâm ... nhưng điều này áp dụng ngay cả khi bạn tạo ra ít hơn nhiều so với khoảng thời gian (như asmeker đã chỉ ra ở trên).
Charles

2
Câu trả lời này dường như đã bị làm cho lỗi thời hơn bởi Nikos M. ?
Raphael

2

Pythons PEP450 đã giới thiệu các chức năng thống kê vào thư viện chuẩn có thể được quan tâm. Là một phần của sự biện minh cho việc có một hàm tính toán phương sai trong thư viện chuẩn của trăn, tác giả Steven D'Aprano viết:

def variance(data):
        # Use the Computational Formula for Variance.
        n = len(data)
        ss = sum(x**2 for x in data) - (sum(data)**2)/n
        return ss/(n-1)

Những điều trên dường như là đúng với một bài kiểm tra thông thường:

>>> data = [1, 2, 4, 5, 8]
>>> variance(data)
  7.5

Nhưng việc thêm hằng số vào mọi điểm dữ liệu sẽ không làm thay đổi phương sai:

>>> data = [x+1e12 for x in data]
>>> variance(data)
  0.0

Và phương sai không bao giờ nên âm:

>>> variance(data*100)
  -1239429440.1282566

Vấn đề là về số và độ chính xác bị mất. Nếu bạn muốn độ chính xác tối đa thì bạn phải đặt hàng các hoạt động của mình theo một cách nhất định. Một triển khai ngây thơ dẫn đến kết quả không chính xác vì sự thiếu chính xác quá lớn. Đó là một trong những vấn đề khóa học số của tôi ở trường đại học là về.


1
n1

2
@Raphael: Mặc dù công bằng, thuật toán được chọn nổi tiếng là một lựa chọn kém cho dữ liệu dấu phẩy động.

2
Nó không chỉ đơn giản là về việc triển khai hoạt động về số và độ chính xác bị mất. Nếu bạn muốn độ chính xác tối đa thì bạn phải đặt hàng các hoạt động của mình theo một cách nhất định. Đó là một trong những vấn đề khóa học số của tôi ở trường đại học là về.
Christian

Ngoài nhận xét chính xác của Raphael, một thiếu sót của ví dụ này là tôi không nghĩ rằng một bằng chứng chính xác sẽ giúp tránh lỗ hổng này. Nếu bạn không nhận thức được sự tinh tế của số học dấu phẩy động, bạn có thể nghĩ rằng mình đã chứng minh điều này đúng (bằng cách chứng minh rằng công thức này hợp lệ). Vì vậy, đây không phải là một ví dụ lý tưởng để dạy sinh viên tại sao điều quan trọng là phải chứng minh thuật toán của họ đúng. Nếu sinh viên nhìn thấy ví dụ này, sự nghi ngờ của tôi là thay vào đó họ sẽ rút ra bài học "công cụ tính toán số / dấu phẩy động là khó khăn".
DW

1

Mặc dù điều này có thể không hoàn toàn là những gì bạn đang theo đuổi, nhưng chắc chắn dễ hiểu và thử nghiệm một số trường hợp nhỏ mà không có bất kỳ suy nghĩ nào khác sẽ dẫn đến một thuật toán không chính xác.

nn2+n+410<dd divides n2+n+41d<n2+n+41

Giải pháp đề xuất :

int f(int n) {
   return 1;
}

n=0,1,2,,39n=40

Cách tiếp cận "thử một số trường hợp nhỏ và suy ra thuật toán từ kết quả" này xuất hiện thường xuyên (mặc dù không cực kỳ như ở đây) trong các cuộc thi lập trình trong đó áp lực phải đưa ra thuật toán (a) nhanh chóng thực hiện và (b) ) có thời gian chạy nhanh.


5
Tôi không nghĩ rằng đây là một ví dụ rất hay, bởi vì ít người sẽ cố gắng tìm ra các ước của một đa thức bằng cách quay lại 1.
Brian S

1
nn3n

Điều này có thể có liên quan, theo nghĩa là trả về giá trị không đổi cho các ước số (hoặc một phép tính toán khác), có thể là kết quả của một cách tiếp cận thuật toán sai cho một vấn đề (ví dụ: một vấn đề thống kê hoặc không xử lý các trường hợp cạnh của thuật toán). Tuy nhiên, câu trả lời cần được đọc lại
Nikos M.

@NikosM. Heh. Tôi cảm thấy như mình đang đánh một con ngựa chết ở đây, nhưng đoạn thứ hai của câu hỏi nói rằng "nếu thuật toán của họ hoạt động chính xác trên một số ví dụ, bao gồm tất cả các trường hợp góc mà họ có thể nghĩ để thử, thì họ kết luận rằng thuật toán phải đúng. Luôn có một sinh viên hỏi: "Tại sao tôi cần chứng minh thuật toán của mình đúng, nếu tôi có thể thử nó trong một vài trường hợp thử nghiệm?" Trong trường hợp này, trong 40 giá trị đầu tiên (nhiều hơn một học sinh có khả năng thử), trả về 1 là chính xác. Dường như với tôi đó là điều mà OP đang tìm kiếm.
Rick Decker

Ok, vâng, nhưng điều này như phrased là tầm thường (có thể là điển hình chính xác), nhưng không phải trong tinh thần của câu hỏi. Vẫn cần phải đọc lại
Nikos M.
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.