移动列表的特定元素的简单函数

时间:2018-04-06 19:01:53

标签: list haskell

我是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]

2 个答案:

答案 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)

您可以实施pairRightrejoin这些功能吗?它们不应该太难,但是如果你遇到困难,答案就在底部。

您可能还想尝试从头开始定义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

定义splitpairRightrejoin后,您应该能够将它们合并到一个函数中:

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不在列表中,请扭曲代码。

玩得开心。