【问题标题】:create a function ved that will only remove the last occurrence of the largest item in the list using recursion创建一个函数 ved 仅使用递归删除列表中最大项目的最后一次出现
【发布时间】:2021-10-14 03:39:28
【问题描述】:

您必须使用递归来定义 rmax2,而且您必须从“头开始”这样做。也就是说,除了 cons 运算符、head、tail 和比较之外,您不应该使用 Haskell 库中的任何函数。

我创建了一个函数,它使用列表解析来删除最大项目的所有实例。如何使用递归删除最大数的最后一个实例?

ved :: Ord a => [a] -> [a]
ved [] =[]
ved as = [ a | a <- as, m /= a ]
  where m= maximum as

【问题讨论】:

    标签: haskell


    【解决方案1】:

    将问题拆分为两个更简单的子问题的简单方法包括:

    1. 获取最右边最大值的位置索引
    2. 编写一个通用函数del,用于消除列表中给定位置的元素。这不需要Ord 约束。

    如果我们被允许使用常规库函数,ved 可以这样写:

    ved0 :: Ord a => [a] -> [a]
    ved0  [] = []
    ved0 (x:xs) =
        let
            (maxVal,maxPos) = maximum (zip (x:xs) [0..])
            del k ys        = let (ys0,ys1) = splitAt k ys  in  (ys0 ++ tail ys1)
        in
            del maxPos (x:xs)
    

    zip 生成的对按字典顺序排序,从而确保选择最右边的最大值。

    我们需要手动递归替换库函数。

    关于第 1 步,即找到最右边的最大值的位置,正如通常所做的那样,我们可以使用递归步进函数及其上方的包装器。

    递归步进函数将整个计算上下文作为参数,即:

    1. 最大值的当前候选者,mxv
    2. 当前最大值的最右边位置,mxp
    3. 当前进入原始列表的深度,d
    4. 原始列表的其余部分,xs

    它返回一对:(currentMaxValue, currentMaxPos)

    -- recursive stepping function:
    findMax :: Ord a => a -> Int -> Int -> [a] -> (a, Int)
    findMax mxv mxp d  []    = (mxv,mxp)
    findMax mxv mxp d (x:xs) = if (x >= mxv) then  (findMax x d (d+1) xs)
                                             else  (findMax mxv mxp (d+1) xs)
    
    -- top wrapper:
    lastMaxPos :: Ord a => [a] -> Int
    lastMaxPos  []    = (-1)
    lastMaxPos (x:xs) = snd (findMax x 0 1 xs)
    

    第 2 步,删除位置 k 的列表元素,可以以非常相似的方式处理:

    -- recursive stepping function:
    del1 :: Int -> Int -> [a] -> [a]
    del1 k d  []    = []
    del1 k d (x:xs) = if (d==k)  then  xs  else  x : del1 k (d+1) xs
    
    -- top wrapper:
    del :: Int -> [a] -> [a]
    del k xs = del1 k 0 xs
    

    把它们放在一起:

    我们现在可以编写最终的基于递归的ved 版本。为简单起见,我们将包装函数的内容内联而不是调用它们。

    -- ensure we're only using authorized functionality:
    {-#  LANGUAGE  NoImplicitPrelude    #-}
    import Prelude (Ord, Eq, (==), (>=), (+), ($), head, tail,
                    IO, putStrLn, show, (++))  -- for testing only
    
    
    ved :: Ord a => [a] -> [a]
    ved  []    = []
    ved (x:xs) =
        let
            findMax mxv mxp d  []    = (mxv,mxp)
            findMax mxv mxp d (y:ys) = if (y >= mxv) then  (findMax y d (d+1) ys)
                                                     else  (findMax mxv mxp (d+1) ys)
            (maxVal,maxPos) = findMax x 0 1 xs
            del1 k d (y:ys) = if (d==k)  then  ys  else  y : del1 k (d+1) ys
            del1 k d  []    = []
        in
            del1 maxPos 0 (x:xs)
    
    
    main :: IO ()
    main = do
        let  xs  = [1,2,3,7,3,2,1,7,3,5,7,5,4,3]
             res = ved xs
        putStrLn $ "input=" ++ (show xs) ++ "\n" ++ "  res=" ++ (show res)
    

    【讨论】:

    • 关于压缩和让词典排序为您完成工作的好主意。 :) (当然,通过手动递归,它会回到你做艰苦的工作:))。几点说明:你不用maxVal,所以它不需要名字。与其从findMax 的基本情况返回元组,不如立即从那里调用del1。这将是一个尾声。因此需要将顶部调用更改为对findMax 的调用。也可以是ved xs@(_:_) = ...
    • @WillNess - 非常感谢您的反馈。从技术上讲,你在所有方面都是对的,我实际上给自己写了一个直接调用 del1 的解决方案。但是,我希望您不会介意我在这里坚持使用 pedestrian 代码,因为这是一个基本问题,与 Template Haskell 或软件事务内存无关。我什至不确定将“包装器”功能短路对我来说是个好主意。一些感兴趣的读者可能是尚未了解 Haskell 语法中 @ 和 _ 奥秘的学生。
    • 我希望我没有以任何方式冒犯你,反正这根本不是故意的。关于x:xs,我假设你用它来防止警告,但我身上的(从来没有以前的)Lisper 是为了避免重新考虑以前取消的内容,因此建议。 :) (简单的xs 将是完美的,除了可能的编译器警告,或其他 SO 用户可能发出的关于编译器警告的警告。)
    • @WillNess - 不,我一点也不觉得被冒犯。我理解您的担忧,但我只是想让我的代码易于理解,以供初学者使用 Haskeller,它的目标读者群。毕竟,SO question 30326249 只是询问 @ 在 Haskell 中是什么意思;它的海报无法通过网络搜索找到信息:-(在我看来,用 _ 替换变量名有时会掩盖代码。也许像 xs@(_) 这样的东西会更好。我不认为使用 _ 可以提高代码效率,因为编译器可以很容易地看到该变量未被使用。
    【解决方案2】:

    如果您严格要求使用递归,您可以使用 2 个辅助函数:一个用于反转列表,第二个用于在反转反转列表时删除第一个最大的。

    这会产生一个列表,其中最后一次出现的最大元素被删除。

    我们还使用布尔标志来确保我们不会删除多个元素。

    这是丑陋的代码,我真的不喜欢它。一种使事情更清洁的方法是将列表的反转移动到当前函数之外的辅助函数,以便主函数只有一个辅助函数。另一种方法是使用内置的reverse 函数并仅使用递归进行删除。

    removeLastLargest :: Ord a => [a] -> [a]
    removeLastLargest xs = go (maximum xs) [] xs where
      go n xs []     = go' n True [] xs
      go n xs (y:ys) = go n (y:xs) ys
      go' n f xs []  = xs
      go' n f xs (y:ys)
        | f && y == n = go' n False xs ys
        | otherwise   = go' n f (y:xs) ys
    

    【讨论】:

    • 您的方式删除所有最大的数字,而不是删除最后一个。
    • 谢谢你的解释,我会努力的。我很感激
    • 你是对的@Z-Y.L 我没有清楚地理解 OP 只希望删除最后一个元素。我认为他们在使用递归删除最后一个元素和其余元素时遇到问题。
    【解决方案3】:

    借用Hackage中dropWhileEnd的实现,我们可以实现一个辅助函数splitWhileEnd

    splitWhileEnd :: (a -> Bool) -> [a] -> ([a], [a])
    splitWhileEnd p = foldr (\x (xs, ys) -> if p x && null xs then ([], x:ys) else (x:xs, ys)) ([],[])
    

    splitWhileEnd 根据预测器从末尾拆分列表。例如:

    ghci> xs = [1,2,3,4,3,2,4,3,2]
    ghci> splitWhileEnd (< maximum xs) xs
    ([1,2,3,4,3,2,4],[3,2])
    

    有了这个辅助函数,你可以把ven写成:

    ven :: Ord a => [a] -> [a]
    ven xs = 
        let (x, y) = splitWhileEnd (< maximum xs) xs
         in init x ++ y
    
    ghci> ven xs
    [1,2,3,4,3,2,3,2]
    

    对于您的情况,您可以将splitWhileEnd 重构为:

    fun p = \x (xs, ys) -> if p x && null xs then ([], x:ys) else (x:xs, ys)
    splitWhileEnd' p [] = ([], [])
    splitWhileEnd' p (x : xs) = fun p x (splitWhileEnd' p xs)
    
    ven' xs = let (x, y) = splitWhileEnd' (< maximum xs) xs in init x ++ y
    

    如果 init++ 不允许,您可以手动实现它们。很简单!

    顺便说一句,我想这可能是你的 Haskell 课程作业。如果你的老师给出限制,我认为这是荒谬的。现在谁在从零开始编程?

    无论如何,您始终可以通过手动重新实现内置函数来解决这种限制。祝你好运!

    【讨论】:

      猜你喜欢
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 2015-08-16
      • 1970-01-01
      • 1970-01-01
      • 2016-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多