6

リスト内の要素を最初の発生時にのみ新しい値に置き換えたい。以下のコードを書きましたが、それを使用すると、一致するすべての要素が変更されます。

replaceX :: [Int] -> Int -> Int -> [Int]
replaceX items old new = map check items where
check item  | item == old = new 
            | otherwise = item

最初に一致した項目でのみ変更が行われるようにコードを変更するにはどうすればよいですか?

助けてくれてありがとう!

4

8 に答える 8

4

次のように、これを再帰的反復として簡単に書くことができます。

rep :: Eq a => [a] -> a -> a -> [a]
rep items old new = rep' items
    where rep' (x:xs) | x == old  = new : xs
                      | otherwise = x : rep' xs
          rep' [] = []
于 2013-01-03T10:53:12.457 に答える
4

直接実装は

rep :: Eq a => a -> a -> [a] -> [a]
rep _ _ [] = []
rep a b (x:xs) = if x == a then b:xs else x:rep a b xs

次のようなことをするための最後の引数としてリストが好きです

myRep = rep 3 5 . rep 7 8 . rep 9 1
于 2013-01-03T13:47:46.807 に答える
3

率直に言うと、これまでの回答のほとんどが好きではありません。dave4420 はmap、私が 2 番目に挙げた素晴らしい洞察を示していますが、彼の解決策も好きではありません。

なぜ私はそれらの答えが気に入らないのですか? このような問題を、より単純な関数、できればライブラリ関数で解決できる小さな問題に分割することで、これらの問題を解決することを学ぶ必要があるからです。この場合、ライブラリはData.Listで、関数はbreak次のとおりです。

breakを predicatepおよび listに適用すると、最初の要素が条件を満たさない要素の中で最も長いプレフィックス (場合によっては空の) で、2 番目の要素がリストの残りの要素でxsあるタプルが返されます。xsp

それを武器に、次のように問題に取り組むことができます。

  1. リストを 2 つの部分に分割します。 が最初に出現する前のすべての要素oldと残りの要素です。
  2. 「rest」リストは空になるか、最初の要素が の最初の出現になりoldます。これらのケースはどちらも簡単に処理できます。

したがって、次の解決策があります。

import Data.List (break)

replaceX :: Eq a => a -> a -> [a] -> [a] 
replaceX old new xs = beforeOld ++ replaceFirst oldAndRest 
    where (beforeOld, oldAndRest) = break (==old) xs
          replaceFirst [] = []
          replaceFirst (_:rest) = new:rest

例:

*Main> replaceX 5 7 ([1..7] ++ [1..7])
[1,2,3,4,7,6,7,1,2,3,4,5,6,7]

だからあなたへの私のアドバイス:

  1. ライブラリをインポートする方法を学びます。
  2. ライブラリのドキュメントを調べて、標準関数を学習します。 Data.List開始するのに最適な場所です。
  3. これらのライブラリ関数をできるだけ使用するようにしてください。
  4. 独学の練習として、いくつかの標準関数を選択してData.List、それらの独自のバージョンを作成できます。
  5. ライブラリ関数の組み合わせでは解決できない問題に遭遇した場合は、役に立つ独自のジェネリック関数を発明してみてください。

編集:breakこれは実際にはPrelude関数であり、インポートする必要がないことに気付きました。それでも、Data.List研究するのに最適なライブラリの 1 つです。

于 2013-01-04T02:29:00.377 に答える
3

Lens ライブラリを使用する代替手段。

>import Control.Lens
>import Control.Applicative

>_find :: (a -> Bool) -> Simple Traversal [a] a                                   
>_find _ _ [] = pure []                                                           
>_find pred f (a:as) = if pred a                                                  
>                       then (: as) <$> f a                                       
>                       else (a:) <$> (_find pred f as)

この関数は (a -> Bool) を受け取ります。これは、変更したい型 'a' に対して True を返す関数です。

5 より大きい最初の数を 2 倍にする必要がある場合は、次のように記述できます。

>over (_find (>5)) (*2) [4, 5, 3, 2, 20, 0, 8]
[4,5,3,2,40,0,8]

レンズの素晴らしいところは、合成することで組み合わせることができることです(。)。したがって、2番目のサブリストの最初の数値<100をゼロにしたい場合は、次のことができます。

>over ((element 1).(_find (<100))) (const 0) [[1,2,99],[101,456,50,80,4],[1,2,3,4]]
[[1,2,99],[101,456,0,80,4],[1,2,3,4]]
于 2013-01-04T03:22:53.807 に答える
2

おそらく最速の解決策ではないかもしれませんが、理解しやすいです:

rep xs x y = 
  let (left, (_ : right)) = break (== x) xs 
  in left ++ [y] ++ right 

[編集]

Dave がコメントしたように、x がリストにない場合、これは失敗します。安全なバージョンは次のとおりです。

rep xs x y = 
  let (left, right) = break (== x) xs 
  in left ++ [y] ++ drop 1 right 

[編集]

ああああ!!!

rep xs x y = left ++ r right where
  (left, right) = break (== x) xs 
  r (_:rs) = y:rs
  r [] = []
于 2013-01-03T14:16:13.330 に答える
0

StateMonadを使用して、これを行うための必須の方法を次に示します。

import Control.Monad.State                                                  

replaceOnce :: Eq a => a -> a -> [a] -> [a]
replaceOnce old new items = flip evalState False $ do
  forM items $ \item -> do
    replacedBefore <- get
    if item == old && not replacedBefore
      then do
        put True
        return new
      else
        return old
于 2013-01-04T09:56:55.680 に答える