简体   繁体   English

Haskell列表串联推断类型

[英]Haskell List concatenation Inferred Type

Trying to replace an element within a list at a given point with a new element then return the element. 尝试用给定的位置用新元素替换列表中的元素,然后返回该元素。

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:(y:yl)) z
   | x == (length yf) = (yf:(z:yl))

Results in this error: 导致此错误:

Inferred type is not general enough
Expression    : setelt
Expected type : Int -> [a] -> a -> [a]
Inferred type : Int -> [[a]] -> [a] -> [[a]]

Doesn't seem to have a problem with the concatenation of yf:y:yl so not sure how to solve. yf:y:yl的串联似乎没有问题,因此不确定如何解决。

You seem to be misunderstanding what the list constructor (:) does. 您似乎误解了列表构造函数(:)作用。 A Haskell list is a sequence of (:) constructors ending with the empty list [] , and pattern matching simply disassembles those constructors in the same order. Haskell列表是(:)构造函数的序列,以空列表[]结尾,并且模式匹配仅按相同顺序反汇编这些构造函数。

So when you pattern match on (yf:(y:yl)) what you're really matching is a list of at least two elements, yf and y , and yl as the rest of the list. 因此,当您在(yf:(y:yl))上进行模式匹配时,您真正匹配的是至少两个元素的列表yfy ,而yl是列表的其余部分。

The inferred type is not what you expect because length yf implies that yf --which is the first element of the input list--is itself a list. 推断的类型不是您期望的类型,因为length yf表示yf是输入列表的第一个元素)本身就是一个列表。

What you need to do instead is walk down the list recursively, building a new list using either the current element of the input or the replacement element x when you reach the right location. 您需要做的是递归地遍历列表,在到达正确的位置时使用输入的当前元素或替换元素x来构建新列表。 The general form should look something like the standard library function map , which is implemented as something like this: 常规形式应类似于标准库函数map ,它是按以下方式实现的:

map _ [] = []
map f (x:xs) = f x : map f xs

Except that you'll need a way to track what index you're searching for, rather than transforming every element. 除了需要一种方法来跟踪要搜索的索引,而不是转换每个元素。

Your current function will also fail if applied to a list of 0 or 1 elements, but fixing that should be easy after correcting the algorithm as a whole. 如果将当前函数应用于0或1个元素的列表,该函数也会失败,但是在整体上校正算法后,修复起来应该很容易。

Read CA McCann's answer to get some more insight, especially for the problems with lists being too short. 阅读CA McCann的答案以获得更多见解,尤其是对于列表太短的问题。 Withouth modifying your algorithm, you can fix your code by the simple following change: 无需修改算法,就可以通过以下简单的更改来修复代码:

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:(y:yl)) z
   | x == (length (y:yl)) = (yf:(z:yl))

which can be rewritten succintly: 可以简洁地重写:

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:ys@(_:yl)) z
   | x == (length ys) = (yf:(z:yl))

In addition to problems with pattern matching (for which I also recommend CA McCann's answer), your program is probably less efficient than you expect, and certainly less efficient than it could be. 除了模式匹配的问题(我也建议CA McCann回答)之外,您的程序效率可能比您预期的要低,并且肯定会比预期的要低。

The problem is that Haskell's lists are simple, singly linked lists, which don't carry around their length in a convenient, O(1)-accessible form. 问题在于Haskell的列表是简单的单链接列表,它们的长度不能以方便的O(1)可访问形式携带。 Haskell's length must count the number of list nodes, which requires O(N) time. Haskell的length必须计算列表节点的数量,这需要O(N)时间。 This means that a straightforwardly corrected version of setelt (as provided by Nicolas Dudebout's answer) will scan the remaining list at each step , yielding O(N^2) worst case performance, rather than the O(N) which is possible. 这意味着setelt的直接更正版本(由Nicolas Dudebout的答案提供)将在每个步骤中扫描其余列表,从而产生O(N ^ 2)最坏情况的性能,而不是可能的O(N)。

To fix this, scan the list first to get the length. 要解决此问题,请先扫描列表以获取长度。 Something like the following implementation, which is O(N) (although using take and drop ends up scanning the list more times than is strictly necessary): 类似于以下实现,它是O(N)(尽管使用takedrop最终扫描列表的次数超过了严格必要的次数):

setelt :: Int -> [a] -> a -> [a]
setelt n ys z = front ++ z:back where
  count = length ys - n
  front = take count ys
  (_:back) = drop count ys

Finally, in case it isn't clear: standard Haskell list indexing (as used by take , drop , and !! ) starts with 0 at the head of the list, not with 1 at the tail (as appears may be your intent with setelt , and is implemented above). 最后,在不清楚的情况下:标准Haskell列表索引(由takedrop!! )在列表的开头以0开头,而不在结尾的1开头(因为这可能是您的意图) setelt ,并在上面实现)。 If your intent is to start with 0 at the head, the implementation is easier: 如果您打算从0开始,那么实现起来会更容易:

setelt n ys z = front ++ z:back where
    front = take n ys
    (_:back) = drop n ys

or, more efficiently: 或者,更有效地:

setelt 0 (y:ys) z = z:ys
setelt n (y:ys) z = y:setelt (n-1) ys z

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM