【问题标题】:How to set value in nth element in a Haskell list?如何在 Haskell 列表的第 n 个元素中设置值?
【发布时间】:2013-03-09 23:00:14
【问题描述】:

我知道 xs!!n 给了我列表中的第 n 个元素,但我不知道如何编辑该列表中的第 n 个元素。您能告诉我如何编辑列表中的第 n 个元素或至少给出提示吗?例如,如何使第二个元素 'a' 成为 'e' : ['s','t','a','c','k'] ? 谢谢。

【问题讨论】:

标签: list haskell edit


【解决方案1】:

您不能编辑列表的第 n 个元素,值是不可变的。您必须创建一个新列表。但由于不可变性,它可以与原始列表共享更改元素之后的部分。

因此,如果您想对列表的第 n 个元素应用转换(并且前后部分相同),则需要三个部分

  • 列表的前面有问题的元素之前,比如front
  • 有问题的元素,比如element
  • 列表后面有问题的元素之后,比如back

然后你会组装零件

front ++ transform element : back

所以仍然需要以一种很好的方式抓住有趣的部分。

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

这样做,splitAt idx list 返回列表的第一部分,在索引 idx 之前作为该对的第一个组成部分,其余部分作为第二个组成部分,所以

changeNthElement :: Int -> (a -> a) -> [a] -> [a]
changeNthElement idx transform list
    | idx < 0   = list
    | otherwise = case spliAt idx list of
                    (front, element:back) -> front ++ transform element : back
                    _ -> list    -- if the list doesn't have an element at index idx

(注:我已经从0开始计数元素,如果要从1开始计数,需要调整并使用idx-1。)

【讨论】:

  • 非常感谢您提供如此好的和有帮助的答案。你能告诉我changeNthElement idx transform list行中的transform list是否只有一个输入?
  • transform 是您用来从旧元素中获取新元素的函数。我比仅仅用给定的元素替换一个元素更通用,所以你可以做例如changeNthElement 3 (*2) list 将索引 3 处的元素加倍。
【解决方案2】:

因为 Haskell 是一种函数式语言,您不能“编辑”列表中的元素,因为一切都是不可变的。相反,您可以使用以下内容创建一个新列表:

take n xs ++ [newElement] ++ drop (n + 1) xs

但是,在 Haskell 中不建议这样做。有关更多信息,您可以查看此帖子:Haskell replace element in list

【讨论】:

    【解决方案3】:

    更改第 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
    

    【讨论】:

      【解决方案4】:

      我很惊讶下面的方法还没有提到,所以我会添加它以供进一步参考:

      replace index elem = map (\(index', elem') -> if index' == index then elem else elem') . zip [0..]
      
      > replace 2 'e' "stack"
      "steck"
      

      它处理超出范围索引的情况。

      > replace (-1) 'z' "abc"
      "abc"
      > replace 0 'z' "abc"
      "zbc"
      > replace 2 'z' "abc"
      "abz"
      > replace 3 'z' "abc"
      "abc"
      

      它并不比 splitAt 方法慢( O(2N) )。

      【讨论】:

        猜你喜欢
        • 2017-05-12
        • 1970-01-01
        • 2011-01-02
        • 1970-01-01
        • 1970-01-01
        • 2013-03-11
        • 2020-12-08
        • 1970-01-01
        相关资源
        最近更新 更多