web-dev-qa-db-ja.com

Haskellの個々のリスト要素を置き換えますか?

要素のリストがあり、それらを更新したい:

これから: ["Off","Off","Off","Off"]

これに:["Off","Off","On","Off"]

私はHaskellに少し慣れていないので、(x:xs)!!y関数を使用して個々のコンポーネントを抽出および更新します。

replace y z [] = []
replace y z (x:xs)
  | x==y           = z:replace y z xs
  | otherwise      = x:replace y z xs

次にghciに次のように入力します:(replace "Off" "On" ["Off",'Off","Off","Off"]) !! 2

私は以下を得ます:"On"

リストの要素を抽出して変換できるようですが、変換された単一の要素でリストを取得できないようです。

この問題に関するどんな助けもいただければ幸いです。

27
maclunian

何をしようとしているのかわかりません。 ["Off"、 "Off"、 "On"、 "Off"]のみを生成する必要がある場合は、明示的に行うことができます。一般的に言って、haskellで状態を変更することは避けるべきです。

おそらく、リストのn番目の要素を「変更」する(異なる値を持つ新しい要素を生成する)関数が必要でしょうか。ドンはこの種の問題に対して非常に一般的なアプローチをしています。明示的な再帰を使用することもできます。

 replaceNth :: Int -> a -> [a] -> [a]
 replaceNth _ _ [] = []
 replaceNth n newVal (x:xs)
   | n == 0 = newVal:xs
   | otherwise = x:replaceNth (n-1) newVal xs

Haskellはリスト操作のための優れた機能を提供します。それらをまだ知らない場合は、filtermap、およびfoldr/foldlは、リスト内包と同様に、すべて見る価値があります。

16
Philip JF

通常、リストの要素を変更するには、リストを分割し、要素を置き換えて、元に戻します。

リストをインデックスで分割するには、次のようにします。

 splitAt :: Int -> [a] -> ([a], [a]) 

次のように、リストを分割するために使用できます。

 > splitAt 2 ["Off","Off","Off","Off"] 
 (["Off","Off"],["Off","Off"])

ここで、リストのsndコンポーネントのヘッド要素をポップするだけです。これはパターンマッチングで簡単に実行できます。

 > let (x,_:ys) = splitAt 2 ["Off","Off","Off","Off"]
 > x
 ["Off","Off"]
 > ys
 ["Off"]

これで、「オン」でリストに再び参加できます。

 > x ++ "On" : ys
 ["Off","Off","On","Off"]

それらを1つの関数にまとめるのは、あなたにお任せします。


スタイルのメモとして、トグルにStringではなく、新しいカスタムデータ型を使用することをお勧めします。

 data Toggle = On | Off deriving Show
49
Don Stewart

N番目の要素を変更する

多くの言語での一般的な操作は、配列のインデックス位置に割り当てることです。 pythonでは、

>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]

lens パッケージは、(.~)演算子。 pythonとは異なり、元のリストは変更されませんが、新しいリストが返されます。

> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]

element 3 .~ 9は単なる関数であり、(&)演算子は lens パッケージの一部であり、逆の関数を適用するだけです。ここでは、より一般的な関数アプリケーションを使用します。

> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]

割り当ては、Traversablesの任意の入れ子で完全に正常に機能します。

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]

または

> set (element 3) 9 [1,2,3,4,5,6,7]

または、複数の要素に影響を与えたい場合は、以下を使用できます。

> over (elements (>3)) (const 99) [1,2,3,4,5,6,7]
> [1,2,3,4,99,99,99]

リスト以外のタイプの操作

これはリストに限定されるだけでなく、 Traversable タイプクラスのインスタンスであるすべてのデータ型で機能します。

たとえば、同じ手法が trees で機能し、標準の containers パッケージを形成します。

 > import Data.Tree
 > :{
 let
  tree = Node 1 [
       Node 2 [Node 4[], Node 5 []]
     , Node 3 [Node 6 [], Node 7 []]
     ]
 :}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ tree & element 1 .~ 99
1
|
+- 99
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ tree & element 3 .~ 99
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 99
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ over (elements (>3)) (const 99) tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 99
   |
   +- 99
   |
   `- 99
29
Davorak

これは完璧に機能するライナーです

replace pos newVal list = take pos list ++ newVal : drop (pos+1) list

Haskellでこの種のことを行うのは効率的ではないようです。

6
Cristian Garcia

ここに私が使用しているいくつかのコードがあります:

-- | Replaces an element in a list with a new element, if that element exists.
safeReplaceElement
  -- | The list
  :: [a]
  -- | Index of the element to replace.
  -> Int
  -- | The new element.
  -> a
  -- | The updated list.
  -> [a]
safeReplaceElement xs i x =
  if i >= 0 && i < length xs
    then replaceElement xs i x
    else xs


-- | Replaces an element in a list with a new element.
replaceElement
  -- | The list
  :: [a]
  -- | Index of the element to replace.
  -> Int
  -- | The new element.
  -> a
  -- | The updated list.
  -> [a]
replaceElement xs i x = fore ++ (x : aft)
  where fore = take i xs
        aft = drop (i+1) xs
5
mhwombat

実際、多くの場合(常にではありません)にListを使用する場合、Data.Vectorの方が適しています。

更新機能が付いています。 Hackage を参照してください。これは、まさに必要なことを行います。

2
Zsolt Szatmari

List以外のデータ構造の使用を検討する必要があると思います。たとえば、4つのオン/オフスイッチの状態だけを取得したい場合は、次のようにします。

data State = St { sw1, sw2, sw3, sw4 :: Bool }

動的な数のスイッチについては、switch nameからBoolへのマッピングを検討してください。

1

これは個々の要素を置き換えるよりエレガントな方法だと思います:

setelt:: Int -> [a] -> a -> [a]

setelt i list newValue = 
  let (ys,zs) = splitAt i-1 list in  ys ++ newValue ++ tail zs

入力エラー処理があります。したがって、インデックスiが範囲外の場合、haskellは誤った出力を表示します。 (注:Haskellでは、索引付けは1から始まります)

ハグは次のように動作します。

Main> setelt 1 [1,2,3] 9
[9,2,3]
Main> setelt 3 [1,2,3] 9
[1,2,9]
Main> setelt 0 [1,2,3] 9
[9,2,3]
Main> setelt 4 [1,2,3] 9
[1,2,3,9]
Program error: pattern match failure: tail []

サービスでのエラー処理:

setelt i y newValue = 
    if and [i>0, i<= length y]
    then let (ys,zs) = splitAt (i-1) y in  ys ++ [newValue] ++ tail zs
    else y

指定したインデックスが間違っている場合は、元のリストを返します。

0
Ziai

この回答はかなり遅れて届きましたが、Haskellのリストのnth要素を置き換えるefficientの方法だと私が思うことを共有したいと思いました。私はHaskellを初めて使用するので、売り込むつもりだと思いました。

set関数は、リストのn番目の要素を指定された値に設定します

set' :: Int -> Int -> a -> [a] -> [a]
set' _ _ _ [] = []
set' ind curr new arr@(x:xs)
    | curr > ind = arr
    | ind == curr = new:(set' ind (curr+1) new xs)
    | otherwise = x:(set' ind (curr+1) new xs)

set :: Int -> a -> [a] -> [a]
set ind new arr = (set' ind 0 new arr)

setがリストをトラバースするときに、リストを分解し、現在のインデックスが指定されたnの場合、前の要素を指定された新しい値と組み合わせます。それ以外の場合は、前の要素をリスト内のそのインデックスの古い値。

0
theideasmith