내 친구가 그가 참석하는 C++ 코스에서 가정 운동을 보여주었습니다. 저는 C++을 이미 알고 있기 때문에 하스켈을 배우기 시작 했으므로 저는 "하스켈 방식"으로 운동을 풀려고했습니다.하스켈에서보다 효율적인 알고리즘 프리폼 더
이는 운동 지침 (내가 지시가 명확하지 않은 경우 그래서 의견을하시기 바랍니다 우리의 모국어 번역)입니다
읽기 프로그램을 작성 비 - 제로 계수 (A, B, C, D) A * x + B * y + C * z = D 또한 프로그램은 범위를 나타내는 사용자 N에서 읽어야합니다. 프로그램은 -N/2 ~ N/2 범위의 방정식에 대해 가능한 모든 통합 솔루션을 찾아야합니다. 예를 들어
:
Input: A = 2,B = -3,C = -1, D = 5, N = 4
Output: (-1,-2,-1), (0,-2, 1), (0,-1,-2), (1,-1, 0), (2,-1,2), (2,0, -1)
가장 솔직 알고리즘은 무력에 의한 모든 가능성을 시도하는 것입니다.
triSolve :: Integer -> Integer -> Integer -> Integer -> Integer -> [(Integer,Integer,Integer)]
triSolve a b c d n =
let equation x y z = (a * x + b * y + c * z) == d
minN = div (-n) 2
maxN = div n 2
in [(x,y,z) | x <- [minN..maxN], y <- [minN..maxN], z <- [minN..maxN], equation x y z]
지금까지 너무 좋은,하지만 운동 지침은보다 효율적인 알고리즘이 구현 될 수 있습니다, 그래서 그것을 더 나은 만드는 방법을 생각 : 나는 다음과 같은 방법으로 하스켈에서 구현. 방정식은 선형이기 때문에 Z가 항상 처음으로 증가한다는 가정에 기초하여 솔루션이 발견되면 Z를 증가시킬 포인트가 없습니다. 대신 Y를 증가시키고 Z를 범위의 최소값으로 설정하고 계속가. 이렇게하면 중복 실행을 줄일 수 있습니다. Haskell에는 루프가 없으므로 (적어도 필자의 이해로) 이러한 알고리즘은 재귀를 사용하여 구현되어야 함을 깨달았습니다. 나는 다음과 같은 방식으로 알고리즘을 구현했다.
solutions :: (Integer -> Integer -> Integer -> Bool) -> Integer -> Integer -> Integer -> Integer -> Integer -> [(Integer,Integer,Integer)]
solutions f maxN minN x y z
| solved = (x,y,z):nextCall x (y + 1) minN
| x >= maxN && y >= maxN && z >= maxN = []
| z >= maxN && y >= maxN = nextCall (x + 1) minN minN
| z >= maxN = nextCall x (y + 1) minN
| otherwise = nextCall x y (z + 1)
where solved = f x y z
nextCall = solutions f maxN minN
triSolve' :: Integer -> Integer -> Integer -> Integer -> Integer -> [(Integer,Integer,Integer)]
triSolve' a b c d n =
let equation x y z = (a * x + b * y + c * z) == d
minN = div (-n) 2
maxN = div n 2
in solutions equation maxN minN minN minN minN
두 결과 모두 똑같은 결과를 산출한다. 바보 알고리즘이 실제로 더 정교보다 더 나은 예비 적 의미
*Main> length $ triSolve' 2 (-3) (-1) 5 100
3398
(2.81 secs, 971648320 bytes)
*Main> length $ triSolve 2 (-3) (-1) 5 100
3398
(1.73 secs, 621862528 bytes)
그러나, 실행 시간을 측정하려고하면 다음과 같은 결과를 얻었다. 내 알고리즘이 올바르다는 가정에 기반하여 (나는 잘못된 것으로 바뀌지 않을 것입니다. :)), 두 번째 알고리즘은 재귀에 의해 생성 된 오버 헤드로 인해 발생한다고 가정합니다. 첫 번째 알고리즘은 목록 이해력. Haskell에서 바보 같은 알고리즘을 구현하는 방법이 있습니까? (내 코딩 스타일에 대한 일반적인 피드백을받을 수있어서 기쁩니다)
뭔가를 테스트하는 신뢰할 수있는 유일한 방법은 -O2 스위치를 사용하여 컴파일하여 생성되는 독립 실행 형 실행 파일을 실행하는 것입니다. ; worker/wrapper 변환 적용 :'nextCall'을'solutions'의 내부적 인 내부 정의로 만드십시오. 따라서 변경되지 않은 인수를 전달할 필요가 없습니다. 중첩 된 정의는 외부 정의의 인수에 액세스 할 수 있습니다. –
프로그램이 임의로 많은 수를 지원해야합니까? 이것이 바로 Integer가하는 것입니다. 효율성을 원하고 Int의 범위가 충분하면 Int! 또한 예, -O2로 빌드하십시오. 그것은 시작으로서의 퍼포먼스를 향상시켜야합니다. –
ghci에서 평가 중입니다. 바이트 코드 인터프리터인데 ghc보다 약 30 배 느립니다. -O2 –