【问题标题】:Calculating the value of a field based on the difference between the values of another field in two adjacent positions using haskell使用haskell根据两个相邻位置的另一个字段的值之间的差异计算一个字段的值
【发布时间】:2021-09-03 15:05:49
【问题描述】:

我有一个自定义数据对象列表,这些对象使用一个字段 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 时填充该值?如果不是,是否需要递归解决方案?

【问题讨论】:

    标签: list haskell map-function


    【解决方案1】:

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

    假设您有一个已填充有 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 就像一个二进制 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 }
    

    【讨论】:

    • 很棒的解决方案,谢谢。你能解释在列表名称后面附加 OK 吗?这仅仅是为了将列表与Item 中的字段区分开来吗?这是 Haskell 的最佳实践还是个人偏好?
    • 不客气。不,这只是我为这个特定问题所做的事情。 totalsOK 变量中包含正确的“总计”字段(“OK”),但不是“新”字段的项目。 “newsOK”变量指的是项目列表,其中包含正确的字段(总计和新)。 (因为最初,“新”字段被初始化为 0,所以最初都是不正确的)。只是名称中的一个心理注释,而不是 cmets。助记符。 :)
    猜你喜欢
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多