注:ライブラリ関数を使用せずにこれを実装しようとしています
これは一般的には良い考えではありません。より良い演習はこれです:
- ライブラリ関数を使用してそれを実装する方法を理解します。
- 手順1で使用したライブラリ関数を自分で実装する方法を理解します。
このようにして、3つの重要なスキルを学びます。
- 標準ライブラリ関数とは何ですか、そしてそれらがいつ役立つかの例。
- 問題を細かく分割する方法
- ライブラリにあるような基本的な関数の書き方。
ただし、この場合、はとほぼwhatIndex
同じ関数elemIndex
でData.List
あるため、問題はこのライブラリ関数の独自のバージョンを作成することになります。
ここでの秘訣は、リストを繰り返しながらカウンターをインクリメントすることです。末尾再帰関数を作成するための標準的な手法があります。これは、累積パラメーターと呼ばれます。それはこのように動作します:
- 「フロントエンド」関数と比較して、追加の情報を追跡するために追加のパラメーター(またはそれ以上)を受け取る補助関数を作成します。
- 次に、「実際の」関数を補助関数の呼び出しとして定義します。
したがって、elemIndex
の場合、補助関数は次のようになります(i
現在の要素インデックスの累積パラメータとして):
-- I'll leave the blanks for you to fill.
elemIndex' i x [] = ...
elemIndex' i x (x':xs) = ...
次に、「ドライバー」機能は次のとおりです。
elemIndex x xs = elemIndex 0 x xs
しかし、ここで私が言及しなければならない深刻な問題があります:この関数をHaskellでうまく機能させるのは難しいです。末尾再帰は、厳密な(遅延のない)関数型言語では便利なトリックですが、Haskellではそれほど多くはありません。理由は次のとおりです。
- Haskellの末尾再帰関数は、スタックを爆破する可能性がありますが、
- 非末尾再帰関数は、一定のスペースで実行できます。
私のこの古い答えは、2番目のポイントの例を示しています。
したがって、あなたの場合、非末尾再帰ソリューションは、一定のスペースで実行される(つまり、長いリストでスタックを爆破しない)、おそらく最も簡単なソリューションです。
elemIndex x xs = elemIndex' x (zip xs [0..])
elemIndex' x pairs = snd (find (\(x', i) -> x == x') pairs)
-- | Combine two lists by pairing together their first elements, their second
-- elements, etc., until one of the lists runs out.
--
-- EXERCISE: write this function on your own!
zip :: [a] -> [b] -> [(a, b)]
zip xs ys = ...
-- | Return the first element x of xs such that pred x == True. Returns Nothing if
-- there isn't one, Just x if there is one.
--
-- EXERCISE: write this function on your own!
find :: (a -> Bool) -> [a] -> Maybe a
find pred xs = ...