【问题标题】:Haskell Linear Search (returning index)Haskell 线性搜索(返回索引)
【发布时间】:2019-03-02 07:06:15
【问题描述】:

所以,我希望我的函数返回给定值的索引(如果它在列表中多次出现,则为第一个)。它似乎只适用于空列表或给定元素的索引为 0。你能告诉我我做错了什么吗?

linsearch _ [] = -1
linsearch y (x:xs) = head [i | let j = length xs, i <- [0..j-1], y == x]

谢谢。

【问题讨论】:

  • 您似乎开始编写递归算法,但随后仅对当前情况使用列表表示法 (x) 并忽略列表的其余部分 (xs)。

标签: haskell linear-search


【解决方案1】:

返回-1 表示搜索失败是您在类型系统错误的语言中所做的事情。此外,如果您只关心第一个元素,则没有理由找到 所有 元素。此外,如果结果列表为空,head 将失败。

linsearch :: Eq a => a -> [a] -> Maybe Int
linsearch _ [] = Nothing
linsearch y (x:xs) | y == x = Just 0
                   | otherwise = fmap (+ 1) (linsearch y xs)

空列表显然失败:返回Nothing。使用非空列表,检查是否 第一个元素是您要查找的元素;如果是,返回Just 0。否则,y 可能在也可能不在xs 中;递归找出来。您将在该结果中添加 1 以说明 xs 是原始列表的“移位”版本。您需要使用fmap,因为您不会在某个整数y 上加1;您将把它添加到像Just y 这样的“包装”值,或者如果y 确实不在列表中,则可能是Nothing。注意

fmap (+1) Nothing == Nothing
fmap (+1) (Just 3) == Just 4

【讨论】:

  • 你好。谢谢您的答复。返回这些确切的值是我的任务的限制。我理解你解决这个问题的方法,但不幸的是我不能用这种方法来解决它。我确信可行的解决方案是创建一个新的索引列表并将其压缩到现有的值之一,但我仍在尝试弄清楚如何编写它:)
  • 你可以使用同样的方法;您只需要检查递归调用的返回值。如果是-1,则返回。否则,返回该值 + 1。
  • 或者,只包装Maybe 版本:如果linsearch 返回Nothing,则maybe (-1) id . linsearch 返回-1,或者从Just 响应中提取包装的Int。
【解决方案2】:

TL;DR:解决方案在帖子末尾。

您的定义中不需要列表分解模式,只需通过列表推导将列表作为一个整体处理即可:

linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | let j = length xs, i <- [0..j-1],
                           -- y == x  -- x? what is that?
                           y == (xs!!i)]

现在linsearch 3 [0..9] 返回3,这是应该的。但是linsearch 3 [0..] 根本没有返回——它在尝试计算列表的长度时丢失了,这里根本不需要它!此外,放弃长度计算使我们将算法从其当前的二次形式重新安排为更好的线性形式:

linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | (x,i) <- zip xs [0..], y == x]

linsearch 3 [0..] 现在成功返回3,应该如此。

linsearch 3 [0,2..] 仍然存在分歧(即永远不会返回),因为 Haskell 不知道——它也想要——搜索有序的递增 列出第一个大于我们正在搜索的元素的元素。之所以如此,是因为 [a] 是 lists 的类型,而不是 ordered 列表的类型。

我们也可以定义这样的变体,例如

linsearchOrdered :: Ord a => a -> [a] -> Int
linsearchOrdered y xs = linsearch y $ takeUntil (> y) xs

takeUntil :: (a -> Bool) -> [a] -> [a]
takeUntil p xs = foldr (\x r -> if not (p x) then x:r else [x]) [] xs

果然,它现在可以工作了:

> linsearchOrdered 3 [0,2..]
*** Exception: Prelude.head: empty list

> takeUntil (> 3) [0,2..]
[0,2,4]
it :: (Ord a, Num a, Enum a) => [a]    

> linsearch 3 [0,2,4]
*** Exception: Prelude.head: empty list

等等,什么?错误来自哪里?它来自您对head 的使用:由于在[0,2,4] 中没有找到3,因此您的代码调用head [] 这是一个错误。

相反,我们可以使用take 1,并将其结果转换为Maybe a,标准用法为listToMaybe:

import Data.Maybe

linsearch :: (Eq a) => a -> [a] -> Maybe Int
linsearch y xs = listToMaybe $ take 1 [i | (x,i) <- zip xs [0..], y == x]

好的,现在它正在工作:

> linsearchOrdered 3 [0,2..]
Nothing    

> linsearchOrdered 4 [0,2..]
Just 2

注意返回类型现在不同了。我们不使用特殊值,而是使用包装类型来指示成功(Just)或失败(Nothing):

data Maybe a = Nothing | Just a

如果您真的想要您的原创设计,我们可以将其编码为

linsearch y xs = head $ [i | (x,i) <- zip xs [0..], y == x] ++ [-1]

Haskell 惰性 所以head 将(安全地,现在,使用++ [-1])停在第一个匹配元素上(如果可能的话)。使用head 和takeUntil 几乎不会浪费任何重复的努力。

虽然两者都是折叠。 zip 也可以编码为折叠,因此 linsearch 也是折叠。 folds composition can be fused into one fold 通过融合 reducer 函数,将其全部转换为明确的一次性算法(如果需要)。

【讨论】:

    【解决方案3】:

    Data.List 中有一个函数 elemIndex 可以满足您的要求。你的函数可以写成如下:

    import Data.List
    linsearch y xs = elemIndex y xs
    

    elemIndex 返回一个Maybe Int。所以调用linsearch 5 [] 产生Nothing 而linsearch 5 [3,4,5] 产生Just 2。旁注,上述实现允许您删除xs:linsearch y = elemIndex y 是有效的。现在,如果您只想返回 Int,我们可以使用 case 表达式从 Maybe 中“提取”值:

    linsearch y xs = caseExtract $ elemIndex y xs
      where
        caseExtract m = case m of
                          Just a  -> a
                          Nothing -> -1
    

    如果你想变得真正花哨,你可以通过删除 xs 并使用 composition(.) 和来自 fromMaybe 的 fromMaybe 来“免费”:

    import Data.List
    import Data.Maybe
    linsearch y = fromMaybe (-1) . elemIndex y
    

    使用像elemIndex 这样的库函数的好处是它们是安全的,不像head,如果传递一个空列表就会爆炸。参考:Haskell ReportLYAH Data.Listreddit unsafe headfromMaybe

    【讨论】:

    • caseExtract = fromMaybe (-1)。它在Data.Maybe 中定义。 :)
    • @WillNess 好看!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多