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