web-dev-qa-db-ja.com

Haskellでfoldrを使用してマップとフィルターをどのように定義しますか?

私は関数型言語について少し独学をしています(現在Haskellを使用しています)。フォルダーの観点からマップとフィルターを定義する必要があるHaskellベースの割り当てに出くわしました。私の人生の間、私はこれについてどうやって行くのか完全には理解していません。

たとえば、次のようなマップ関数を定義すると、次のようになります。

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] xs

リストの最初の要素が常に無視される理由がわかりません。つまり:

map' (*2) [1,2,3,4]

結果は[2,4,6,8]ではなく[4,6,8]になります

同様に、私のフィルターの関数:

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] xs

実行時:

filter' even [2,3,4,5,6]

結果は[2,4,6]ではなく[4,6]になります

なぜそうなるのでしょうか?そして、期待される結果を得るために、これらの関数をどのように定義する必要がありますか?ラムダ式に何か問題があると思います...

16
klactose

コメントできればいいのですが、残念ながら、カルマが足りません。

他の答えはすべて良いものですが、最大の混乱はxとxsの使用に起因しているように思われます。

あなたがそれを次のように書き直した場合

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\y ys -> (f y):ys) [] xs

xが右側に記載されていないことがはっきりとわかるので、それがソリューションに含まれる可能性はありません。

乾杯

45
tredontho

最初の質問では、foldrにはすでに空のリストのケースがあるため、独自のマップでケースを提供する必要はありません。

map' f = foldr (\x xs -> f x : xs) []

同じことがfilter'にも当てはまります

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

ラムダ式に問題はありませんが、filter'map'の定義に問題があります。短所の場合(x:xs)、頭(x)を食べてから、尾をfoldrに渡します。 foldr関数は、すでに食べた最初の要素を見ることができません。 :)

また、次の点に注意してください。

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

同等( η-同等 ):

filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs
20

Foldrと 関数合成 を使用してマップを次のように定義します。

map :: (a -> b) -> [a] -> [b]
map f = foldr ((:).f) []

そしてフィルターの場合:

filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr (\x xs -> if p x then x:xs else xs) []

Foldrまたはfoldlを使用してリスト上で関数を定義する場合、リスト自体を渡す必要はないことに注意してください。ソリューションの問題は、リストの先頭を削除してからマップをリストに適用することです。これが、結果が表示されたときにリストの先頭が欠落している理由です。

4
coffeMug

定義では、x:xsのパターンマッチングを行っています。つまり、引数が[1,2,3,4]の場合、x1およびxsにバインドされます。リストの残りの部分にバインドされています:[2,3,4]

あなたがしてはいけないことは単にx:の部分を捨てることです。次に、foldrがリスト全体で機能します。

したがって、定義は次のようになります。

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f xs       = foldr (\x xs -> (f x):xs) [] xs

そして

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p xs        = foldr (\x xs -> if p x then x:xs else xs ) [] xs
3
x13n

それについて考える別の方法-次の再帰パターンが頻繁に使用されるため、foldrが存在します。

-- Example 1: Sum up numbers
summa :: Num a => [a] -> a
summa []     = 0
summa (x:xs) = x + suma xs

数値の積を取るか、リストを逆にすることは、前の再帰関数と構造的に非常に似ています。

-- Example 2: Reverse numbers
reverso :: [a] -> [a]
reverso []      = []
reverso (x:xs)  = x `op` reverso xs
  where
    op = (\curr acc -> acc ++ [curr])

上記の例の構造は、初期値(0summaおよび[] for reverso)と、最初の値と再帰呼び出しの間の演算子(+summaおよび(\q qs -> qs ++ [q])リバーソの場合)。したがって、上記の例の関数構造は、一般的に次のように見ることができます。

-- Generic function structure
foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
foo op init_val []      = init_val
foo op init_val (x:xs)  = x `op` foo op init_val xs

この「ジェネリック」fooが機能することを確認するために、fooを使用して、演算子、初期値、およびリスト自体を渡すことにより、リバーソを書き換えることができます。

-- Test: reverso using foo
foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]

他の問題でも機能するように、fooにもっと一般的な型シグネチャを与えましょう。

foo :: (a -> b -> b) -> b -> [a] -> b

さて、あなたの質問に戻りましょう-私たちは次のようにフィルターを書くことができます:

-- Example 3: filter
filtero :: (a -> Bool) -> [a] -> [a]
filtero p []     = []
filtero p (x:xs) = x `filterLogic` (filtero p xs)
  where
     filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)

これもsummaやreversoと非常によく似た構造になっています。したがって、fooを使用して書き直すことができるはずです。リスト[1,2,3,4]から偶数をフィルタリングするとします。次に、fooに演算子(この場合はfilterLogic)、初期値、およびリスト自体を渡します。この例のfilterLogicは、述語と呼ばれるp関数を取ります。これは、呼び出しに対して定義する必要があります。

let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 

haskellのfooはfoldrと呼ばれます。そこで、foldrを使用してフィルターを書き直しました。

let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 

したがって、filterは、これまで見てきたようにfoldrで記述できます。

-- Solution 1: filter using foldr
filtero' :: (a -> Bool) -> [a] -> [a]
filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs 

mapについては、次のように書くこともできます。

-- Example 4: map
mapo :: (a -> b) -> [a] -> [b]
mapo f []   = []
mapo f (x:xs) = x `op` (mapo f xs)
  where
    op = (\curr acc -> (f curr) : acc)

したがって、foldrを使用して書き換えることができます。たとえば、リスト内のすべての数値に2を掛けるには:

let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]

したがって、mapfoldrで記述できます。

-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs
2
Daniel

私はHaskellを初めて使用します(実際、このページで同じ質問をしているのを見つけました)が、これはこれまでのリストとフォルダーについての私の理解です。

  • リストは、cons (:)演算子を使用して次の要素にリンクされる要素です。それらは空のリスト[]で終了します。 (加算(+)1+2+3+4 = 101:2:3:4:[] = [1,2,3,4]と同じように二項演算子と考えてください。
  • foldr関数は、2つのパラメーターを受け取る関数を取ります。これにより、各アイテムが次のアイテムにリンクされる方法を定義するcons演算子が置き換えられます。
  • また、操作の最終値を取得します。これは、空のリストに割り当てられる初期値として取得できます。短所の場合は空のリスト[]です。空のリストを任意のリストにリンクすると、結果はリスト自体になります。したがって、sum関数の場合は0です。乗算関数の場合は1などです。
  • そしてそれはリスト自体を取ります

したがって、私の解決策は次のとおりです。

filter' p = foldr (\x n -> if p x then x : n else n) []

ラムダ式はリンク関数であり、cons (:)演算子の代わりに使用されます。空のリストは、空のリストのデフォルト値です。述語が満たされている場合は、通常どおり(:)を使用して次のアイテムにリンクします。それ以外の場合は、まったくリンクしません。

map' f = foldr (\x n -> f x : n) []

ここでは、xだけでなく、f xを次のアイテムにリンクします。これは単にリストを複製するだけです。

また、リストが空の場合の処理​​方法はすでにfoldrに指示されているため、パターンマッチングを使用する必要はないことに注意してください。

この質問は本当に古いことは知っていますが、とにかく答えたかっただけです。規則に反しないことを願っています。

2
kureta

あなたの解決策はほとんど機能します。)問題は、両方の関数(パターンマッチング内とラムダ式内)にxの2つの異なるバインディングがあるため、最初の要素を見失うことです。

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] (x:xs)

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] (x:xs)

これはトリックになります:)。また、関数をポイントフリースタイルで簡単に記述できます。

1
Jakob Runge
*Main> :{
*Main| map' :: (a -> b) -> [a] -> [b]
*Main| map' = \f -> \ys -> (foldr (\x -> \acc -> f x:acc) [] ys)
*Main| :}
*Main> map' (^2) [1..10]
[1,4,9,16,25,36,49,64,81,100]

*Main> :{
*Main| filter' :: (a -> Bool) -> [a] -> [a]
*Main| filter' = \p -> \ys -> (foldr (\x -> \acc -> if p x then x:acc else acc) [] ys)
*Main| :}
*Main> filter' (>10) [1..100]

上記のスニペットでは、accはアキュムレータを指し、xは最後の要素を指します。

0
Abhijit Gupta