web-dev-qa-db-fra.com

Remplacer les éléments de liste individuels dans Haskell?

J'ai une liste d'éléments et je souhaite les mettre à jour:

à partir de ceci: ["Off","Off","Off","Off"]

pour ça: ["Off","Off","On","Off"]

Comme je suis un peu nouveau pour Haskell, j'utilise (x:xs)!!y pour extraire et mettre à jour des composants individuels à l'aide de la fonction:

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

puis en saisissant ce qui suit dans ghci: (replace "Off" "On" ["Off",'Off","Off","Off"]) !! 2

J'obtiens ce qui suit: "On"

Il me semble pouvoir extraire et convertir des éléments d'une liste mais je n'arrive pas à obtenir une liste avec l'élément unique converti.

Toute aide à ce sujet serait appréciée.

27
maclunian

Je ne sais pas trop ce que vous essayez de faire. Si vous avez seulement besoin de générer ["Off", "Off", "On", "Off"], vous pouvez le faire explicitement. D'une manière générale, il faut éviter de modifier l'état dans haskell.

Peut-être que vous voulez une fonction pour "modifier" (générer un nouvel élément avec une valeur différente) le nième élément d'une liste? Don donne une approche très générale de ce type de problème. Vous pouvez également utiliser la récursivité explicite:

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

Haskell fournit d'excellentes fonctionnalités pour la manipulation de listes. Si vous ne les connaissez pas déjà, filter, map et foldr/foldl valent la peine d'être examinés, tout comme les listes de compréhension.

16
Philip JF

En règle générale, vous modifiez des éléments d'une liste en fractionnant la liste, en remplaçant un élément et en le réunissant à nouveau.

Pour diviser une liste en index, nous avons:

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

que vous pouvez utiliser pour diviser une liste, comme ceci:

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

il suffit maintenant de faire apparaître l'élément head du composant snd de la liste. Cela se fait facilement avec correspondance de motifs:

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

vous pouvez maintenant rejoindre la liste ensemble, avec un "On":

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

Je vous laisse le soin de rassembler ces pièces en une seule fonction.


En tant que note de style, je suggère d'utiliser un nouveau type de données personnalisé, au lieu de String pour vos bascules:

 data Toggle = On | Off deriving Show
49
Don Stewart

Modification du nième élément

Une opération courante dans de nombreuses langues consiste à affecter une position indexée dans un tableau. Dans python vous pourriez:

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

Le package lens donne cette fonctionnalité avec le (.~) opérateur. Bien que contrairement à python la liste d'origine n'est pas modifiée, une nouvelle liste est plutôt renvoyée.

> 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 n'est qu'une fonction et le (&) L'opérateur, qui fait partie du package lens , n'est qu'une application de fonction inverse. Le voici avec une application de fonction plus courante.

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

L'affectation fonctionne à nouveau parfaitement avec l'imbrication arbitraire de Traversables.

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

ou

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

Ou si vous souhaitez effectuer plusieurs éléments, vous pouvez utiliser:

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

Travailler avec des types autres que des listes

Ce n'est pas seulement limité aux listes, mais cela fonctionnera avec tout type de données qui est une instance de la classe de types Traversable .

Prenons par exemple la même technique qui fonctionne sur arbres forme le package standard 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

Voici une doublure qui fonctionne parfaitement

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

Je ne semble pas efficace pour faire ce genre de choses dans haskell.

6
Cristian Garcia

Voici du code que j'utilise:

-- | 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

En fait, dans de nombreux cas (pas toujours) où vous utiliseriez une liste, un Data.Vector est un meilleur choix.

Il est livré avec une fonction de mise à jour, voir Hackage , qui fait exactement ce dont vous avez besoin.

2
Zsolt Szatmari

Je pense que vous devriez envisager d'utiliser une structure de données autre que List. Par exemple, si vous voulez simplement avoir un état de quatre interrupteurs marche/arrêt, alors:

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

Pour un nombre dynamique de commutateurs, envisagez un mappage à partir de switch name à Bool.

1

Je pense que c'est une manière plus élégante de remplacer un élément individuel:

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

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

Il existe une gestion des erreurs d'entrée. Donc, si l'index i est hors des limites, haskell affichera une sortie incorrecte. (Remarque: dans Haskell, l'indexation commence à partir de 1)

Les câlins se comporteraient comme suit:

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 []

Traitement des erreurs à votre service:

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

si l'index que vous donnez est incorrect, il renvoie la liste d'origine.

0
Ziai

Cette réponse arrive assez tard, mais je pensais partager ce que je pense être une manière efficace de remplacer l'élément nth dans une liste dans Haskell. Je suis nouveau chez Haskell et je pensais que j'allais intervenir.

La fonction set définit le nième élément d'une liste à une valeur donnée:

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)

Comme set parcourt une liste, il casse la liste et si l'index actuel est le n il combine l'élément précédent avec la nouvelle valeur donnée, sinon, il combine l'élément précédent avec le ancienne valeur dans la liste de cet index.

0
theideasmith