移动列表并保持状态

时间:2017-11-18 22:26:11

标签: haskell functional-programming

我正在尝试将此伪代码转换为Haskell:

function whatever(list)
    int somestate
    foreach list as item
        if item === 1
              state++
        else 
              state--
    endforeach
    return state

现在这样的事情显然是错误的(状态是3):

test :: [Int] -> Int
test item
       | head item > 1 = 1
       | otherwise = newItem
       where newItem = tail item

在Haskell中实现这样的目标的最佳方法是什么?

4 个答案:

答案 0 :(得分:4)

您只需使用该函数的另一个参数即可完成此操作:

test [] counter = counter
test (x:xs) counter
    | x == 1 = test xs (counter + 1)
    | otherwise = test xs (counter - 1)

这里我们使用模式匹配(x:xs)将列表的头部绑定到x,将列表的尾部绑定到xs。一旦我们到达空列表,我们就会返回最终的计数器。

我们必须使用初始计数器调用此函数,如此

test [1..3] 0

然而,我们可以通过不必传递初始计数器,通过累积计数器来做得更好,就像这样。

test [] = 0
test (x:xs)
    | x == 1 = test xs + 1
    | otherwise = test xs - 1

这次,每次该值等于1时,我们在空列表中的函数结果中加一。最终我们将满足基本情况,即0,其余的值将加起来给我们最终值。

然而,这种在列表上递归并执行值非常常见的模式。我们可以使用折叠:

test = foldl' (\acc x -> if x == 1 then acc + 1 else acc - 1) 0

这基本上是我们以前的递归函数。

有一些方法可以在Haskell中存储持久状态,但通常我们不需要像这样的简单情况。

答案 1 :(得分:4)

我想我可能会这样做:

whatever xs = length ones - length others where
    (ones, others) = partition (1==) xs

唯一可以让我停下来的是对长名单的考虑;如果重要的是列表不能同时存在于内存中,那么这将无法正常工作。

答案 2 :(得分:1)

另一种选择:

> sum . map (bool (-1) 1 . (==1)) $ [1,2,2]
-1
> sum . map (bool (-1) 1 . (==1)) $ [1,2,2,1]
0
> sum . map (bool (-1) 1 . (==1)) $ [1,2,2,1,1,1,1,1]
4

请注意,bool (-1) 1 . (==1)也可以使用基本语法编写

(\x -> if x==1 then 1 else -1)

答案 3 :(得分:1)

你只需调整它并随便洗几下,就像

一样
foo (list) = 
    -- int somestate
    -- foreach list as item   
    -- foreach item in list
           do  item <- list
               if item == 1
                 then  return incr -- state
                 else  return decr -- state
    -- return state

incr state = state + 1
decr state = state - 1

并且已经为自己创建了一个的东西,并推导出类型

foo :: (Num a, Num b, Monad m, Eq a) => m a -> m (b -> b)

我们知道m ~ []所以它实际上是

foo :: (Num a1, Num a, Eq a) => [a] -> [b -> b]

那么,我们可以在一行中对一堆状态更新函数(如incr / decr)做些什么呢?有几件事:

import Control.Monad
import Data.Monoid
import Data.Foldable

sequence . foo    :: (Num a, Num b, Eq a) => [a] -> b -> [b]

foldr (.) id . foo :: (Num a, Num b, Eq a) => [a] -> b -> b

appEndo . foldMap Endo . foo 
                   :: (Num a, Num b, Eq a) => [a] -> b -> b

foldl (.) id . foo :: (Num a, Num b, Eq a) => [a] -> b -> b

appEndo . getDual . foldMap (Dual . Endo) . foo  
                   :: (Num a, Num b, Eq a) => [a] -> b -> b

当然不是第一个。

foldl似乎是这里的门票。但话又说回来,它违背了非常规的乘数用户需要。真的,它是

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr g id 
    where
    g x r state | x==1 = r (incr state)     -- or even `r $! incr state`
             | otherwise = r (decr state)   --         `r $! decr state`

或更简单,

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr g id 
    where
    g x r | x==1 = r . incr
       | otherwise = r . decr

甚至只是

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = foldr (\x -> if x==1 then (. incr) else (. decr)) id 

实际上是我们一直拥有Dual的那个。我认为。有乐趣验证这个(或其他)。 :)

更新:foldr (.) id . foo融合到一个功能中会带来更好看的效果

foo :: (Num t, Num a, Eq a) => [a] -> t -> t
foo = flip $ foldr (\x -> if x==1 then incr else decr) 

但它以相反的顺序处理状态。