【问题标题】:Replace individual list elements in Haskell?替换 Haskell 中的单个列表元素?
【发布时间】:2011-08-16 16:48:10
【问题描述】:

我有一个元素列表,我希望更新它们:

来自:["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"

我似乎能够提取和转换列表的元素,但我似乎无法通过转换的单个元素获得列表。

任何有关此问题的帮助将不胜感激。

【问题讨论】:

    标签: list haskell state


    【解决方案1】:

    通常,您通过拆分列表、替换元素并将其重新组合在一起来修改列表的元素。

    要在索引处拆分列表,我们有:

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

    我会让你把这些部分组合成一个函数。


    作为风格说明,我建议您使用新的自定义数据类型,而不是 String 进行切换:

     data Toggle = On | Off deriving Show
    

    【讨论】:

    • 这不适用于替换列表的最后一个元素
    【解决方案2】:

    我不确定您要做什么。如果您只需要生成 ["Off","Off","On","Off"] ,则可以显式生成。一般来说,应该避免在haskell中修改状态。

    也许您想要的是一个“修改”(生成具有不同值的新元素)列表的第 n 个元素的函数? Don 对这类问题给出了一个非常通用的方法。 你也可以使用显式递归:

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

    Haskell 为列表操作提供了出色的功能。如果你还不知道filtermapfoldr/foldl 都值得一看,列表推导式也是如此。

    【讨论】:

    • 考虑更通用的modifyNth :: Int -> (a -> a) -> [a] -> [a],它采用一个函数来修改元素,而不是硬编码的新值。这个函数现在可以实现为replaceNth n newVal = modifyNth n (const newVal)
    【解决方案3】:

    我认为您应该考虑使用 List 以外的数据结构。例如,如果您只想拥有四个开/关开关的状态,那么:

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

    对于动态数量的开关,请考虑从switch nameBool 的映射。

    【讨论】:

    • 对于动态数量的开关,我建议使用Vector,或者可能是IntMap
    • @John Right,根据您的具体需求,映射可以是 Map、IntMap 或 HashMap(甚至是其他东西)。你为什么会建议一个矢量呢?如果每次运行都是静态的,当然可以,但是如果开关的数量确实需要增加或减少,那就是 Vector 无法做好的事情。
    【解决方案4】:

    这是我一直在使用的一些代码:

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

      我相信这是替换单个元素的更优雅的方式:

      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
      

      如果你给出的索引是错误的,它会返回原始列表。

      【讨论】:

        【解决方案6】:

        更改第 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 只是一个函数,(&amp;) 运算符是 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
        

        【讨论】:

          【解决方案7】:

          这是一个完美运行的单衬

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

          我在 haskell 中做这种事情似乎效率不高。

          【讨论】:

          • ++ 的第二个参数很长时性能很差。这就是ShowS 的原因;递归方法可能会执行得更好。
          【解决方案8】:

          这个答案来得很晚,但我想我会分享我认为在 Haskell 的列表中替换 nth 元素的一种有效方法。我是 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,它将前一个元素与给定的新值结合起来,否则,它将前一个元素与旧值结合起来该索引的列表。

          【讨论】:

            【解决方案9】:

            实际上,对于许多使用 List 的情况(并非总是如此),Data.Vector 是更好的选择。

            它带有更新功能,请参阅Hackage,这正是您所需要的。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-05-19
              • 1970-01-01
              • 2012-01-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多