获取Haskell列表中的下一个最小元素的索引

时间:2018-12-12 18:54:20

标签: list haskell compiler-errors

我是Haskell的新手。我对命令式语言非常满意,但对功能不满意。 Haskell是我的第一门功能语言。

我正在尝试找出如何获取列表中最小元素的索引,其中最小元素是由我定义的。

让我通过示例进行解释。

例如:

功能签名     minList :: x-> [x]

id

这应该返回1,因为at列表[1]为3。由于3是x(= 2)之后的最小元素,因此返回1。

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

minList x list --output 1 <- is index

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

到目前为止我尝试过的。

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

加载文件时出现此错误

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高。

谢谢您的帮助!

4 个答案:

答案 0 :(得分:3)

minListIndex :: (Ord a, Num  a) => a -> [a] -> a

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

假设您正在尝试评估函数以获取双打列表。在这种情况下,编译器应将函数的类型实例化为Double -> [Double] -> Double,这是毫无意义的。

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

length ys返回Int,因此您可以尝试以下操作:

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

关于您的原始问题,看来您无法通过简单的递归来解决。考虑使用accumulator定义辅助递归函数。在您的情况下,可以是一对(min_value_so_far, its_index)

答案 1 :(得分: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之类的东西要快得多。

答案 2 :(得分:1)

您的功能可以由现成的部件构成

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之上)保留为直接递归,以直接递归实现来查找倒数第二个,等等。

正确性更高,效率更高!


效率更新:在排序后删除 会删除它们 sorted ,所以那里浪费了精力;实际上,应该在排序之前将其替换为过滤(如the answer中的Luis Morillo所示)。并且如果我们的元素类型在Integral中(因此它是一个适当的离散类型,不像Enum,这要归功于@dfeuerpointing了!),这里有一个机会优化的更多机会:如果我们通过纯粹的机会击中succ个最小元素 ,就没有进一步改善的机会,因此我们应该在那儿纾困:< / p>

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

答案 3 :(得分:1)

我不清楚您到底想要什么。根据示例,我猜是:找到高于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 -> x /= fst a)
  • 使用x丢弃小于filter (\a -> x < fst a)的元素
  • 找到结果列表的最小值。元组是按字典顺序排序的,因此适合您的问题
  • 使用snd
  • 获取索引