我是Haskell的新手,我正在试图弄清楚如何创建一个函数:
TypeError: coercing to Unicode: need string or buffer, datetime.timedelta found
输入:通用列表和相同类型的元素x
前提条件:元素x存在于列表中
输出:
如果n< 0,将x向左移动直到到达列表的第一个
如果n> 0,向右移动x直到到达列表的最后一个
如果n == 0,则从列表中删除x(总是在谈论x的第一次出现,它可能会在列表中显示超过1次)。
请记住,我已完成删除功能
shift:: Eq a => a -> [a] -> Int -> [a]
shift x (h:t) z
找到所需元素的第一个外观并删除它。我猜想我可以在内部使用删除。任何形式的帮助都表示赞赏。
[部分I / O样本]
delete :: Eq b => b -> [b] -> [b]
答案 0 :(得分:2)
正如@WillemVanOnsem建议的那样,您可能想尝试编写一个将目标元素向右移动一个空格的函数。即使这个简化的问题也可能具有挑战性!
看看你是否可以实现直接递归版本。它可能在结构上类似于delete
函数,除了它将交换两个元素而不是在临界点放置一个元素。 (在底部回答 - 寻找simpleShiftRight
的定义。)
一旦你完成了这个,尝试使用这种替代方法,这种方法的优点是可以更容易地推广到解决原始问题。
首先,使用delete
不是很有帮助,因为delete
“忘记”元素最初所在的位置。例如,以下两个:
delete '.' "abc.def"
delete '.' "abcde.f"
收益率"abcdef"
,并且不清楚如何将此结果用于,例如,将句点移位到其位置的右侧。
相反,您真正想要做的是将字符串分解为目标元素之前和之后的部分。也就是说,您想定义一个函数split
,其功能如下:
> split '.' "abc.def"
("abc","def")
> split '.' "abcde.f"
("abcde","f")
通过这个结果,改变周期就变得容易了。
例如,如果我们想将一个位置向右移动,我们可以从定义一个函数开始
pairRight :: ([a], [a]) -> ([a], [a])
就像这样:
> pairRight ("abc","def")
("abcd","ef")
> pairRight ("abcde","f")
("abcdef","")
和一个功能
rejoin :: a -> ([a], [a]) -> [a]
就像这样:
> rejoin '.' ("abcd","ef")
"abcd.ef"
> rejoin '.' ("abcdef","")
"abcdef."
并将它们结合起来:
> rejoin '.' (pairRight (split '.' "abc.def"))
"abcd.ef"
> rejoin '.' (pairRight (split '.' "abcde.f"))
"abcdef."
获取将角色向右移动一个空格的函数。
现在,split
可以根据库函数break
来定义,如下所示:
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = let (a, _:b) = break (==x) xs in (a,b)
您可以实施pairRight
和rejoin
这些功能吗?它们不应该太难,但是如果你遇到困难,答案就在底部。
您可能还想尝试从头开始定义split
而不使用break
。这是一个有点棘手的递归函数。如果你从“明显”的方法开始:
split :: Eq a => a -> [a] -> ([a], [a])
split x (y:ys) | x == y = (..., ys)
| otherwise = split x ys
split _ [] = error "split: target not found"
你会遇到问题。目前尚不清楚如何填写...
,因为你在递归中抛弃了列表的开头。希望您已经了解到这方面的一个方法是引入一个额外的参数来跟踪已经处理的列表元素并定义一个函数:
split' :: Eq a => a -> [a] -> [a] -> ([a], [a])
split' x ls (r:rs) = ...
其中x
是我们要查找的元素,ls
是我们已经处理过的列表左侧的元素集(我们没有找到副本的地方) x
),(r:rs)
是我们仍在处理的列表的右侧。
如果您需要进一步提示,请参阅以下模板:
split' x ls (r:rs) | x == r = (..., ...)
| otherwise = split' x (...) rs
split' _ _ [] = error "split: target not found"
你能在这里填写...
吗?如果可以,那么你可以定义:
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = split' x [] xs
定义split
,pairRight
和rejoin
后,您应该能够将它们合并到一个函数中:
shiftRight :: Eq a => a -> [a] -> [a]
可以将目标元素向右移动一个位置。
如果你遇到困难,这里有shiftRight
及其完整定义
助手:
shiftRight :: (Eq a) => a -> [a] -> [a]
shiftRight x xs = rejoin x (pairRight (split x xs))
-- alternative definition of split:
-- split :: Eq a => a -> [a] -> ([a], [a])
-- split x xs = let (a, _:b) = break (==x) xs in (a,b)
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = split' x [] xs
split' :: Eq a => a -> [a] -> [a] -> ([a], [a])
split' x ls (r:rs) | x == r = (ls, rs)
| otherwise = split' x (ls ++ [r]) rs
split' _ _ [] = error "split: target not found"
pairRight :: ([a], [a]) -> ([a], [a])
pairRight (ls, r:rs) = (ls ++ [r], rs)
rejoin :: a -> ([a], [a]) -> [a]
rejoin x (ls, rs) = ls ++ [x] ++ rs
在此版本中,尝试shiftRight
不在列表中或已位于最右侧位置的目标将产生错误。您可能想尝试修复它。 (请注意,split
可能会返回Maybe [a]
,如果找不到目标,则会产生Nothing
;也不应该太难以修改pairRight
以便它如果该对已经向右移动了就没有任何意义。)
如果这对于一个简单的问题似乎很麻烦,我想是的。在实际代码中,经验丰富的Haskell程序员可能会编写直接递归版本:
simpleShiftRight :: (Eq a) => a -> [a] -> [a]
simpleShiftRight x (y:z:rest) | x == y = z:y:rest
| otherwise = y : simpleShiftRight x (z:rest)
simpleShiftRight _ rest = rest
或使用break
的
。
simpleShiftRight :: (Eq a) => a -> [a] -> [a]
simpleShiftRight x xs = case break (==x) xs of
(ls, y:z:rs) -> ls ++ z:y:rs
_ -> xs
两个版本都很简洁,处理所有角落案例,并且“显然是正确的”。如前所述,缺点是这个版本不容易归结为原始问题。
上面的版本很容易概括 - 你只需要用更复杂的移位功能替换pairRight
。例如,定义:
pairRightN :: Int -> ([a],[a]) -> ([a],[a])
pairRightN n (ls, r:rs) | n > 0 = pairRightN (n-1) (ls ++ [r], rs)
pairRightN _ (ls, rs) = (ls, rs)
允许您处理n
的所有正值(即所有正确的值,无论多大)。要进一步概括它以处理左移也不是太难。
答案 1 :(得分:0)
对于您是Haskell的新手,最好将问题分解为更简单的子问题。解决问题的一个技巧是:
thinking of "insert" instead of "shift". Pseudo code:
- remove x from the list
- insert x back to the list at the proper position based on given n
您已经实现了删除,让我们利用它。享受简单的代码:
-- assume x is in the list, otherwise your problem would not make sense
shift :: Eq a => a->[a]->Int->[a]
shift x lst n = if n == 0 then delete x lst
else insert x (n + (position x lst)) (delete x lst)
子问题是:
1. Delete x from the list: you did that
delete :: Eq a => a->[a]->[a]
delete x [] = []
delete x (head:tail) = if x == head then tail else head : delete x tail
2. find the position of x in the list
-- assume x in the list
position :: Eq a => a->[a]->Int
position x (head:tail) = if x == head then 1 else 1 + position x tail
3. -- insert x into the list at nth position
insert :: a->Int->[a]->[a]
insert x n (head:tail) = if n <= 1 then x : (head:tail)
else head : (insert x (n-1) tail)
insert x n lst = if n >= length lst then lst ++ [x]
else insert x n lst
你的先决条件是x在列表中,否则没有意义。如果要包含案例x不在列表中,请扭曲代码。
玩得开心。