【问题标题】:Haskell: Obtaining the Nth element in a Snoc listHaskell:获取Snoc列表中的第N个元素
【发布时间】:2018-08-18 11:38:32
【问题描述】:

我编写了以下函数来查找给定 Snoc 列表中的第 N 个元素(反向缺点),但我认为这不是问题的最佳解决方案。我想知道您是否愿意分享一些见解、代码或建议以查看不同的解决方案;您可能会考虑更有效地完成这项工作:

这是我的功能:

data ListS a = NilS 
              |Snoc (ListS a) a deriving Show

len :: ListS a -> Int
len NilS       = 0
len (Snoc a b) = 1 + len(a) 

nthElementS :: Int -> ListS a -> a
nthElementS _ NilS = error "Empty List"
nthElementS n s    = if n < 0 || n >= len(s) then error "Invalid Index" 
                     else nthAux ((len(s)-1)-n) s where
                          nthAux 0 (Snoc a b) = b
                          nthAux m (Snoc a b) = nthAux (m-1) a

一些例子:

Main> nthElementS 0 (Snoc (Snoc (Snoc (Snoc (Snoc NilS 1) 2) 3) 4) 5)
1

Main> nthElementS 2 (Snoc (Snoc (Snoc (Snoc (Snoc NilS 1) 2) 3) 4) 5) 
3

作为附加询问:实现连接 2 个 Snoc 列表的函数的最佳方法是什么?我已经在考虑一个解决方案,但它需要一个辅助函数来跟踪第 N 个位置,而且我觉得这不会充分利用 Haskell 相对于其他语言的优势。

提前致谢。

【问题讨论】:

  • 您的类型与常规的 cons 列表同构。有了它,你的函数实际上是nthFromEnd。与列表一起保持列表的长度将使实施变得微不足道。否则,只需从反转列表中取出第 n 个元素,或者使用 foldr 进行等效计算,融合反转和长度测量(与答案中的代码不同)。至于 O(1) 附加,你可以例如使用a different data type 表示列表。
  • 谢谢你,Will Ness,我也从没想过要应用 foldr。很棒的建议。

标签: haskell


【解决方案1】:

我们可以使用“try-and-error”方法,首先尝试在“前缀列表”中找到该元素,如果这还不够(因为索引更大) ,然后我们尝试当前元素。如果这还不够,则由“家长电话”来处理这种情况。我们可以通过首先定义一个输出类型略有不同的函数来做到这一点:

nthShelp :: Int -&gt; ListS a -&gt; <b>Either a Int</b>

所以它返回 Left x 以防它找到元素(x 是元素),或者返回 Rightn, withn` 它需要遍历的“剩余”元素。

所以我们可以这样定义:

nthShelp :: Int -> ListS a -> Either a Int
nthShelp n NilS = Right n
nthShelp n (Snoc hs t) = progress nhs
    where nhs = nthElementS n hs
          progress lx@(Left _) = lx
          progress (Right 0) = Left t
          progress (Right n) = Right (n-1)

所以我们首先在列表的头部递归调用,然后通过递减索引直到它达到零来解决递归调用,在这种情况下,我们返回相应的Left t,然后传递Left t退出递归调用。

我们可以利用Either aMonad 的事实,这样写更有效:

nthShelp :: Int -> ListS a -> Either a Int
nthShelp n NilS = Right n
nthShelp n (Snoc hs t) = nthElementS n hs >>= progress
    where progress 0 = Left t
          progress n = Right (n-1)

然后我们可以将nthElementS 写成nthShelp

nthElementS :: Int -> ListS a -> a
nthElementS n l | n < 0 = error "Index too small"
                | Left a <- nthShelp n l = a
                | otherwise = error "Index too large"

在时间复杂度上,还是O(n)(其中n是列表的长度,不是我们的索引想获得)。没有办法用这种数据结构做得更好,因为在我们知道要返回什么元素之前,我们需要知道前缀中元素的数量。

作为附加询问:实现连接 2 个 Cons 列表的函数的最佳方法是什么?我已经在考虑一个解决方案,但它需要一个辅助函数来跟踪第 N 个位置,而且我觉得这不会充分利用 Haskell 相对于其他语言的优势。

你可以在这里看到一个串联,用第一个列表替换 second 列表的(通常很深)嵌套的 NlS。所以如果我们连接concatS l1 NilS,那么它就是l1,如果我们连接concatS l1 (Snoc NilS x),那么它就是Snic l1 x)。所以我们可以递归定义为:

concatS :: ListS a -> ListS a -> ListS a
concatS l1 NilS = l1
concatS l1 (Snoc l2 x) = Snoc (concatS l1 l2) x

上述方法的一个缺点是,如果第一个列表是NilS(如此空),它仍然可以在O(n)中工作。我们可以为此添加一个守卫来处理这种情况:

concatS :: ListS a -> ListS a -> ListS a
concatS NilS = id
concatS l1 = go
    where go NilS = l1
          go (Snoc l2 x) = Snoc (go l2) x

【讨论】:

  • Willem Van Onsem,在分享您的知识时,您不仅慷慨而且非常清楚。感谢您对这个特定问题的详细回答和许多解决方案。我目前正在研究您的解决方案,以更清楚地了解思维过程。它解释得很好。非常感谢,伙计!
猜你喜欢
  • 2011-01-02
  • 2017-05-12
  • 1970-01-01
  • 2013-03-11
  • 1970-01-01
  • 2015-02-10
  • 1970-01-01
  • 2019-09-05
  • 2013-03-09
相关资源
最近更新 更多