【问题标题】:F# idiomatic Seq/List Look ahead and modification?F# 惯用的 Seq/List 向前看和修改?
【发布时间】:2015-01-29 04:19:06
【问题描述】:

在 F# 中是否有一种惯用的方式来查看列表/序列/数组并使用在处理当前项目时学到的信息?在我的场景中,还需要对前面的项目进行变异(或以其他方式存储它已更改的事实),以便依次正确处理它。我正在实施一些相当愚蠢的业务规则,这样的模式或技术会很有用。

现在我正在使用累加器来存储信息,然后在处理每个项目时对数组中的项目进行变异。正如您在下面的简化示例中所见,这感觉有点笨拙。我正在解决的问题的实际业务规则更复杂,所以如果有更好的方法,我宁愿不走这条路。本质上,我想摆脱Acc 类型中的graceMonths,而是通过在list/seq/array 中向前看来解决这些月。

模拟示例:当工人每月达到所需的生产水平时,他们会获得某种类型的奖金。如果他们未能达到所需的水平,他们可以通过在接下来的几个月中超过该水平来弥补。同样,他们可以将多余的生产存入银行,以备将来不足的几个月使用。以下脚本显示了一个示例。

type CalendarMonth = 
    { year : int
      month : int }

type InMonth = 
    { month : CalendarMonth
      prodCount : int }

type OutMonth = 
    { month : CalendarMonth
      prodCount : int
      borrowedFrom : InMonth list
      metProd : bool }

type OutMonthAcc = 
    { outMonth : OutMonth
      notUsed : InMonth list }

type IndexOutMonth = 
    { index : int
      outMonth : OutMonth }

type Acc = 
    { index : int
      graceMonths : IndexOutMonth list
      bankedProd : InMonth list
      arrRef : OutMonth array }

type GraceAcc = 
    { processed : IndexOutMonth list
      notUsed : InMonth list }

let createMonth y m c = 
    { InMonth.month = 
          { year = y
            month = m }
      prodCount = c }

let toOutPutMonth (x : InMonth) = 
    { month = x.month
      prodCount = x.prodCount
      borrowedFrom = []
      metProd = false }

let toSimple (x : OutMonth) = sprintf "year: %i, month: %i, metProd: %b" x.month.year x.month.month x.metProd

let solveWithBanked desiredProd bank m = 
    let useProd (acc : OutMonthAcc) inMonth = 
        let m = acc.outMonth
        if m.metProd then 
            { acc with notUsed = inMonth :: acc.notUsed }
        else 
            let borrowed = m.borrowedFrom |> List.sumBy (fun x -> x.prodCount)
            let needed = desiredProd - (m.prodCount + borrowed)
            match inMonth.prodCount with
            | x when x < needed -> 
                { outMonth = { m with borrowedFrom = inMonth :: m.borrowedFrom }
                  notUsed = acc.notUsed }
            | x when x > needed -> 
                let newInMonth = { inMonth with prodCount = inMonth.prodCount - needed }

                let newOutMonth = 
                    { m with borrowedFrom = newInMonth :: m.borrowedFrom
                             metProd = true }
                { outMonth = newOutMonth
                  notUsed = newInMonth :: acc.notUsed }
            | _ -> 
                { outMonth = 
                      { m with borrowedFrom = inMonth :: m.borrowedFrom
                               metProd = true }
                  notUsed = acc.notUsed }
    bank |> List.fold useProd { outMonth = m
                                notUsed = [] }

let solveGrace desiredProd bank (graceLst : IndexOutMonth list) = 
    let useBank acc iOutMonth = 
        let result = iOutMonth.outMonth |> solveWithBanked desiredProd acc.notUsed
        if result.outMonth.metProd then 
            let iMonth = 
                { index = iOutMonth.index
                  outMonth = result.outMonth }
            { processed = iMonth :: acc.processed
              notUsed = result.notUsed }
        else { acc with processed = iOutMonth :: acc.processed }
    graceLst
    |> List.sortBy (fun x -> x.index)
    |> List.fold useBank { processed = []
                           notUsed = bank }

let solve desiredProd acc m = 
    match m.prodCount < desiredProd with
    | true -> // less
        let result = m |> solveWithBanked desiredProd acc.bankedProd
        if result.outMonth.metProd then 
            acc.arrRef.[acc.index] <- result.outMonth
            { acc with index = acc.index + 1
                       bankedProd = result.notUsed }
        else 
            let iMonth = 
                { IndexOutMonth.index = acc.index
                  outMonth = m }
            { acc with index = acc.index + 1
                       graceMonths = iMonth :: acc.graceMonths }
    | false -> // greater
        let newM = 
            { index = acc.index
              outMonth = { m with metProd = true } }

        let newIn = 
            { InMonth.month = m.month
              prodCount = m.prodCount - desiredProd }

        let result = acc.graceMonths |> solveGrace desiredProd (newIn :: acc.bankedProd)
        let solved, unsolved = result.processed |> List.partition (fun x -> x.outMonth.metProd)
        newM :: solved |> List.iter (fun x -> acc.arrRef.[x.index] <- x.outMonth)
        { acc with index = acc.index + 1
                   graceMonths = unsolved
                   bankedProd = result.notUsed }

let jan = createMonth 2013 01 4
let feb = createMonth 2013 02 4
let mar = createMonth 2013 03 6
let apr = createMonth 2013 04 7
let may = createMonth 2013 05 4
let jun = createMonth 2013 06 4

let arr = 
    jan :: feb :: mar :: apr :: may :: jun :: []
    |> Array.ofList
    |> Array.map toOutPutMonth

arr |> Array.fold (solve 5) { index = 0
                              graceMonths = []
                              bankedProd = []
                              arrRef = arr }

let result = 
    arr
    |> Array.map toSimple
    |> List.ofArray

result 的值应显示除六月之外的所有月份。这是 F# 中的正确方法还是有更好的方法?

【问题讨论】:

    标签: f#


    【解决方案1】:

    这是我会在这里尝试的方法:

    • 预先计算每个月的预期金额与实际金额之间的差异,
    • 将月份分为三组 - 低于配额的月份、高于配额的月份和正好达到配额的月份,
    • 尝试平衡低于配额的配额与高于配额的配额。

    前两点对我来说似乎很容易解释,至于第三点,这里是 balance 函数的草稿和示例用法:

    let (|Lt|Eq|Gt|) (a, b) =
        if a = b 
            then Eq
            elif a > b then Gt else Lt
    
    let rec balance below above balanced = 
        match below, above with
        | (x, required)::xs, (y, available)::ys ->
            match required, available with
            | Lt -> balance xs ((y, available - required) :: ys) (x::balanced)
            | Eq -> balance xs ys (x::y::balanced)
            | Gt -> balance ((x, required - available) :: xs) ys (y::balanced)
        | _, _ -> 
            below, above, balanced
    
    balance [("a", 4); ("b", 1)] [ ("c", 2); ("d", 2) ] [ "e" ]
    balance [("a", 1); ("b", 1)] [ ("c", 2); ("d", 2) ] [ "e" ]
    

    基本上你并行地遍历两个列表,从一个“获取”并“添加”到另一个,直到你用完任何一个。剩下的就是使事情平衡的最佳尝试。

    在编写 F# 代码时,通常您希望使用 List 模块等集合 API,但请记住,当您的用例似乎不适合现有方案时,您始终可以退回到“原始”递归。

    【讨论】:

    • 感谢您的反馈。我认为您的解决方案适用于我给出的示例,但现实世界的问题更为复杂。在实际用例中,我对可以借用/携带生产的前/后月数有限制。当您将其纳入此解决方案时,我认为它变得与使用累加器一样复杂。在将您的问题标记为答案之前,我将把这个问题再开放几天,看看是否有其他人有想法。感谢您的反馈!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    • 2019-04-20
    相关资源
    最近更新 更多