4

Project Euler ( http://projecteuler.net/problem=14 )の問題 14 を解決しようとしていますが、Haskell を使用して行き詰まりました。

数値が十分に小さい可能性があり、ブルートフォースを実行できることはわかっていますが、それは私の演習の目的ではありません. 次の意味を持つ Mapタイプの中間結果を記憶しようとしています。Map Integer (Bool, Integer)

- 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

元:

  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)

今、別の番号を検索する40と、チェーンが持っていることがわかってい(10 - 1) lengthます。10 を検索すると、長さが 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 を送信しているため、最後の命令は機能します。また、ある時点で終了し、cnt が 1 未満であってはならないことが常にわかっています。

4

7 に答える 7

3

あなたは Haskell で命令型プログラムを書こうとして、難しい方法でメモ化しようとしています。David Eisenstat のソリューションを借りて、j_random_hacker が提案したように解決します。

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

これをテーブル バージョンにすることもできます (テーブル サイズの数値を取り、そのサイズのテーブルを使用して計算される collat​​zLength 関数を返します)。

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

これは、collat​​zLength をテーブル ルックアップとして定義することで機能します。テーブルは関数の定義ですが、再帰呼び出しはテーブル ルックアップに置き換えられます。テーブル ルックアップ関数は、関数への引数がテーブル化された範囲内にあるかどうかを確認し、関数の定義にフォールバックします。次のような関数をテーブル化するためにこれを機能させることもできます。

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 つのテーブルのみがメモリに組み込まれます。

于 2013-08-30T16:07:42.810 に答える
2

Haskell で動的プログラミング アルゴリズムのメモ化が非常に直観に反していることに気付いたのを覚えています。

しかし、最初に、現在の DP スキームをよく理解していませんが、回答ごとに多くのエントリを更新する必要があるように見えるため、非常に非効率的であると思われます。(a) Haskell でこれを行う方法がわかりません。(b) 問題を効率的に解決するためにこれを行う必要はありません ;-)

代わりに、次のアプローチをお勧めします。最初に、入力数値の正しい答えを計算する通常の再帰関数を作成します。(ヒント: のような署名がありcollatzLength :: Int -> Intます。) この関数が機能するようになったら、その定義を、要素が連想リストを使用して関数で遅延定義された配列の定義にarray置き換え、関数へのすべての再帰呼び出しを次のように置き換えます。配列ルックアップ (例:collatzLength 42になりますcollatzLength ! 42)。これにより、必要な順序で配列が自動的に設定されます。したがって、「トップレベル」のcollatzLengthオブジェクトは、実際には関数ではなく配列になります。

上で提案したように、1 から 1,000,000 までのすべての整数インデックスの値を格納する必要があるため、マップ データ型の代わりに配列を使用して DP テーブルを保持します。

于 2013-08-30T14:57:01.063 に答える
0

たぶん、私がどのように問題を解決したか興味深いと思うかもしれません。地球上で最も効率的なものではないかもしれませんが、かなり機能的です:)

ここでコードを見つけることができます: https://github.com/fmancinelli/project-euler/blob/master/haskell/project-euler/Problem014.hs

PS: 免責事項: 私は Haskell を学ぶために Project Euler の演習を行っていたので、ソリューションの品質については議論の余地がある可能性があります。

于 2013-08-30T15:05:18.047 に答える