2012-04-19 7 views
2

私は以下の質問に対する答えを受け入れましたが、haskellの配列がどのように働いているのか誤解しているようでした。私は彼らが単なるリストアップされたと思った。以下の質問を読むときは、それを覚えておいてください。ハスケルの非モノリシックアレイ


私は、haskellのモノリシックアレイは、大きなアレイに使用すると非常に非効率的であることを発見しました。

私はhaskellで配列の非モノリシック実装を見つけることができませんでした。私が必要とするのは、O(1)時間を多次元配列で調べることです。

これをサポートする配列の実装はありますか?

編集:私はモノリシックという用語を誤解しているようです。問題は、haskellの配列がリストのような配列を扱うように思えることです。私は間違っているかもしれません。

EDIT2:非効率的なコードのショート例:

fibArray n = a where 
    bnds = (0,n) 
    a = array bnds [ (i, f i) | i <- range bnds ] 
    f 0 = 0 
    f 1 = 1 
    f i = a!(i-1) + a!(i-2) 

これはi番目のフィールドは、i番目のフィボナッチ数を保持する長さn+1の配列です。しかし、haskellの配列にはO(n)時間のルックアップがあるため、計算にはO(n²)時間がかかります。

+5

モノリシックアレイとはどういう意味ですか? – luqui

+0

'IO(U)Array'と' ST(U)Array'はモノリシックに見えません... –

+1

効率的でないコードの短い例を挙げて、用語をゼロにすることはできますか? –

答えて

4

アレイにはO(1)インデックスがあります。問題は、各要素が遅れて計算されることです。だから、これはあなたがGHCiの中でこれを実行すると何が起こるかです:検索が非常に高速であること

*Main> :set +s 
*Main> let t = 100000 
(0.00 secs, 556576 bytes) 
*Main> let a = fibArray t 
Loading package array-0.4.0.0 ... linking ... done. 
(0.01 secs, 1033640 bytes) 
*Main> a!t -- result omitted 
(1.51 secs, 570473504 bytes) 
*Main> a!t -- result omitted 
(0.17 secs, 17954296 bytes) 
*Main> 

注意、後にそれが既に一度見上げされています。 array関数は、最終的に値を生成するために計算されるサンクへのポインタの配列を作成します。初めて価値を評価するときは、この費用を支払うことになります。ここでa!tを評価するためのサンクの最初の数拡張は以下のとおりです。

a!t -> a!(t-1)+a!(t-2)-> a!(t-2)+a!(t-3)+a!(t-2) -> a!(t-3)+a!(t-4)+a!(t-3)+a!(t-2) 

それは高価だ計算自体のコストではない、むしろそれは、この非常に大きなサンクを作成し、通過する必要があります。

arrayに渡されたリストの値を厳密にしようとしましたが、それは無限ループに終わったようです。

これを回避する一般的な方法の1つは、STArrayなどの可変配列を使用することです。配列の作成中に使用可能になると要素を更新し、最終結果をフリーズして戻すことができます。ベクターパッケージでは、createconstructNの関数がこれを簡単に行う方法を提供します。

-- constructN :: Unbox a => Int -> (Vector a -> a) -> Vector a 


import qualified Data.Vector.Unboxed as V 
import Data.Int 

fibVec :: Int -> V.Vector Int64 
fibVec n = V.constructN (n+1) c 
where 
    c v | V.length v == 0 = 0 
    c v | V.length v == 1 = 1 
    c v | V.length v == 2 = 1 
    c v = let len = V.length v 
     in v V.! (len-1) + v V.! (len-2) 

しかしfibVec機能は、箱なしのベクトルで動作します。規則的なベクトル(および配列)は厳密ではないので、あなたがすでに見つけたのと同じ問題に戻る。残念ながら、IntegerのUnboxedインスタンスはありません。無制限の整数型が必要な場合(このテストではすでにfibVecがオーバーフローしています)、可変配列をIOまたはSTに作成して、必要な厳密性を有効にしています。あなたのfibArray例を特に参照

+0

Qのように再帰的に定義された配列 'arr'の制御された細かさ' k'を使って、 'foldl1(\ a b-> a 'seq' arr!b)[u、u + k..m]ここで(u、v)= bounds arr'、' arr'の 'm'番目の要素にアクセスします。 –

+0

@WillNess私は一般的に要素を強制するだけで2回目にデータ構造をたどる必要はありませんが、この場合は最も実用的な解決策になるかもしれません。 –

+0

であり、 'a!i'で作成されたサンクは非常に小さいです。それはちょうど '\() - > f i'と言う。それが強制されると、再帰関数 'f'が評価されます。ここには大きなサンクはなく、深い回帰があります。あなたの展開例は間違っています: 'a!(t-1)'がポーリングされた後、 'a!(t-2)'は配列からすでに計算された結果を取り出します。 –

8

あなたはHaskellのリンクリストを配列と混同しています。

[1,2,3,5] 

のように定義:

リンクされたリストは、次の構文を使用するデータ・タイプである

data [a] = [] | a : [a] 

これらは、O(n)のインデックスとO(1を支持する古典的な再帰的なデータ・タイプであります)prepend。

O(1)ルックアップを使用して多次元データを検索する場合は、真の配列または行列データ構造を使用する必要があります。良好な候補である:

  • Repa - 高速、平行、多次元配列 - (Tutorial
  • Vector - (可変及び不変の両方)のIntインデックス配列の効率的な実施、強力なループ最適化フレームワークを有します。 (Tutorial
  • HMatrix - GSL、BLAS、およびLAPACKを使用して内部的に実装された基本線形代数と他の数値計算に対する純粋に機能的なインターフェース。
+0

Data.Arrayの配列は、リストを強化するだけではありませんか? – Undreren

+3

これはリストではありません。ポインタ(Data.Array)またはプリミティブ値(Data.Array.Unboxed)のいずれかを保持する連続したメモリブロックです。 –

+0

最新の編集によると、OPはリンクリストを配列と混同しているとは思われません。私はそれが厳密な問題だと考えています。これは、関係するタイプに応じて、簡単に解決できないかもしれません。 –

1

、これを試してみて、それは少し物事をスピードアップしている場合を参照してください。私にとって

-- gradually calculate m-th item in steps of k 
--  to prevent STACK OVERFLOW , etc 
gradualth m k arr       
    | m <= v = pre `seq` arr!m 
    where         
    pre = foldl1 (\a b-> a `seq` arr!b) [u,u+k..m] 
    (u,v) = bounds arr 

を、let a=fibArray 50000ため、gradualth 50000 10 aはちょうどすぐにa!50000を呼び出すの0.65実行時に走りました。

+0

だから、基本的には、大きなチャンクを作り出しているだけですか?私は 'seq'の定義を読み込もうとしましたが、"最初の引数を評価して、通常の形式に戻してから、2番目の引数を結果として返します。 "どういう意味ですか? – Undreren

+0

'seq a b'は' a'を計算し、結果を投げ捨ててから 'b'だけ返します。これはどういうことでしょうか? – Undreren

+0

それ以上のサンクは、逆に - より小さいチャンク。 'seq a b' **は強制依存関係を記録する**:(もし)' b'が* forced *のとき、 'a'が最初に強制され、' b'が強制され、 'b'の値が返されます。強制的にパターンマッチに値が必要な場合は、パターンマッチ2に進みます。ここで、 'a!10'を最初に評価したのと同じように、' a!20'、 'a!30'を配列の上限まで評価したような効果があります。したがって、 'a!n'がポーリングされると、' a!(n-10) 'はすでに事前計算されているので、再帰深度は10以下に制限されています。 –