使用haskell根据两个相邻位置的另一个字段的值之间的差异计算一个字段的值

Calculating the value of a field based on the difference between the values of another field in two adjacent positions using haskell

我有一个自定义数据对象列表,这些对象使用一个字段 total 每天跟踪增加的总价值。自定义数据类型中的另一个字段是值 new。使用 csv 文件,我读入了 datetotal 的值,并尝试根据这些值计算和设置 new 的值。

data item = Item{
    date :: Day,
    total :: Int,
    new :: Int
}

之前

date total new
01/01/2021 0 0
02/01/2021 2 0
03/01/2021 6 0
04/01/2021 15 0

之后

date total new
01/01/2021 0 0
02/01/2021 2 2
03/01/2021 6 4
04/01/2021 15 9

我的理解是,在 haskell 中,我应该尽量避免使用 for 循环遍历列表直到到达最后一行,例如使用终止的循环控制在达到等于列表长度的值时。

相反,我尝试创建一个函数来分配 new 的值,它可以与 map 一起使用来更新列表中的每个项目。我的问题是这样的功能需要访问正在更新的项目以及 total 的前一个项目的值,我不确定如何在 haskell.

中实现它
--Set daily values by mapping single update function across list
calcNew:: [Item] -> Int -> [Item]
calcNew items = map updateOneItem items 

-- takes an item and a value to fill the new field
updateOneItem :: Item -> Int -> Item
updateOneItem item x = Item date item total item x

是否可以在使用 map 时填充该值?如果不是,是否需要递归解决方案?

我们可以通过将输入列表自身压缩并移动一步来做到这一点。

假设您有一个已经填充了 total 值的项目列表,您想要更新该列表以包含正确的 new 值(当然要构建更新的副本),

type Day = Int

data Item = Item{    -- data Item, NB
    date :: Day,
    total :: Int,
    new :: Int
} deriving Show

calcNews :: [Item] -> [Item]
calcNews [] = []
calcNews totalsOK@(t:ts) = t : zipWith f ts totalsOK
  where
  f this prev = this{ new = total this - total prev }

这给了我们

> calcNews [Item 1 0 0, Item 2 2 0, Item 3 5 0, Item 4 10 0]
[Item {date = 1, total = 0, new = 0},Item {date = 2, total = 2, new = 2},
 Item {date = 3, total = 5,new = 3},Item {date = 4, total = 10, new = 5}]

当然 zipWith f x y == map (\(a,b) -> f a b) $ zip x y,正如我们在您之前的问题中看到的,所以 zipWith 就像 binary map.

有时(尽管这里没有)我们可能还需要访问先前计算的值,以计算下一个值。为此,我们可以通过使用 result 本身的移位版本压缩输入来创建结果:

calcNews2 :: [Item] -> [Item]
calcNews2 [] = []
calcNews2 (t:totalsOK) = newsOK
  where
  newsOK = t : zipWith f totalsOK newsOK
  f tot nw = tot{ new = total tot - total nw }