“일부 테스트 사례 시도”휴리스틱을 속이는 방법 : 올바르게 보이지만 실제로는 잘못된 알고리즘


105

어떤 문제에 대한 알고리즘이 올바른지 테스트하기 위해 일반적인 출발점은 여러 간단한 테스트 사례에서 직접 알고리즘을 실행하는 것입니다. 몇 가지 간단한 "코너 사례를 포함하여 몇 가지 예제 인스턴스에서 시도해보십시오. ". 이것은 휴리스틱입니다. 알고리즘에 대한 많은 잘못된 시도를 신속하게 제거하고 알고리즘이 작동하지 않는 이유를 이해하는 데 좋은 방법입니다.

그러나 알고리즘을 학습 할 때 일부 학생들은 여기서 그만두려고합니다. 시도 할 수있는 모든 경우를 포함하여 소수의 예제에서 알고리즘이 올바르게 작동하면 알고리즘이 정확해야한다는 결론을 내립니다. "여러 가지 테스트 사례에서 알고리즘을 시도 할 수 있다면 왜 알고리즘을 올바르게 증명해야합니까?"라고 묻는 학생이 항상 있습니다.

그렇다면 "다양한 테스트 사례를 시도하십시오"휴리스틱을 어떻게 속이겠습니까? 이 휴리스틱이 충분하지 않다는 것을 보여줄 좋은 예를 찾고 있습니다. 다시 말해서, 나는 그것이 겉으로는 정확할 것으로 보이는 알고리즘의 하나 이상의 예를 찾고 있으며, 누군가가 생각해 낼 수있는 모든 작은 입력에 대해 올바른 답을 출력하지만 알고리즘이 실제로 어디에 작동하지 않습니다. 알고리즘은 모든 작은 입력에서 올바르게 작동하고 큰 입력에서만 실패하거나 특이한 패턴의 입력에서만 실패 할 수 있습니다.

특히, 나는 찾고 있습니다 :

  1. 알고리즘. 결함은 알고리즘 수준이어야합니다. 구현 버그를 찾고 있지 않습니다. 예를 들어 최소한 예제는 언어에 구애받지 않아야하며 결함은 소프트웨어 엔지니어링 또는 구현 문제보다는 알고리즘 문제와 관련이 있어야합니다.

  2. 누군가가 그럴듯하게 생각 해낼 수있는 알고리즘. 의사 코드는 최소한 그럴듯하게 보일 것입니다 (예 : 난독 화되거나 명백하게 모호한 코드는 좋은 예가 아닙니다). 숙제 나 시험 문제를 해결하려고 할 때 일부 학생이 실제로 만든 알고리즘 인 경우 보너스 포인트.

  3. 확률이 높은 합리적인 수동 테스트 전략을 통과하는 알고리즘입니다. 손으로 몇 가지 작은 테스트 사례를 시도하는 사람은 결함을 발견하지 못할 것입니다. 예를 들어, "수십 개의 작은 테스트 사례에서 수작업으로 QuickCheck 시뮬레이션"은 알고리즘이 잘못되었음을 나타내지 않을 것입니다.

  4. 바람직하게는 결정적 알고리즘이다. 많은 학생들이 "수동으로 일부 테스트 사례를 시험해 보는 것"이 ​​결정 론적 알고리즘이 올바른지 확인하는 합리적인 방법이라고 생각하지만 대부분의 학생들은 몇 가지 테스트 사례를 시도하는 것이 확률론을 검증하는 좋은 방법이라고 생각하지 않을 것입니다 알고리즘. 확률 알고리즘의 경우 특정 출력이 올바른지 여부를 알 수있는 방법이없는 경우가 많습니다. 출력 분포에 대한 유용한 통계 테스트를 수행하기에 충분한 예제를 수동으로 크랭크 할 수 없습니다. 따라서 결정 론적 알고리즘에 중점을두기를 원합니다. 학생들의 오해의 중심에 더 명확하게 도달하기 때문입니다.

알고리즘의 정확성을 증명하는 것이 중요하다는 것을 가르치고 싶습니다. 정확성 증명에 동기를 부여하기 위해 이와 같은 몇 가지 예를 사용하고 싶습니다. 학부생들이 비교적 간단하고 접근 할 수있는 사례를 선호합니다. 중장비 또는 많은 수학적 / 알고리즘 배경이 필요한 예는 그다지 유용하지 않습니다. 또한 "부 자연스러운"알고리즘을 원하지 않습니다. 휴리스틱을 속이는 이상한 인공 알고리즘을 구성하는 것이 쉽지만, 매우 부자연 스럽거나이 휴리스틱을 속이는 것으로 명확한 백도어가 있다면 학생들에게 설득력이 없을 것입니다. 좋은 예가 있습니까?


2
나는 당신의 질문을 좋아합니다. 그것은 또한 요즘 수학 에서 본 매우 흥미로운 질문 과 관련이 있습니다. 여기에서
ZeroUltimax

1
좀 더 파고 두 가지 기하학적 알고리즘을 발견했습니다 .
ZeroUltimax

@ZeroUltimax 당신은 옳습니다. 3 개의 비선형 pt의 중심점은 내부에 보장되지 않습니다. 가장 빠른 해결책은 가장 왼쪽과 오른쪽 사이의 선에 pt를 얻는 것입니다. 다른 곳에서 문제가 있습니까?
정보 : A

이 질문의 전제는 내가 머리를 돌리는 데 어려움을 겪고있는 방식으로 이상하게 보이지만 설명에 따르면 알고리즘 설계 프로세스는 근본적으로 고장났습니다. '중지'하지 않는 학생들의 경우에도 파산됩니다. 1> 쓰기 알고리즘, 2> 테스트 사례 생각 / 실행, 3a> 중지 또는 3b>가 올바른 것으로 판명되었습니다. 첫 번째 단계는 거의 했다 문제 도메인 입력 클래스를 식별한다. 코너 케이스와 알고리즘 자체가 코너 케이스에서 발생합니다. (계속)
Mr.Mindor

1
결함이있는 알고리즘과 구현 버그를 어떻게 공식적으로 구별합니까? 나는 당신의 질문에 관심이 있었지만, 동시에 당신이 묘사하는 상황이 예외보다 더 규칙적인 것 같다는 사실에 귀찮았습니다. 많은 사람들이 자신이 구현 한 것을 테스트하지만 일반적으로 버그가 있습니다. 가장 많이 대답 된 답변의 두 번째 예는 정확히 그러한 버그입니다.
babou

답변:


70

내가 생각하는 일반적인 오류는 욕심 많은 알고리즘을 사용하는 것이며, 항상 올바른 접근법은 아니지만 대부분의 테스트 사례에서 작동 할 수 있습니다.

예 : 동전의 교단, 와 숫자 표현 의 합으로 :의를 가능한 한 적은 수의 동전. n n d id1,,dknndi

순진한 접근 방식은 가능한 한 가장 큰 동전을 먼저 사용하고 탐욕스럽게 그러한 합계를 생성하는 것입니다.

예를 들어, 값이 , 및 동전 은 숫자 제외하고 과 사이의 모든 숫자에 대해 욕심과 정답을 제공합니다 .5 1 1 14 10 = 6 + 1 + 1 + 1 + 1 = 5 + 565111410=6+1+1+1+1=5+5


10
이것은 실제로 좋은 예이며, 특히 학생들이 일상적으로 잘못하는 예입니다. 알고리즘이 실패하는 것을 확인하기 위해 특정 코인 세트뿐만 아니라 특정 값을 선택해야합니다.
Raphael

2
또한 학생들 이이 예에서 종종 잘못된 증거 를 가지고 있다고하자 (면밀한 시험에 실패한 순진한 주장을 취함). 여기서 하나 이상의 수업을 배울 수 있습니다.
Raphael

2
구식 영국 동전 체계 (1971 년 10 진수 이전)는 이에 대한 실제 예를 보여주었습니다. 4 개의 실링을 계산하기위한 탐욕스러운 알고리즘은 반 크라운 (2½ 실링), 1 실링 코인, 6 펜스 (1 실링)를 사용합니다. 그러나 최적의 솔루션은 두 개의 플로린을 사용합니다 (각각 2 개의 실링).
Mark Dominus

1
실제로 많은 경우 탐욕스러운 알고리즘은 합리적으로 보이지만 작동하지 않습니다. 또 다른 예는 최대 이분법 일치입니다. 반면에 욕심 많은 알고리즘이 작동하지 않아야하는 것처럼 보이는 예제도 있지만 최대 스패닝 트리입니다.
jkff

62

나는 즉시 R. Backhouse 의 사례를 회상했다 . 분명히, 그는 학생들이 두 줄의 동등성을 테스트하기 위해 파스칼 프로그램을 작성해야하는 프로그래밍 과제를 배정했습니다. 학생이 제출 한 프로그램 중 하나는 다음과 같습니다.

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

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

write(issame);

이제 다음 입력으로 프로그램을 테스트 할 수 있습니다.

"대학" "대학" True; 승인

"코스" "코스" True; 승인

"" "" True; 승인

"대학" "코스" False; 승인

"강의" "코스" False; 승인

"정확도" "정확도" False, OK

이 모든 것이 매우 유망한 것으로 보입니다. 아마도 프로그램이 실제로 작동합니다. 그러나 "pure"및 "true"로보다 신중하게 테스트하면 출력에 결함이있는 것으로 나타납니다. 실제로 문자열의 길이와 마지막 문자가 같은 경우 프로그램은 "참"이라고 말합니다!

그러나 테스트는 상당히 철저했습니다. 길이가 다른 문자열, 길이는 같지만 내용이 다른 문자열, 심지어는 문자열도있었습니다. 또한 학생은 모든 지점을 테스트하고 실행했습니다. 프로그램이 실제로 매우 단순하다는 점에서 테스트가 부주의했다고 주장 할 수는 없습니다. 철저히 테스트 할 동기와 에너지를 찾기가 어려울 수 있습니다.


또 다른 귀여운 예는 이진 검색입니다. Knuth는 TAOCP에서 "이진 검색의 기본 개념은 비교적 간단하지만 세부 사항은 놀랍도록 까다로울 수 있습니다"라고 말합니다. 분명히 자바의 바이너리 검색 구현에서 버그는 10 년 동안 눈에 띄지 않았다. 정수 오버플로 버그였으며 충분히 큰 입력에서만 나타납니다. 바이너리 검색 구현에 대한 까다로운 세부 사항은 Programming Pearls 책에서 Bentley가 다루고 있습니다.

결론 : 이진 검색 알고리즘을 테스트하는 것만으로는 정확하다는 것이 놀랍게도 어려울 수 있습니다.


9
물론 결함은 소스에서 분명합니다 (이전에 비슷한 것을 쓴 적이 있다면).
Raphael

3
예제 프로그램의 간단한 결함이 수정 되더라도 문자열은 상당히 흥미로운 문제가됩니다! 문자열 반전은 고전입니다.이를 수행하는 "기본"방법은 단순히 바이트를 반전시키는 것입니다. 그런 다음 인코딩이 시작됩니다. 그런 다음 대리합니다 (보통 두 번). 물론 문제는 공식적으로 방법이 올바른지 쉽게 증명할 수있는 방법이 없다는 것입니다.
Ordous

6
어쩌면 나는 질문을 완전히 잘못 해석하고 있지만 알고리즘 자체 의 결함보다는 구현 상의 결함 인 것 같습니다 .
Mr.Mindor

8
(! 나는 "제대로"라고 주저) : @ Mr.Mindor 어떻게 프로그래머가 올바른 알고리즘을 작성하고 잘못을 구현하거나, 잘못된 알고리즘을 작성하고 충실하게 그것을 실행 여부를 알 수 있습니다
스티브 Jessop

1
@wabbit 논쟁의 여지가 있습니다. 1 학년 학생에게는 분명하지 않은 것이 있습니다.
Juho

30

내가 본 최고의 예는 우선 성 테스트입니다.

입력 : 자연수 p, p! = 2
출력 : pa 프라임인가?
알고리즘 : 계산 2 ** (p-1) mod p. 결과 = 1이면 p는 소수이고 p는 그렇지 않습니다.

이것은 소수의 카운터 예제를 제외하고 (거의) 모든 숫자에서 작동하며 실제로 현실적인 예제에서 카운터 예제를 찾으려면 기계가 필요합니다. 첫 번째 반례는 341이며, 대수의 경우에도 p가 증가함에 따라 실제로 반례의 밀도가 감소합니다.

2를 거듭 제곱의 기초로 사용하는 대신 이전 소수가 1을 반환하는 경우에 따라 작은 소수를 추가로 증가시켜 알고리즘을 개선 할 수 있습니다. 그럼에도 불구하고이 체계에 대한 반례, 즉 Carmichael 수, 그래도 꽤 드문


Fermat 원시성 테스트는 확률 론적 테스트이므로 사후 조건이 올바르지 않습니다.
Femaref

5
그것은 확률 론적 테스트이지만 그 대답은 정확한 알고리즘으로 잘못 알려진 확률 론적 알고리즘이 어떻게 오류의 원인이 될 수 있는지를 잘 보여줍니다. Carmichael 번호
vzn

2
비제 한 암호화 키 생성에 익숙한 우선 순위 테스트를 실제로 사용하려면 확률 알고리즘을 사용합니다. 정확한 테스트를하기에는 숫자가 너무 큽니다 (그렇지 않은 경우 실제 시간에 무차별 대입으로 키를 찾을 수 있기 때문에 암호에 적합하지 않습니다).
Gilles

1
당신이 말하는 제한은 이론적이지 않고 실용적이며 암호화 시스템에서 주요한 테스트, 예를 들어 RSA 는 이러한 이유로 정확하게 드물고 매우 불가능한 실패 를 겪으며 , 다시 예제의 중요성을 강조합니다. 즉, 실제로이 제한은 피할 수없는 것으로 받아 들여진다. AKS와 같은 우선 순위 테스트를위한 P 시간 알고리즘이 있지만 실제로 사용되는 "작은"숫자에는 너무 오래 걸립니다.
vzn

2 p뿐만 아니라 50 다른 임의의 값 2 ≤ a <p에 대해 p를 사용하여 테스트하면 대부분의 사람들은 확률이 높다는 것을 알지만 실패로 인해 컴퓨터의 오작동이 발생할 가능성이 더 큽니다. 오답. 2 , P 3 , P (5) P (7) 및 P, 오류가 이미 매우 드물다.
gnasher729

21

다음은 내가 갔던 대회에서 Google 담당자가 나에게 던진 것입니다. C로 코딩되었지만 참조를 사용하는 다른 언어로 작동합니다. [cs.se]에서 코드를 작성 해줘서 죄송합니다. 그러나 그것을 설명하는 유일한 방법입니다.

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

이 알고리즘은 x와 y에 동일한 값이 있더라도 x와 y에 주어진 모든 값에 대해 작동합니다. 그러나 swap (x, x)로 호출되면 작동하지 않습니다. 이 상황에서 x는 0으로 끝납니다. 이제이 연산이 수학적으로 정확하다는 것을 증명할 수는 있지만 여전히이 경우를 잊어 버리기 때문에 만족스럽지 않을 수 있습니다.


1
이 속임수는 언더 핸드 C 컨테스트에서 결함이있는 RC4 구현 을 생성하는 데 사용되었습니다 . 다시 기사를 읽고, 난 그냥이 해킹 아마 @DW에 의해 제출 된 것으로 나타났습니다
CodesInChaos

7
이 결함은 실제로 미묘하지만 언어마다 다르기 때문에 알고리즘의 결함은 아닙니다. 구현상의 결함입니다. 미묘한 결함을 쉽게 숨길 수있는 언어의 이상에 대한 다른 예를 생각해 낼 수는 있지만 실제로는 내가 원하는 것이 아닙니다 (알고리즘 추상화 수준에서 무언가를 찾고있었습니다). 어쨌든이 결함은 증거의 가치를 보여주는 이상적인 증거가 아닙니다. 이미 앨리어싱에 대해 생각하지 않는 한 "정확한"증거를 작성할 때 동일한 문제가 간과 될 수 있습니다.
DW

그렇기 때문에 이것이 투표율이 너무 높아서 놀랐습니다.
ZeroUltimax

2
@DW 그것은 알고리즘을 어떤 모델로 정의하는지에 관한 문제입니다. 공유가없는 것으로 가정하는 일반적인 모델이 아닌 메모리 참조가 명시적인 수준으로 내려 가면 이것은 알고리즘 결함입니다. 결함은 실제로 언어별로 다르지 않으며 메모리 참조 공유를 지원하는 모든 언어로 나타납니다.
Gilles

16

의사 난수 생성기 라는 본질적으로 테스트하기 어려운 전체 알고리즘 클래스가 있습니다. 단일 출력을 테스트 할 수는 없지만 통계 수단으로 일련의 출력을 조사해야합니다 (많은). 무엇을 어떻게 테스트 하느냐에 따라 무작위가 아닌 특성을 놓칠 수도 있습니다.

일이 끔찍하게 잘못 된 유명한 사례 중 하나는 RANDU 입니다. 당시 사용 가능한 정밀 검사를 통과하여 후속 출력 의 튜플 동작을 고려하지 못했습니다 . 이미 트리플은 많은 구조를 보여줍니다.

기본적으로 테스트는 모든 사용 사례를 다루지는 않았습니다. RANDU의 단일 차원 사용은 (아마도 대부분) 양호하지만 3 차원 점을 샘플링하는 데는이 방법을 사용하지 않았습니다 (이와 같은 방식).

적절한 의사 랜덤 샘플링은 까다로운 사업입니다. 운 좋게도, 제안 된 생성기에서 우리가 알고있는 모든 통계를 처리 하는 것을 전문으로하는 다 이하 드러 와 같은 강력한 테스트 스위트가 있습니다. 충분한가?

공정하게, 나는 당신이 PRNGs에 대해 어떤 것을 증명할 수 있는지 전혀 모른다.


2
그러나 좋은 예는 실제로 일반적으로 PRNG에 결함이 없음을 증명할 방법이 없으며, 약한 테스트와 강한 테스트의 무한한 계층 구조 만 있다는 것입니다. 실제로 어떤 의미에서 "무작위"임을 증명하는 것은 아마도 결정 불가능할 것입니다.
vzn

1
그것은 테스트하기 어려운 것에 대한 좋은 아이디어이지만 RNG는 입증하기도 어렵습니다. PRNG는 잘못 구현 될 정도로 구현 버그에 취약하지 않습니다. diehard와 같은 테스트는 일부 용도에는 적합하지만 암호의 경우 diehard를 통과하고 여전히 방에서 웃을 수 있습니다. “증명 된 안전한”CSPRNG는 없습니다. CSPRNG가 고장 나면 AES도 마찬가지임을 증명할 수 있습니다.
Gilles

@Gilles 나는 암호로 들어 가려고하지 않았으며 통계적 무작위성 만 사용했습니다 (두 가지가 거의 직교 요구 사항을 가지고 있다고 생각합니다). 답을 분명히해야합니까?
Raphael

1
암호화 무작위성은 통계적 무작위성을 의미합니다. 정보 이론상의 임의성이라는 이상적인 개념 (결정 론적 튜링 머신에 구현 된 PRNG의 개념과 모순됨)을 제외하고는 수학적으로 공식적인 정의를 가지고 있지 않습니다. 통계적 무작위성은“우리가 테스트 할 분포와 독립적이어야한다”를 넘어 공식적인 정의를 가지고 있습니까?
Gilles

1
@vzn : 임의의 숫자 시퀀스라는 의미는 여러 가지 방법으로 정의 할 수 있지만 간단한 방법은 "대형 Komolgorov 복잡성"입니다. 이 경우 임의성을 결정하는 것이 결정 불가능하다는 것을 쉽게 알 수 있습니다.
코디

9

2D 로컬 최대

입력 : 2 차원 배열n×nA

출력 : 로컬 최대 - 쌍 이되도록 엄밀히 큰 값을 포함하는 어레이 내에 인접 셀이 없다. (i,j)A[i,j]

(인접한 셀은 어레이에 존재 중 하나임 ) 예를 들어 가A[i,j+1],A[i,j1],A[i1,j],A[i+1,j]A

0134323125014013

각 굵은 셀은 로컬 최대 값입니다. 비어 있지 않은 모든 배열에는 하나 이상의 로컬 최대 값이 있습니다.

연산. 가 단, 각 셀을 확인 : - 시간 알고리즘. 더 빠르고 재귀적인 알고리즘에 대한 아이디어가 있습니다.O(n2)

주어지면 중간 열의 셀과 가운데 행의 셀로 구성되도록 교차 를 정의 하십시오. 먼저 각 셀을 확인 하여 셀이 의 로컬 최대 값인지 확인하십시오 . 그렇다면 해당 셀을 반환하십시오. 그렇지 않으면 는 최대 값을 갖는 의 셀이되게하십시오 . 이후 극대가 아니며, 이는 인접 셀이 있어야 더 큰 값.AXXA(i,j)X(i,j)(i,j)

자연스럽게 (배열 , 의 셀 빼기 )를 4 개의 사분면 (왼쪽 위, 오른쪽 위, 왼쪽 아래 및 오른쪽 아래 사분면)으로 분할합니다. 더 큰 값을 가진 인접 셀 은 해당 사분면 중 하나에 있어야합니다. 그 사분면 부릅니다 . 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)

따라서 우리는 다음 정리를 증명했습니다.

정리. 가 에서 로컬 최대 값을 구하는 알고리즘 - 시간 어레이.O(n)n×n

아니면 우리가?


나는 아직도 이것을 흡수하고 있습니다. 내가 할 때까지 빠른 질문 : 당신은 실행 시간이 이라는 것을 의미 했습니까 ? (그것이 재귀에 대한 해결책이므로 )T ( N ) = T ( N / 2 ) + O ( N )T(n)=O(nlogn)T(n)=T(n/2)+O(n)
DW

2
이것은 아름다운 예입니다! 나는 그것을 좋아한다. 감사합니다. (드디어이 알고리즘의 결함을 알아 냈습니다. 타임 스탬프에서 시간이 얼마나 걸리는지에 대한 하한을 얻을 수 있습니다. 실제 시간을 밝히기에는 너무 당황합니다. :-)
DW

1
O(n)

8

이것들은 공통적이기 때문에 우선 순위의 예입니다.

(1) SymPy의 우선 순위. 문제 1789 . 잘 알려진 웹 사이트에 대한 잘못된 테스트가 10 ^ 14 이후까지 실패하지 않았습니다. 수정은 정확하지만 문제를 다시 생각하기보다는 구멍을 패치하는 것입니다.

(2) Perl의 우선 순위 6. Perl6에는 고정 된베이스로 여러 MR 테스트를 사용하는 is-prime이 추가되었습니다. 알려진 반례가 있지만 기본 테스트 수가 많기 때문에 (기본적으로 성능 저하로 실제 문제를 숨기므로) 상당히 큽니다. 이것은 곧 해결 될 것입니다.

(3) FLINT의 우선 순위. n_isprime ()은 고정 된 이후 composites대해 true를 리턴합니다 . 기본적으로 SymPy와 동일한 문제입니다. SPRP-2 유사 프라임의 Feitsma / Galway 데이터베이스를 2 ^ 64까지 사용하여이를 테스트 할 수 있습니다.

(4) Perl의 Math :: Primality. is_aks_prime broken . 이 시퀀스는 많은 AKS 구현과 유사 해 보입니다. 실수로 작동했거나 (예 : 1 단계에서 잃어 버려 시험 시행에 의해 전체 작업을 수행 한) 많은 코드가 더 큰 예제에서는 작동하지 않았습니다. 불행히도 AKS는 너무 느리기 때문에 테스트하기가 어렵습니다.

(5) Pari의 2.2 이전 is_prime. 수학 :: 파리 티켓 . MR 테스트에는 10 개의 임의 기준을 사용했습니다 (매번 호출 할 때마다 GMP의 고정 시드 대신 시작시 고정 시드 사용). 1M 통화 중 9 개가 소수임을 나타냅니다. 올바른 숫자를 선택하면 비교적 자주 실패 할 수 있지만 숫자가 희박 해 지므로 실제로는 많이 표시되지 않습니다. 그들은 이후 알고리즘과 API를 변경했습니다.

이것은 잘못된 것이 아니지만 고전적인 확률 론적 테스트입니다. mpz_probab_prime_p는 몇 번의 라운드를 제공합니까? 5 라운드를 주면 제대로 작동하는 것처럼 보입니다. 숫자는 base-210 Fermat 테스트를 통과 한 다음 5 개의 미리 선택된 bases Miller-Rabin 테스트를 통과해야합니다. 3892757297131 (GMP 5.0.1 또는 6.0.0a)이 될 때까지 반례를 찾을 수 없으므로 많은 테스트를 거쳐야합니다. 그러나 2 ^ 64에는 수천 개의 반례가 있습니다. 그래서 당신은 계속 숫자를 올립니다. 얼마나 멀리? 적이 있습니까? 정답은 얼마나 중요합니까? 랜덤베이스와 고정베이스를 혼동하고 있습니까? 어떤 입력 크기가 제공되는지 알고 있습니까?

1016

이것들은 올바르게 테스트하기가 매우 어렵습니다. 내 전략에는 명백한 단위 테스트, 엣지 케이스, 다른 패키지 이전 또는 다른 패키지에서 볼 수있는 실패의 예, 가능한 경우 알려진 데이터베이스와 비교 한 테스트가 포함됩니다 (예 : 단일 base-2 MR 테스트를 수행하는 경우 계산 불가능 함) 2 ^ 64 개의 숫자를 테스트하여 약 3 천 2 백만 개의 숫자를 테스트하는 작업), 마지막으로 다른 패키지를 표준으로 사용하는 수많은 무작위 테스트. 마지막 요점은 상당히 간단한 입력과 알려진 출력이있는 원시성과 같은 기능에 적용되지만 상당히 많은 작업이 이와 같습니다. 나는 이것을 사용하여 자체 개발 코드에서 결함을 발견하고 비교 패키지에서 때때로 문제를 발견했습니다. 그러나 무한 입력 공간이 주어지면 모든 것을 테스트 할 수 없습니다.

정확성을 증명하기 위해 여기에 또 다른 원시성 예제가 있습니다. BLS75 방법 및 ECPP에는 기본 인증서 개념이 있습니다. 기본적으로 교정에 적합한 값을 찾기 위해 검색을 중단 한 후 알려진 형식으로 출력 할 수 있습니다. 그런 다음 검증자를 작성하거나 다른 사람이 작성하도록 할 수 있습니다. 이것들은 생성과 비교하여 매우 빠르게 실행되며 이제는 (1) 두 코드가 모두 정확하지 않으므로 (따라서 검증자가 다른 프로그래머를 선호하는 이유) 또는 (2) 증거 아이디어의 수학이 잘못되었습니다. # 2는 항상 가능하지만 일반적으로 여러 사람들이 게시하고 검토했습니다 (일부 경우 자신을 쉽게 이해할 수있는 경우도 있음).

이에 비해 AKS, APR-CL, 시험 분할 또는 결정 론적 Rabin 테스트와 같은 방법은 모두 "프라임"또는 "복합"이외의 출력을 생성하지 않습니다. 후자의 경우 검증 할 수있는 요인이있을 수 있지만 전자의 경우에는이 1 비트 출력 외에는 아무것도 남지 않습니다. 프로그램이 올바르게 작동 했습니까? 던노

몇 가지 장난감 예제 이상에서 소프트웨어를 테스트하고 알고리즘의 각 단계에서 몇 가지 예제를 살펴보고 "이 입력을 받았으면이 상태에 있다는 것이 합리적입니까?"라고 말하는 것이 중요합니다.


1
이 중 많은 부분이 (1) 구현 오류 (기본 알고리즘은 정확하지만 올바르게 구현되지 않았 음)와 비슷하지만이 질문의 요점은 아닙니다. (2) 무언가를 선택하기위한 신중하고 신중한 선택 빠른 속도와 대부분 작동하지만 매우 적은 확률로 실패 할 수 있습니다 (하나의 임의의 염기 또는 소수의 고정 / 임의의 염기로 테스트하는 코드의 경우, 수행하기로 선택한 사람이 성능 트레이드 오프를하고 있음을 알고 있기를 바랍니다).
DW

토론과 다른 예제들도 마찬가지로 혼란 스럽지만 올바른 알고리즘 + 버그는 중요하지 않습니다. 이 분야는 소수에 대해서는 효과가 있지만 틀린 추측으로 잘 익었다. 포인트 (2)의 경우 일부 경우에 해당하지만 내 예제 # 1 및 # 3 은이 경우가 아닙니다. 알고리즘이 정확하다고 믿었습니다 (이 5 개의 염기는 10 ^ 16 미만의 숫자에 대해 입증 된 결과를 제공합니다). 그렇지 않다는 것을 발견했습니다.
DanaJ

이것은 의사 우선 순위 테스트의 근본적인 문제가 아닙니까?
asmeurer

asmeurer, 그렇습니다. # 2와 나중에 논의 할 내용입니다. 그러나 # 1과 # 3은 알려진베이스와 함께 Miller-Rabin을 사용하여 결정적인 정확한 결과를 임계 값 미만으로 사용하는 경우입니다. 따라서이 경우 "알고리즘"(OP와 일치시키기 위해 용어를 느슨하게 사용)이 잘못되었습니다. # 4는 가능한 주요 테스트는 아니지만 DW가 지적했듯이 알고리즘이 제대로 작동하지만 구현이 어려울뿐입니다. 비슷한 상황으로 이어지기 때문에 포함 시켰습니다. 테스트가 필요하며 작동하기 전에 간단한 예를 넘어 얼마나 멀리 가십니까?
DanaJ

귀하의 게시물 중 일부는 질문에 맞는 것으로 보이지만 일부는 그렇지 않습니다 (cf @DW의 의견). 질문에 대답하지 않는 예 (및 기타 내용)를 제거하십시오.
Raphael

7

Fisher-Yates-Knuth 셔플 링 알고리즘 은 실제적인 예 이며이 사이트의 저자 중 한 사람이에 대해 언급 한 것 입니다.

알고리즘은 다음과 같이 주어진 배열의 무작위 순열을 생성합니다.

 // 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

"순진한"알고리즘은 다음과 같습니다.

 // 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]

루프에서 교체 될 요소는 사용 가능한 모든 요소에서 선택됩니다. 그러나 이것은 순열의 바이어스 샘플링을 생성합니다 (일부는 과도하게 표현됩니다).

실제로 간단한 (또는 순진한) 카운팅 분석을 사용하여 피셔-율-knuth 셔플 링을 시작할 있습니다.

nn!=n×n1×n2..nn1

셔플 링 알고리즘이 올바른지 ( 바이어스 드인지 아닌지 ) 확인하는 데있어 주요 문제점 은 통계로 인해 많은 수의 샘플이 필요하다는 것입니다. codinghorror 기사 나는 위의 링크는 정확히 (실제 테스트 포함)에 대해 설명합니다.


1
셔플 알고리즘에 대한 정확성 증명의 예는 여기 를 참조 하십시오 .
Raphael

5

내가 본 가장 좋은 예 (읽기 : 내가 가장 아파하는 것)는 collatz 추측관련이 있습니다.. 나는 프로그래밍 경쟁 (첫 번째 줄에 500 달러 상금이 있음)에서 두 가지 숫자가 같은 숫자에 도달하는 데 필요한 최소 단계 수를 찾는 것이 문제였습니다. 물론 해결책은 둘 다 이전에 본 것에 도달 할 때까지 각 단계를 번갈아 가면서 단계적으로 진행하는 것입니다. 우리는 다양한 범위의 숫자를 받았으며 (1과 1000000 사이라고 생각합니다) collatz 추측은 2 ^ 64까지 검증되었으므로 주어진 모든 숫자는 1에서 수렴됩니다. 그러나 단계를 수행하는 정수. 1과 1000000 (170,000,000) 사이에 하나의 모호한 숫자가있어 32 비트 정수가 적시에 오버플로 될 수 있습니다. 실제로이 숫자는 매우 드물다 (2 ^ 31). 우리는 오버플로가 발생하지 않았 음을 확인하기 위해 1000000보다 큰 HUGE 수에 대해 시스템을 테스트했습니다. 방금 테스트하지 않은 훨씬 작은 숫자가 오버플로를 일으켰습니다. "긴"대신 "int"를 사용했기 때문에 $ 500 상이 아닌 300 달러 상을 받았습니다.


5

배낭 0/1 문제는 거의 모든 학생들이 욕심 알고리즘에 의해 분해 할 생각입니다. 욕심 많은 알고리즘이 작동 하는 배낭의 문제 버전으로 욕심 많은 해결책을 이전에 보여 주면 더 자주 발생 합니다 .

이러한 문제의 경우 클래스 에서 의심을 제거하고 욕심 많은 문제 버전에 대한 배낭 0/1 ( 동적 프로그래밍 )에 대한 증거를 보여 주어야합니다 . 실제로, 두 가지 증거는 모두 사소한 것이 아니며 학생들은 아마도 그것들이 매우 도움이된다는 것을 알게 될 것입니다. 또한 이에 대한 의견은 CLRS 3ed , 16 장, 페이지 425-427에 있습니다.

문제 : 도둑이 가게를 강탈하고 배낭에 최대 W의 무게를 실을 수 있습니다. n 개의 품목이 있으며 i 번째 품목의 무게는 6 달러입니다. 도둑은 어떤 물건을 가져 가야합니까? 그의 이익을 극대화하기 위해 ?

배낭 0/1 문제 : 설정은 동일하지만 항목이 더 작은 조각으로 나뉘 지 않을 수 있으므로 도둑이 항목가져 가거나 떠나도록 결정할 수 있지만 (이진 선택) 항목의 일부를 차지하지 않을 수 있습니다 .

그리고 욕심 많은 버전 문제와 같은 아이디어를 따르는 아이디어 나 알고리즘을 학생들로부터 얻을 수 있습니다.

  • 가방의 전체 용량을 가져와 가능한 한 가장 가치있는 물건을 최대한 넣은 다음, 가방이 가득 찼거나 가방 안에 넣을 무게가 적은 물건이 없어 더 많은 물건을 넣을 수 없을 때까지이 방법을 반복하십시오.
  • 다른 잘못된 방법은 생각입니다 : 더 가벼운 물건을 넣고 다음과 같이 최고에서 최저 가격으로 올리십시오.
  • ...

도움이 되셨습니까? 실제로, 우리는 동전 문제 가 배낭 문제 버전이라는 것을 알고 있습니다. 그러나 배낭 문제의 숲에는 더 많은 사례가 있습니다. 예를 들어 배낭 백 2D ( 가구를 만들기 위해 나무를 자르고 싶을 때 실제로 도움이 됩니다 . 욕심은 여기서도 작동하지만 그렇지 않습니다.


욕심은 이미 받아 들여진 대답 에서 다루어 졌지만 특히 배낭 문제는 일부 함정을 세우는 데 적합합니다.
Raphael

3

일반적인 실수는 셔플 링 알고리즘을 잘못 구현하는 것입니다. Wikipedia에 대한 토론을 참조하십시오 .

n!nn(n1)n


1
테스트는 셔플 링 알고리즘에 실제로 적용되지 않기 때문에 좋은 버그이지만 테스트 사례 휴리스틱을 속이는 좋은 예는 아닙니다 (임의적이므로 테스트 방법은 무엇입니까? 출력을 보면 어떻게 감지 할 수 있습니까?)
DW

물론 통계적으로 테스트합니다. 균일 한 무작위성은 "출력에서 일어날 수있는 모든 것"과는 거리가 멀다. 주사위를 흉내내는 프로그램이 당신에게 100 3을 연속으로 줬다면 의심하지 않겠습니까?
Per Alexandersson

다시 한 번, "손으로 몇 가지 테스트 사례를 시도하십시오"라는 학생 추론에 대해 이야기하고 있습니다. 많은 학생들이 이것이 결정 론적 알고리즘이 올바른지 확인하는 합리적인 방법이라고 생각하지만 셔플 링 알고리즘이 올바른지 여부를 테스트하는 좋은 방법이라고 생각하지 않습니다 (셔플 링 알고리즘이 무작위 화되어 있기 때문에) 특정 출력이 올바른지 알 수있는 방법이 없습니다. 어쨌든 유용한 통계 테스트를 수행하기에 충분한 수의 예제를 직접 수작업으로 만들 수는 없습니다. 따라서 셔플 알고리즘이 일반적인 오해를 해결하는 데 많은 도움이 될 것으로 기대하지 않습니다.
DW

1
@PerAlexandersson : 단 하나의 셔플 만 생성하더라도 n> 2080 인 MT를 사용하면 실제로 무작위로 할 수 없습니다. 이제 예상과의 편차가 매우 작으므로 신경 쓰지 않아도됩니다. 위의 asmeurer가 지적한 것처럼 기간보다 훨씬 적게 생성합니다.
찰스

2
이 답변은 Nikos M.의 더 정교한 답변에 의해 더 이상 사용되지 않은 것 같습니다 .
Raphael

2

파이 PEP450 표준 라이브러리에 통계 기능을 도입 관심이있을 수 있습니다. Python의 표준 라이브러리에서 분산을 계산하는 함수를 갖는 타당성의 일부로 저자 Steven D' Aprano는 다음과 같이 씁니다.

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)

위의 테스트는 캐주얼 테스트에서 올바른 것으로 보입니다.

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

그러나 모든 데이터 포인트에 상수를 추가하면 분산이 변경되지 않아야합니다.

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

그리고 분산은 절대로 음수 가 아니 어야합니다 .

>>> variance(data*100)
  -1239429440.1282566

문제는 숫자와 정밀도가 어떻게 손실되는지에 관한 것입니다. 최대 정밀도를 원하면 특정 방식으로 작업을 주문해야합니다. 순진한 구현은 부정확성이 너무 커서 잘못된 결과를 초래합니다. 그것은 대학에서 내 숫자 과정에 관한 문제 중 하나였습니다.


1
n1

2
@Raphael : 공평하지만, 선택한 알고리즘은 부동 소수점 데이터에 적합하지 않은 것으로 잘 알려져 있습니다.

2
단순히 숫자와 정밀도가 손실되는 방법에 대한 작업 구현이 아닙니다. 최대 정밀도를 원하면 특정 방식으로 작업을 주문해야합니다. 그것은 대학에서 내 숫자 과정에 관한 문제 중 하나였습니다.
Christian

Raphael의 정확한 의견 외에도이 예의 단점은 정확성의 증거 가이 결함을 피하는 데 도움이 될 것이라고 생각하지 않는다는 것입니다. 부동 소수점 산술의 미묘한 점을 모르는 경우 수식이 유효하다는 것을 증명하여 이것이 올바른 것으로 판단 될 수 있습니다. 따라서 학생들에게 알고리즘이 올바른지 증명하는 것이 중요한 이유를 가르치는 이상적인 예는 아닙니다. 학생들이이 예제를 보았을 경우, "부동 소수점 / 숫자 계산은 까다 롭습니다"라는 수업을 대신받을 것입니다.
DW

1

이것이 당신이 추구하는 것은 아니지만 아마도 다른 사고없이 작은 사례를 이해하고 테스트하는 것은 잘못된 알고리즘으로 이어질 것입니다.

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

제안 된 해결책 :

int f(int n) {
   return 1;
}

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

이 "일부 작은 사례를 시도하고 결과에서 알고리즘을 추론"접근 방식은 (a) 구현하기 쉽고 (b) )의 실행 시간이 빠릅니다.


5
나는 이것이 아주 좋은 예라고 생각하지 않습니다. 소수의 사람들이 1을 반환함으로써 다항식의 제수를 찾으려고 시도하기 때문입니다.
Brian S

1
nn3n

이는 제수 (또는 다른 계산)에 대해 상수 값을 반환하는 것이 문제에 대한 잘못된 알고리즘 접근법 (예 : 통계적 문제 또는 알고리즘의 에지 사례를 처리하지 않음)의 결과 일 수 있다는 점에서 관련이있을 수 있습니다. 그러나 대답은 다시 표현해야합니다.
Nikos M.

@NikosM. 허. 나는 여기서 죽은 말을 치고있는 것처럼 느껴지지만 질문의 두 번째 단락은 "알고리즘이 시도 할 수있는 모든 경우를 포함하여 소수의 예제에서 알고리즘이 올바르게 작동하면 알고리즘이 "몇 가지 테스트 사례에서 알고리즘을 시도해 볼 수 있다면 왜 알고리즘을 올바르게 증명해야합니까?"라는 질문을하는 학생이 항상 있습니다. .. 1이 올바른지 반환) 시도 할 가능성이 그 영업 찾던 무엇으로 나에게 보인다
릭 데커

그래, 그러나 이것은 문구처럼 사소하지만 (전형적으로 정확할 수도 있지만) 질문의 정신에는 맞지 않습니다. 여전히 재고가 필요합니다
Nikos M.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.