2013-08-30 3 views
4

프로젝트 오일러 (http://projecteuler.net/problem=14)의 문제 14를 해결하려고하는데 하스켈을 사용하여 막 다른 골목에 섰습니다.프로젝트 오일러 번호 14 하스켈

이제는 숫자가 충분히 작아서 무차별 한 행동을 할 수는 있지만 그게 내 운동의 목적이 아니라는 것을 알고 있습니다. 나는 체인이 알고 40처럼 다른 번호를 검색 할 때 이제

for 13: the chain is 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 
    My map should contain : 
    13 - (True, 10) 
    40 - (False, 13) 
    20 - (False, 40) 
    10 - (False, 20) 
    5 - (False, 10) 
    16 - (False, 5) 
    8 - (False, 16) 
    4 - (False, 8) 
    2 - (False, 4) 
    1 - (False, 2) 

:

- the first Integer (the key) holds the number 
- the Tuple (Bool, Interger) holds either (True, Length) or (False, Number) 
              where Length = length of the chain 
               Number = the number before him 

예 : 나는의 의미와 유형 Map Integer (Bool, Integer)Map의 중간 결과를 암기하는 것을 시도하고있다 (10 - 1) length 등이 있습니다. 10 개를 검색하면 길이가 (10 - 3) length이고 맵을 업데이트 할뿐만 아니라, 여전히 (False, _)의 경우 20, 40을 업데이트하려고합니다.

내 코드 :

import Data.Map as Map 

solve :: [Integer] -> Map Integer (Bool, Integer) 
solve xs = solve' xs Map.empty 
    where 
     solve' :: [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer) 
     solve' []  table = table 
     solve' (x:xs) table = 
      case Map.lookup x table of 
       Nothing  -> countF x 1 (x:xs) table 
       Just  (b, _) -> 
        case b of 
         True -> solve' xs table 
         False -> {-WRONG-} solve' xs table 

     f :: Integer -> Integer 
     f x 
      | x `mod` 2 == 0 = x `quot` 2 
      | otherwise  = 3 * x + 1 

     countF :: Integer -> Integer -> [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer) 
     countF n cnt (x:xs) table 
      | n == 1 = solve' xs (Map.insert x (True, cnt) table) 
      | otherwise = countF (f n) (cnt + 1) (x:xs) $ checkMap (f n) n table 

     checkMap :: Integer -> Integer -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer) 
     checkMap n rez table = 
      case Map.lookup n table of 
       Nothing -> Map.insert n (False, rez) table 
       Just _ -> table 

은 {-WRONG-} 부분에서 우리는 다음과 같은 예처럼 모든 값을 업데이트해야합니다

--We are looking for 10: 
    10 - (False, 20) 
    | 
    V         {-finally-} update 10 => (True, 10 - 1 - 1 - 1) 
    20 - (False, 40)         ^
    |             | 
    V         update 20 => 20 - (True, 10 - 1 - 1) 
    40 - (False, 13)      ^
    |          | 
    V      update 40 => 40 - (True, 10 - 1) 
    13 - (True, 10)   ^
    |       | 
    --------------------------- 

문제는 그것의 가능하면 내가 모르겠입니다 숫자를 업데이트하고 재 경기를 계속하는 것과 같은 기능으로 2 가지 일을하십시오. C 같은 언어에서 나는 (의사)과 같이 할 수 있습니다 : 우리가 참조에 의해 탄소 나노 튜브를 전송하기 때문에

void f(int n, tuple(b,nr), int &length, table) 
{ 
     if(b == False) f (nr, (table lookup nr), 0, table); 
     // the bool is true so we got a length 
     else 
     { 
      length = nr; 
      return; 
     } 
     // Since this is a recurence it would work as a stack, producing the right output 
     table update(n, --cnt); 
} 

마지막 명령이 작동합니다. 또한 우리는 항상 어떤 점에서 끝낼 것이고 cnt는 0이되어서는 안된다는 것을 알고 있습니다.

+1

지도의 값 유형은 [정수형 정수 중 하나]로 더 관용적으로 표현됩니다 (http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Either). html)하지만, 아직 메모가 작성되지 않은 항목에 대해서는 매핑을 사용하지 않는 것이 더 편리하다고 생각합니다. –

+0

업데이트 된 테이블을 다시 전달해야하는 이유는 무엇입니까? –

+0

@groovy 전화 할 때 (내가 재현 중에있을 때) 나는 어떤 가치를 업데이 트해야하는지 모른다. 먼저 답변을 알고 나서 바닥에서부터 업데이트하기 전에 먼저 많은 재판을해야합니다. 예를 들어 10을 살펴보면 40, 20, 10으로 업데이트해야 할 것이 무엇인지 알기에 앞서 20, 40, 13로 가야합니다. – Thanatos

답변

8

가장 쉬운 최적화 (확인한대로)는 메모입니다. 메모 작성 시스템을 직접 만들려고했지만 메모 값을 저장하는 방법에 관한 문제가 있습니다. 상태 모나드 또는 STArray을 사용하는 등 유지 보수가 가능한 방식으로이 작업을 수행 할 수있는 솔루션이 있습니다. 그러나 문제에 대한 훨씬 간단한 해결책이 있습니다 - haskell의 기존 메모를 사용하십시오. Haskell은 기본적으로 상수 값을 기억하므로 collatz 값을 저장하는 값을 만들면 자동으로 메모가됩니다!

fib :: Int -> Integer 
fib n = fibValues !! n where 
    fibValues = 1 : 1 : zipWith (+) fibValues (tail fibValues) 

fibValues[Integer]이며, 단지 일정한 값이기 때문에,이 memoized된다

이것의 간단한 예는 다음과 피보나치 정의이다. 그러나, 그것이 그것이 infinte 목록이므로, 이것은 결코 끝나지 않을 것이기 때문에, 모든 것이 한번에 memoized되었음을 의미하지는 않습니다. 대신, 값은 필요할 때만 계산됩니다. 왜냐하면 haskell은 게으르다.


그래서 당신이 당신의 문제와 비슷한 것을하면, 많은 일을하지 않고 메모를 할 수 있습니다. 그러나 위의 목록을 사용하면 솔루션에서 제대로 작동하지 않습니다. 이것은 collatz 알고리즘이 주어진 숫자에 대한 결과를 얻기 위해 여러 가지 다른 값을 사용하기 때문에 사용 된 컨테이너는 효율적이기 위해 임의 액세스가 필요합니다. 확실한 선택은 배열입니다.

collatzMemoized :: Array Integer Int 

다음은 올바른 값으로 배열을 채울 필요가 있습니다. 이 함수는 어떤 n에 대해서도 collatz 값을 계산하는 collatz 함수가있는 것으로 작성합니다. 또한 배열은 고정 크기이므로 메모 할 최대 개수를 결정하는 데 값을 사용해야합니다. 나는 백만을 사용할 것이지만, 어떤 값도 사용될 수 있습니다 (이것은 메모리/속도의 절충입니다). 매우 간단

collatzMemoized = listArray (1, maxNumberToMemoize) $ map collatz [1..maxNumberToMemoize] where 
    maxNumberToMemroize = 1000000 

listArray는 경계 주어, 그 범위에있는 모든 collatz 값의 목록은 주어집니다. 값이 게으르기 때문에 모든 collatz 값을 즉시 계산하지는 않습니다.

이제 collatz 함수를 작성할 수 있습니다. ghci에서

collatz :: Integer -> Int 
collatz 1 = 1 
collatz n 
    | inRange (bounds collatzMemoized) nextValue = 1 + collatzMemoized ! nextValue 
    | otherwise = 1 + collatz nextValue 
    where 
    nextValue = case n of 
     1 -> 1 
     n | even n -> n `div` 2 
     | otherwise -> 3 * n + 1 

, 당신은 지금 메모이 제이션의 효과를 볼 수 있습니다 확인되는 숫자가 범위 내에있는 경우 가장 중요한 부분 만 collatzMemoized 배열을 확인하는 것입니다. 시도하십시오 collatz 200000. 완료하는 데 약 2 초가 소요됩니다. 그러나 다시 실행하면 즉시 완료됩니다.

maxCollatzUpTo :: Integer -> (Integer, Int) 
maxCollatzUpTo n = maximumBy (compare `on` snd) $ zip [1..n] (map collatz [1..n]) where 

을 한 후 인쇄 :

마지막으로, 솔루션을 찾을 수 있습니다 당신은 주요 실행하면

main = print $ maxCollatzUpTo 1000000 

는, 결과는 약 10 초에 인쇄됩니다.

이제이 방법의 작은 문제는 많은 스택 공간을 사용한다는 것입니다. 그것은 ghci에서 잘 동작 할 것입니다. (이것은 스택 공간과 관련하여 좀 더 유연한 것으로 보입니다). 그러나 컴파일하고 실행 파일을 실행하려고하면 스택 공간이 오버플로되어 충돌합니다. 따라서 프로그램을 실행하려면 컴파일 할 때 더 많이 지정해야합니다. 이는 컴파일 옵션에 -with-rtsopts='K64m'을 추가하여 수행 할 수 있습니다. 이렇게하면 스택이 64MB로 증가합니다.

은 이제 프로그램을 컴파일하고 실행 할 수 있습니다

> ghc -O3 --make -with-rtsopts='-K6m' problem.hs 

./problem를 실행하면 초 미만에 결과를 제공 할 것입니다.

+3

메모 만 쓰는 것 : 메모 작성과 마찬가지로 용어는 "메모 작성"입니다. – Carl

+0

@Carl : 허, 실종 된 "r"을 결코 알지 못했다. –

+0

어떻게 당신들을 위해 작동합니까?! 이 크기의 문제에 대해서는 Int64 여야합니다. –

2

하스켈에서 매우 직관력이 떨어지는 동적 프로그래밍 알고리즘의 메모를 찾은 것을 기억하고 있습니다. 다음 트릭이 당신을 위해 작동합니다.

하지만 먼저 각 DP 스키마를 이해하지 못합니다. 각 응답마다 많은 항목을 업데이트해야하는 것처럼 보이기 때문에 상당히 비효율적 일 수 있습니다. (a) 나는 Haskell에서 이것을하는 법을 모른다. (b) 문제를 효율적으로 해결하기 위해 이것을 할 필요가 없다 ;-)

나는 다음과 같은 접근법을 제안한다. 입력 번호에 대한 정답을 계산하는 재귀 함수. (힌트 : collatzLength :: Int -> Int과 같은 서명을 갖습니다.)이 함수가 작동하면 연관 목록을 사용하여 array 함수를 사용하여 지연 정의 된 배열의 정의로 해당 정의를 바꾸고 모든 재귀 호출을 (예 : collatzLength 42collatzLength ! 42이됩니다). 그러면 자동으로 배열이 필요한 순서대로 채워집니다! 따라서 "최상위"인 collatzLength 개체는 실제로 함수가 아닌 배열이됩니다.

위에서 제안했듯이 모든 정수 인덱스의 값을 1에서 1,000,000까지 저장해야하므로 맵 테이블 데이터 형식 대신 배열을 사용하여 DP 테이블을 유지합니다.

2

나는 하스켈 컴파일러를 사용하지 않아서 깨진 코드가 있으면 사과드립니다.memoCL 입력으로 테이블을 수신하고 출력으로서 업데이트 된 테이블을 제공 이후

가 메모이 제이션없이 기능 메모이 제이션으로

collatzLength :: Integer -> Integer 
collatzLength n 
    | n == 1 = 1 
    | even n = 1 + collatzLength (n `div` 2) 
    | otherwise = 1 + collatzLength (3*n + 1) 

있어, 유형 서명

memoCL :: Map Integer Integer -> Integer -> (Map Integer Integer, Integer) 

이다. memoCL이 수행해야하는 작업은 let 양식을 사용하여 재귀 호출의 응답을 가로 채고 새 결과를 삽입하는 것입니다.

-- table must have an initial entry for 1 

memoCL table n = case Map.lookup n table of 
    Just m -> (table, m) 
    Nothing -> let (table', m) = memoCL table (collatzStep n) in (Map.insert n (1 + m) table', 1 + m) 

collatzStep :: Integer -> Integer 
collatzStep n = if even n then n `div` 2 else 3*n + 1 

어느 시점에서 위의 관용구에 질려 버립니다. 그렇다면 모나드를위한 시간입니다.

0

내가 어떻게 문제를 해결했는지 알 수 있습니다. https://github.com/fmancinelli/project-euler/blob/master/haskell/project-euler/Problem014.hs

PS :이 땅 : 당신은 여기에 코드를 찾을 수 있습니다

에없는 가장 효율적인 일이 될 수도 있지만 그것의 꽤 기능입니다 부인 성명 : 내가 뭐하고 있었 프로젝트 오일러 하스켈을 배우기 위해 행사 때문에 솔루션의 품질은 논쟁의 여지가 있습니다.

3

하스켈에서 필수 프로그램을 작성하려고 힘든 방법으로 메모를하려고합니다. j_random_hacker가 제안 데이비드 Eisenstat의 솔루션에서 차용하는 것은, 우리는 그것을 해결할 수 있습니다 :

collatzLength :: Integer -> Integer 
collatzLength n 
    | n == 1 = 1 
    | even n = 1 + collatzLength (n `div` 2) 
    | otherwise = 1 + collatzLength (3*n + 1) 

이에 대한 동적 프로그래밍 솔루션은 테이블에 물건을 찾고 재귀를 대체하는 것입니다. 이제 우리는 재귀 호출 대체 할 수있는 함수를 만들어 보자 :

collatzLengthDef :: (Integer -> Integer) -> Integer -> Integer 
collatzLengthDef r n 
    | n == 1 = 1 
    | even n = 1 + r (n `div` 2) 
    | otherwise = 1 + r (3*n + 1) 

이제 우리는 이제 우리는이의 상정 버전을 만들 수

collatzLength :: Integer -> Integer 
collatzLength = collatzLengthDef collatzLength 

로 재귀 알고리즘을 정의 할 수 있습니다를 (그것을위한 수를 소요 이는 t로 테이블 룩업가되도록 collatzLength를 형성함으로써 작동

-- A utility function that makes memoizing things easier 
buildTable :: (Ix i) => (i, i) -> (i -> e) -> Array i e 
buildTable bounds f = array $ map (\x -> (x, f x)) $ range bounds 

collatzLengthTabled :: Integer -> Integer -> Integer 
collatzLengthTabled n = collatzLengthTableLookup 
    where 
     bounds = (1, n) 
     table = buildTable bounds (collatzLengthDef collatzLengthTableLookup) 
     collatzLengthTableLookup = 
      \x -> Case inRange bounds x of 
       True -> table ! x 
       _ -> (collatzLengthDef collatzLengthTableLookup) x 

: 테이블 크기와 그 크기의 테이블)을 이용하여 계산된다 collatzLength 함수 반환 함수의 정의가 가능하지만 재귀 호출은 테이블 조회로 대체됩니다. 테이블 조회 함수는 함수에 대한 인수가 테이블에있는 범위에 있는지 확인하고 함수의 정의로 돌아갑니다. 우리는 심지어 같은 모든 기능을 테이블 화이 작품을 만들 수 있습니다

tableRange :: (Ix a) => (a, a) -> ((a -> b) -> a -> b) -> a -> b 
tableRange bounds definition = tableLookup 
    where 
     table = buildTable bounds (definition tableLookup) 
     tableLookup = 
      \x -> Case inRange bounds x of 
       True -> table ! x 
       _ -> (definition tableLookup) x 

collatzLengthTabled n = tableRange (1, n) collatzLengthDef 

당신은 당신이

let memoized = collatzLengthTabled 10000000 
    ... memoized ... 

그래서 하나의 테이블이 메모리에 내장되어 있는지 확인해야합니다.

1

나는 결국 mark x (b, n) [] xs table

 mark :: Integer -> (Bool, Integer) -> [Integer] -> [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer) 
     mark crtElem (b, n) list xs table 
      | b == False = mark n (findElem n table) (crtElem:list) xs table 
      | otherwise = continueWith n list xs table 

     continueWith :: Integer -> [Integer] -> [Integer] -> Map Integer (Bool, Integer) -> Map Integer (Bool, Integer) 
     continueWith _ []  xs table = solve' xs table 
     continueWith cnt (y:ys) xs table = continueWith (cnt - 1) ys xs (Map.insert y (True, cnt - 1) table) 

     findElem :: Integer -> Map Integer (Bool, Integer) -> (Bool, Integer) 
     findElem n table = 
      case Map.lookup n table of 
       Nothing  -> (False, 0) 
       Just (b, nr) -> (b, nr) 

를 호출 무엇을해야 작업을 수행 할 {-WRONG-} 부분을 수정하지만이 1

보다 (훨씬 덜 장황) 답이 있다는 것을 솔기
0

우리는 재귀 계획을 연구하고 있으므로, 여기에 대한 설명이 있습니다.

이다 고려하자 펑 N (A, B, X) = A + B * X, 마지막 요소 인 A.와 기지국의 스트림 인

{-# LANGUAGE DeriveFunctor 
      , TypeFamilies 
      , TupleSections #-} 

import Data.Functor.Foldable 
import qualified Data.Map as M 
import Data.List 
import Data.Function 
import Data.Int 

data N a b x = Z a | S b x deriving (Functor) 

이 스트림은 여러 종류의 편리 반복. (int 치의 같은 체인 일부 변화가 동형 아니므

type instance Base Int64 = N Int Int64 

instance Foldable Int64 where 
    project 1 = Z 1 
    project x | odd x = S x $ 3*x+1 
    project x = S x $ x `div` 2 

이것은 단지 대수가 아니라 초기 하나 하나를 위해, 우리는 Collatz 시퀀스 INT의 체인을 나타내는 데 사용할 수있다 2 * x 및 (x-1)/3)에 대한 체인의 수를 나타내지 만 고정 점 Base Int64 Int64를 나타내는 데 충분합니다.

이 정의에서, cata는 주어진 대수에 사슬을 공급할 것이며, 사슬 길이에 대한 정수의 메모 맵을 구성하는 데 사용할 수 있습니다. 마지막으로, anamorphism는 다양한 크기의 문제에 대한 해결책의 스트림을 생성하는 데 사용할 수 있습니다 :

problems = ana (uncurry $ cata . phi) (M.empty, 1) where 
    phi :: M.Map Int64 Int -> 
      Base Int64 (Prim [(Int64, Int)] (M.Map Int64 Int, Int64)) -> 
      Prim [(Int64, Int)] (M.Map Int64 Int, Int64) 
    phi m (Z v) = found m 1 v 
    phi m (S x ~(Cons (_, v') (m', _))) = maybe (notFound m' x v') (found m x) $ 
              M.lookup x m 

이 ~ 전에 (단점 ...) 게으른 패턴 매칭을 의미한다. 값이 필요할 때까지 패턴을 만지지 않습니다. 게으른 패턴 일치가 아니라면 항상 전체 체인을 구성하고지도를 사용하면 쓸모가 없습니다. lazy 패턴 매칭을 사용하면 x의 체인 길이가지도에없는 경우에만 값 v '와 m'을 구성합니다.

도우미 함수 (INT, 사슬 길이) 쌍 스트림을 구성 :

found m x v = Cons (x, v) (m, x+1) 
    notFound m x v = Cons (x, 1+v) (M.insert x (1+v) m, x+1) 
이제 단지 제 999999 문제를 가지고, 가장 긴 사슬이있는 하나 알아낼

:

main = print $ maximumBy (compare `on` snd) $ take 999999 problems 

맵 조회가 맵 크기의 대수이므로이 솔루션은 고정 크기가 아니기 때문에 배열 기반 솔루션보다 느리게 작동합니다. 그래도 약 5 초 후에 끝납니다.