简体   繁体   English

确定元素在列表中的位置的递归 Haskell 函数

[英]Recursive Haskell Function to Determine Position of Element in List

I have this code that will return the index of a char in a char array but I want my function to return something like -1 if the value isn't in the array.我有这段代码将返回字符数组中字符的索引,但如果该值不在数组中,我希望我的函数返回类似 -1 的内容。 As it stands the function returns the size of the array if the element isn't in the array.如果元素不在数组中,则函数返回数组的大小。 Any ideas on how to change my code in order to apply this feature?关于如何更改我的代码以应用此功能的任何想法?

I am trying not to use any fancy functions to do this.我尽量不使用任何花哨的功能来做到这一点。 I just want simple code without built-in functions.我只想要没有内置函数的简单代码。

isPartOf :: [(Char)] -> (Char) -> Int
isPartOf [] a = 0
isPartOf (a:b) c
    | a == c = 0
    | otherwise = 1 + isPartOf b c

For example:例如:

*Main> isPartOf [('a'),('b'),('c')] ('z') 
3

But I want:但我想要:

*Main> isPartOf [('a'),('b'),('c')] ('z') 
-1

Let's try to define such a function, but instead of returning -1 in case of element being not a part of the list, we can return Nothing :让我们尝试定义这样一个函数,但是如果元素不是列表的一部分,我们可以返回Nothing ,而不是返回-1

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

So, it works like that:所以,它是这样工作的:

>> isPartOf [('a'),('b'),('c')] ('z')
Nothing
it :: Maybe Int

>> isPartOf [('a'),('b'),('c')] ('c')
Just 2
it :: Maybe Int

After that we can use built-in function fromMaybe to convert the Nothing case to -1 :之后我们可以使用内置函数fromMaybeNothing情况转换为-1

>> fromMaybe (-1) $ isPartOf [('a'),('b'),('c')] ('c')
2
it :: Int

>> fromMaybe (-1) $ isPartOf [('a'),('b'),('c')] ('z')
-1
it :: Int

In case you're curios if such a function already exist, you can use Hoogle for that, searching the [a] -> a -> Maybe Int function: https://www.haskell.org/hoogle/?hoogle=%5Ba%5D+-%3E+a+-%3E+Maybe+Int如果你对这样的函数已经存在好奇,你可以使用 Hoogle,搜索[a] -> a -> Maybe Int函数: https ://www.haskell.org/hoogle/?hoogle =% 5Ba%5D+-%3E+a+-%3E+Maybe+Int

And the first answer will be elemIndex :第一个答案将是elemIndex

>> elemIndex 'c' [('a'),('b'),('c')]
Just 2
it :: Maybe Int

>> elemIndex 'z' [('a'),('b'),('c')]
Nothing
it :: Maybe Int

Hope this helps.希望这可以帮助。

The smallest change to achieve this is实现这一目标的最小变化是

isPartOf :: [Char] -> Char -> Int
isPartOf [] a = (-1)    -- was: 0
isPartOf (a:b) c
    | a == c = 0
    | otherwise = 1 +   -- was: isPartOf b c
          if  (isPartOf b c) < 0  then  (-2)  else  (isPartOf b c)

This is terrible computationally though.但这在计算上很糟糕。 It recalculates the same value twice;它重新计算相同的值两次; what's worse is that the calculation is done with the recursive call and so the recursive call will be done twice and the time complexity overall will change from linear to exponential!更糟糕的是,计算是通过递归调用完成的,因此递归调用将进行两次,整体时间复杂度将从线性变为指数!

Let's not do that.我们不要那样做。 But also, what's so special about Char ?但是, Char什么特别之处? There's lots of stuff special about the Char but none are used here, except the comparison, (==) . Char有很多特别之处,但除了比较(==)之外,这里没有使用任何东西。

The types the values of which can be compared by equality are known as those belonging to the Eq (for "equality") type class: Eq a => a .可以通过相等来比较其值的类型称为属于Eq (用于“相等”)类型类的类型: Eq a => a a is a type variable capable of assuming any type whatsoever; a是一个能够假设任何类型的类型变量; but here it is constrained to be such that ... yes, belongs to the Eq type class.但在这里它被限制为......是的,属于Eq类型类。

And so we write所以我们写

isPartOf :: Eq a => [a] -> a -> Int
isPartOf [] a = (-1)
isPartOf (a:b) c
    | a == c    = 0
    | otherwise = let d = isPartOf b c in
                  1 + if  d < 0  then  (-2)  else  d

That (-2) looks terribly ad-hoc!(-2)看起来非常特别! A more compact and idiomatic version using guards will also allow us to address this:使用守卫的更紧凑和惯用的版本也将允许我们解决这个问题:

isPartOf :: Eq a => [a] -> a -> Int
isPartOf [] a = (-1)
isPartOf (a:b) c
    | a == c    = 0
    | d < 0     = d 
    | otherwise = 1 + d
          where 
          d = isPartOf b c

Yes, we can define d in the where clause, and use it in our guards, as well as in the body of each clause.是的,我们可以在where子句中定义d ,并在我们的守卫以及每个子句的主体中使用它。 Thanks to laziness it won't even be calculated once if its value wasn't needed, like in the first clause.由于懒惰,如果不需要它的值,它甚至不会被计算一次,就像在第一个子句中一样。

Now this code is passable.现在这段代码是可以通过的。

The conditional passing and transformation is captured by the Maybe data type's Functor interface / instance:条件传递和转换由Maybe数据类型的Functor接口/实例捕获:

fmap f Nothing  = Nothing     -- is not changed
fmap f (Just x) = Just (f x)  -- is changed

which is what the other answer here is using.这是这里的另一个答案正在使用的。 But it could be seen as "fancy" when we only start learning Haskell.但是当我们刚刚开始学习 Haskell 时,它可以被视为“花哨的”。

When you've written more functions like that, and become "fed up" with repeating the same pattern manually over and over, you'll come to appreciate it and will want to use it.当您编写了更多这样的函数,并且对一遍又一遍地手动重复相同的模式感到“厌倦”时,您会开始欣赏它并想要使用它。 But only then .但只有这样

Yet another concern is that our code calculates its result on the way back from the recursion's base case .另一个问题是我们的代码在从递归的基本情况返回的路上计算其结果。

But it could instead calculate it on the way forward , towards it, so it can return it immediately when the matching character is found.但是它可以前进的路上计算它,朝着它,所以它可以在找到匹配的字符时立即返回它。 And if the end of list is found, discard the result calculated so far, and return (-1) instead.如果找到列表末尾,则丢弃到目前为止计算出的结果,并返回(-1)代替。 This is the approach taken by the second answer .这是第二个答案所采用的方法。

Though creating an additional function litters the global name space.虽然创建一个额外的函数会破坏全局命名空间。 It is usual to do this by defining it internally , in the so called "worker/wrapper" transformation:通常通过在内部定义它来做到这一点,在所谓的“worker/wrapper”转换中:

isPartOf :: Eq a => [a] -> a -> Int
isPartOf xs c = go xs 0
   where
   go [] i = (-1)
   go (a:b) i
    | a == c    = i
    | otherwise = -- go b (1 + i)
                  go b $! (1 + i)

Additional boon is that we don't need to pass around the unchanged value c -- it is available in the outer scope, from the point of view of the internal "worker" function go , "wrapped" by and accessible only to our function, isPartOf .额外的好处是我们不需要传递未更改的值c —— 从内部“worker”函数go的角度来看,它在外部作用域中可用,被“包裹”并且只能被我们的函数访问, isPartOf

$! is a special call operator which ensures that its argument value is calculated right away, and not delayed.是一个特殊的调用运算符,可确保立即计算其参数值,而不是延迟。 This eliminates an unwanted (in this case) laziness and improves the code efficiency even more.这消除了不必要的(在这种情况下)懒惰并进一步提高了代码效率。

But from the point of view of overall cleanliness of the design it is better to return the index i wrapped in a Maybe (ie Just i or Nothing ) instead of using a "special" value which is not so special after all -- it is still an Int .但是从设计的整体清洁度的角度来看,最好返回i包裹在Maybe (即Just iNothing )中的索引,而不是使用毕竟不是那么特殊的“特殊”值——它是仍然是一个Int

It is good to have types reflect our intentions, and Maybe Int expresses it clearly and cleanly, so we don't have to remember which of the values are special and which regular, so that that knowledge is not external to our program text, but inherent to it.类型反映我们的意图是件好事,而Maybe Int表达的很清楚干净,所以我们不必记住哪些值是特殊的,哪些是常规的,这样知识就不会在我们的程序文本之外,而是它固有的。

It is a small and easy change, combining the best parts from the two previous variants:这是一个小而简单的更改,结合了前两个变体的最佳部分:

isPartOf :: Eq a => [a] -> a -> Maybe Int
isPartOf .....
  .......
  .......  Nothing .....
  .......
  .......  Just i  .....
  .......

(none of the code was tested. if there are errors, you're invited to find them and correct them, and validate it by testing). (所有代码都没有经过测试。如果有错误,请您查找并纠正它们,并通过测试对其进行验证)。

You can achieve it easily if you just pass current element idx to the next recursion:如果您只需将当前元素 idx 传递给下一个递归,您就可以轻松实现它:

isPartOf :: [Char] -> Char -> Int
isPartOf lst c = isPartOf' lst c 0

isPartOf' :: [Char] -> Char -> Int -> Int
isPartOf' [] a _ = -1
isPartOf' (a:b) c idx
    | a == c = idx
    | otherwise = isPartOf' b c (idx + 1)

You are using your function as an accumulator.您将函数用作累加器。 This is cool except the additions with negative one.这很酷,除了负一的添加。 An accumulator cannot switch from accumulating to providing a negative 1. You want two different things from your function accumulator.累加器不能从累加转换为提供负 1。您需要从函数累加器中获得两个不同的东西。 You can use a counter for one thing then if the count becomes unnecessary because no match is found and a negative 1 is issued and nothing is lost.您可以将计数器用于一件事,然后如果计数变得不必要,因为找不到匹配项并且发出负 1 并且没有任何损失。 The count would be yet another parameter.计数将是另一个参数。 ugh.啊。 You can use Maybe but that complicates.您可以使用 Maybe ,但这很复杂。 Two functions, like above is simpler.两个函数,像上面一样更简单。 Here are two functions.这里有两个函数。 The first is yours but the accumulator is not additive it's concatenative.第一个是你的,但累加器不是相加的,它是连接的。

cIn (x:xs) c | x == c    =  [1]
             | null xs   = [-1]
             | otherwise = 1:cIn xs c


Cin ['a','b','c'] 'c'

[1,1,1] [1,1,1]

cIn ['a','b','c'] 'x'

[1,1,-1] [1,1,-1]

So the second function is所以第二个函数是

f ls = if last ls == 1 then sum ls else -1 

It will它会

f $ Cin ['a','b','c'] 'c'

3 3

and

f $ Cin ['a','b','c'] 'x'

-1 -1

You can zero the index base by changing [1] to [0]您可以通过将[1]更改为[0]来将索引基数归零

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM