【问题标题】:Get index of next smallest element in the list in Haskell获取Haskell列表中下一个最小元素的索引
【发布时间】:2018-12-12 18:54:19
【问题描述】:

我是 Haskell 的新手。我很擅长命令式语言,但不擅长功能性语言。 Haskell 是我的第一个函数式语言。

我想弄清楚,如何获取列表中最小元素由我定义的最小元素的索引。

让我通过例子来解释。

例如:

函数签名 minList :: x -> [x]

let x = 2
let list = [2,3,5,4,6,5,2,1,7,9,2] 

minList x list --output 1 <- is index

这应该返回 1。因为 at list[1] 是 3。它返回 1 因为 3 是 x (=2) 之后的最小元素。

let x = 1
let list = [3,5,4,6,5,2,1,7,9,2] 
minList x list -- output 9 <- is index

它应该返回 9,因为在 list[9] 处是 2,而 2 是 1 之后的最小元素。x = 1 这是我定义的。

到目前为止我已经尝试过什么。

minListIndex :: (Ord a, Num  a) => a -> [a] -> a
minListIndex x [] = 0
minListIndex x (y:ys) 
            | x > y =  length ys
            | otherwise = m
            where m = minListIndex x ys

当我加载文件时出现此错误

• Couldn't match expected type ‘a’ with actual type ‘Int’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          minListIndex :: forall a. (Ord a, Num a) => a -> [a] -> a
        at myFile.hs:36:17
    • In the expression: 1 + length ys
      In an equation for ‘minListIndex’:
          minListIndex x (y : ys)
            | x > y = 1 + length ys
            | otherwise = 1 + m
            where
                m = minListIndex x ys
    • Relevant bindings include
        m :: a (bound at myFile.hs:41:19)
        ys :: [a] (bound at myFile.hs:38:19)
        y :: a (bound at myFile.hs:38:17)
        x :: a (bound at myFile.hs:38:14)
        minListIndex :: a -> [a] -> a (bound at myFile.hs:37:1)

当我像这样修改函数时

minListIndex :: (Ord a, Num  a) => a -> [a] -> a
minListIndex x [] = 0
minListIndex x (y:ys) 
            | x > y =  2 -- <- modified...
            | otherwise = 3 -- <- modifiedd
            where m = minListIndex x ys

我再次加载文件,然后它编译并运行,但输出不需要。

有什么问题

| x > y =  length ys
| otherwise = m

?

简而言之:基本上,我想找到最小元素的索引,但高于我在参数/函数签名中定义的 x。

提前感谢您的帮助!

【问题讨论】:

  • 你能解释一下x &gt; y = length ys这行背后的原因吗?为什么如果列表的第一个元素小于x,它应该返回列表其余部分的长度?
  • 您自己“定义最小元素”也有点“奇怪”。如果是这种情况,那么这基本上“崩溃”为findIndex
  • @WillemVanOnsem 感谢您的回答。我只是在测试长度 ys 并且它失败了。关于 findIndex,我不想导入任何库。 :(
  • 根据例子,看起来你基本上想找到给定元素之后的最小元素的index
  • 是的,正是……

标签: list haskell compiler-errors


【解决方案1】:
minListIndex :: (Ord a, Num  a) => a -> [a] -> a

问题是您试图返回泛型类型a 的结果,但它实际上是列表中的索引。

假设您正在尝试评估您的函数以获得双打列表。在这种情况下,编译器应该将函数的类型实例化为Double -&gt; [Double] -&gt; Double,这是无稽之谈。

实际上,编译器注意到您正在返回从列表长度派生的内容,并警告您无法将泛型类型a 与具体Int 匹配。

length ys 返回Int,所以你可以试试这个:

minListIndex :: Ord a => a -> [a] -> Int

关于你原来的问题,似乎你不能用简单的递归来解决它。考虑使用accumulator 定义辅助递归函数。在您的情况下,它可以是一对(min_value_so_far, its_index)

【讨论】:

  • 这有助于我理解。是否可以将length 的返回值转换为 Integer 或 a
  • 这不可能。否则它将允许您编写不安全的代码。
【解决方案2】:

首先,我将索引类型与列表元素类型完全分开。他们没有明显的理由相同。我将使用BangPatterns 扩展来避免空间泄漏而没有太多符号;通过将{-# language BangPatterns #-} 添加到文件的最顶部来启用它。我还将导入Data.Word 以访问Word64 类型。

有两个阶段:首先,找到给定元素的索引(如果它存在)以及该点之外的列表的其余部分。然后,找到尾部最小值的索引。

-- Find the 0-based index of the first occurrence
-- of the given element in the list, and
-- the rest of the list after that element.
findGiven :: Eq a => a -> [a] -> Maybe (Word64, [a])
findGiven given = go 0 where
  go !_k [] = Nothing --not found
  go !k (x:xs)
    | given == xs = Just (k, xs)
    | otherwise = go (k+1) xs

-- Find the minimum (and its index) of the elements of the
-- list greater than the given one.
findMinWithIndexOver :: Ord a => a -> [a] -> Maybe (Word64, a)
findMinWithIndexOver given = go 0 Nothing where
  go !_k acc [] = acc
  go !k acc (x : xs)
    | x <= given = go (k + 1) acc xs
    | otherwise
    = case acc of
        Nothing -> go (k + 1) (Just (k, x)) xs
        Just (ix_min, curr_min)
          | x < ix_min = go (k + 1) (Just (k, x)) xs
          | otherwise = go (k + 1) acc xs

您现在可以将这些函数组合在一起来构建您想要的函数。如果您想要一个通用的Num 结果而不是Word64,您可以在最后使用fromIntegral。为什么使用Word64?与IntWord 不同,它(实际上)保证不会在任何合理的时间内溢出。它可能比直接使用 IntegerNatural 之类的东西快得多。

【讨论】:

  • @WillNess,我想我修好了。
  • 看起来像。一切都是内联的、消除的、融合的;每个 elem,findIndex,length,span,dropWhile,filter,minimum ... 它也可以用 C 编写。:( :/ :S :L :) .... 想要一个“智能编译器”是不受欢迎的,但真的,应该吗?仍然? /ranting/ --- 无论如何,你还有最后一个优化要添加。 可能有时可以提前退出如果我们以离散命中successor最小元素纯属偶然 /i> 索引类型(它离散的)。 :)(一种基数排序——短路式……还是整数排序?)
  • err,元素类型必须是Enum,不是index,d'oh。
  • @WillNess,如果你愿意,我也可以通过让findMinWithIndexOver 获取初始计数器值来融合添加。我可以从zip [0...] 开始,也许我应该这样做。真正的麻烦是需要处理两种“未找到”的情况(即,当给定的元素不在列表中或没有比它更大的元素时)。 dropWhilefilter 都想丢弃长度信息。使用length 本身就是浪费时间,还可能造成空间泄漏。我同意这个解决方案丑陋且类似于 C,但我不知道如何有效地解决它。
  • /chuckle re:addition/ 我知道这是我们不得不经常支付的代价,超出了我们的预期。顺便说一句,我的答案中的代码真的效率低得多吗?我现在也实施了提前救助。
【解决方案3】:

我不清楚你到底想要什么。根据示例,我猜它是:找到出现在 x 之后的高于 x 的最小元素的索引。在这种情况下,这个解决方案很简单Prelude。没有进口

minList :: Ord a => a -> [a] -> Int
minList x l = snd . minimum . filter (\a -> x < fst a) . dropWhile (\a -> x /= fst a) $ zip l [0..]

逻辑是:

  • 创建对列表,[(elem, index)] 使用 zip l [0..]
  • 删除元素,直到找到输入 x 使用 dropWhile (\a -&gt; x /= fst a)
  • 使用filter (\a -&gt; x &lt; fst a)丢弃小于x的元素
  • 找到结果列表的最小值。元组按字典顺序排序,因此适合您的问题
  • 使用snd获取索引

【讨论】:

  • 这个解决方案似乎对我有用。但我有点怀疑,在哪些情况下函数必须有签名?就像在您的解决方案中一样,它没有签名。
  • 几乎总是,编译器可以为您推断类型。然而,不指定签名是一种不好的做法。我正在编辑答案以添加签名;)
【解决方案4】:

你的函数可以用现成的部分构建成

import Data.Maybe (listToMaybe)
import Data.List  (sortBy)
import Data.Ord   (comparing)

foo :: (Ord a, Enum b) => a -> [a] -> Maybe b
foo x = fmap fst . listToMaybe . take 1 
                 . dropWhile ((<= x) . snd) 
                 . sortBy (comparing snd) 
                 . dropWhile ((/= x) . snd)
                 . zip [toEnum 0..] 

这个Maybe 找到列表中给定元素上方的下一个最小元素的索引,位于给定元素之后,在输入列表中。如您所愿。

您可以使用您选择的任何Enum 类型作为索引。

现在您可以将这个更高级别的可执行规范实现为直接递归,使用高效的Map 数据结构来保存您的排序元素高于x 目前看到的,以找到下一个最小的,等等

正确第一,效率第二!


效率更新:删除 after 排序会删除它们 sorted,所以那里的努力是浪费的;实际上它应该被替换为过滤(如the answerLuis Morillo所见)排序之前。如果我们的元素类型在Integral 中(所以它是一个适当的离散类型,与Enum 不同,感谢@dfeuer for pointing 这个!),还有一个机会主义优化的机会:如果我们碰上了succ 最小元素纯属偶然,没有进一步改进的机会,所以我们应该在那个时候退出:

bar :: (Integral a, Enum b) => a -> [a] -> Maybe b
bar x = fmap fst . either Just (listToMaybe . take 1 
                                . sortBy (comparing snd))
                 . findOrFilter ((== succ x).snd) ((> x).snd)
                 . dropWhile ((/= x) . snd)
                 . zip [toEnum 0..] 

findOrFilter :: (a -> Bool) -> (a -> Bool) -> [a] -> Either a [a]
findOrFilter t p = go 
   where  go []                 = Right []
          go (x:xs) | t x       = Left   x
                    | otherwise = fmap ([x | p x] ++) $ go xs

测试:

> foo 5 [2,3,5,4,6,5,2,1,7,9,2] :: Maybe Int
Just 4
> foo 2 [2,3,5,4,6,5,2,1,7,9,2] :: Maybe Int
Just 1
> foo 1 [3,5,4,6,5,2,1,7,9,2] :: Maybe Int
Just 9

【讨论】:

  • 不幸的是,FloatDoubleRatio aEnum 实例,因此您的捷径方法将失败。是的,那是因为Enum 非常损坏,但是你去了。也许您应该改用Integral
  • (再想一想,我就照你说的把 Integral 放进去。谢谢你的建议!)
猜你喜欢
  • 1970-01-01
  • 2023-03-13
  • 2013-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-16
  • 2013-12-03
相关资源
最近更新 更多