私は関数型言語について少し独学をしています(現在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]になります
なぜそうなるのでしょうか?そして、期待される結果を得るために、これらの関数をどのように定義する必要がありますか?ラムダ式に何か問題があると思います...
コメントできればいいのですが、残念ながら、カルマが足りません。
他の答えはすべて良いものですが、最大の混乱はxとxsの使用に起因しているように思われます。
あなたがそれを次のように書き直した場合
map' :: (a -> b) -> [a] -> [b]
map' f [] = []
map' f (x:xs) = foldr (\y ys -> (f y):ys) [] xs
x
が右側に記載されていないことがはっきりとわかるので、それがソリューションに含まれる可能性はありません。
乾杯
最初の質問では、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
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を使用してリスト上で関数を定義する場合、リスト自体を渡す必要はないことに注意してください。ソリューションの問題は、リストの先頭を削除してからマップをリストに適用することです。これが、結果が表示されたときにリストの先頭が欠落している理由です。
定義では、x:xs
のパターンマッチングを行っています。つまり、引数が[1,2,3,4]
の場合、x
は1
および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
それについて考える別の方法-次の再帰パターンが頻繁に使用されるため、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])
上記の例の構造は、初期値(0
summaおよび[]
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]
したがって、mapはfoldrで記述できます。
-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs
私はHaskellを初めて使用します(実際、このページで同じ質問をしているのを見つけました)が、これはこれまでのリストとフォルダーについての私の理解です。
(:)
演算子を使用して次の要素にリンクされる要素です。それらは空のリスト[]
で終了します。 (加算(+)
1+2+3+4 = 10
、1:2:3:4:[] = [1,2,3,4]
と同じように二項演算子と考えてください。[]
です。空のリストを任意のリストにリンクすると、結果はリスト自体になります。したがって、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に指示されているため、パターンマッチングを使用する必要はないことに注意してください。
この質問は本当に古いことは知っていますが、とにかく答えたかっただけです。規則に反しないことを願っています。
あなたの解決策はほとんど機能します。)問題は、両方の関数(パターンマッチング内とラムダ式内)に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)
これはトリックになります:)。また、関数をポイントフリースタイルで簡単に記述できます。
*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は最後の要素を指します。