F# 地道Seq/List 向前看和修改?
F# idiomatic Seq/List Look ahead and modification?
F# 中是否有一种惯用的方法可以在 list/seq/array 中向前看并使用在当前项目的处理中学到的信息?在我的场景中,还需要改变(或以其他方式存储它已更改的事实)前面的项目,以便依次正确处理它。我正在实施一些相当愚蠢的业务规则,这样的模式或技术会很有用。
现在我正在使用一个累加器来存储信息,然后在我处理每个项目时改变数组的项目。正如您在下面的简化示例中所见,这感觉有点笨拙。我正在解决的问题的实际业务规则更加复杂,所以如果有更好的方法,我宁愿不走这条路。本质上,我想摆脱 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
的值应该显示除 6 月以外的所有月份都达到了产量。这是 F# 中的正确方法还是有更好的方法?
这是我在这里尝试的方法:
- 预先计算每个月的预期金额与实际金额之间的差异,
- 将月份分成三组 - 低于配额、高于配额和恰好达到配额的那些,
- 尝试平衡低于配额的和高于配额的。
前两点对我来说似乎是不言自明的,至于第三点,这是平衡函数的草稿和示例用法:
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" ]
基本上你并行地遍历两个列表,"taking" 从一个列表和 "adding" 到另一个列表,直到你 运行 从任何一个列表中走出来。剩下的就是让事情平衡的最好尝试。
通常,您希望在编写 F# 代码时使用 List 模块等集合 API,但请记住,当您的用例似乎不适合时,您始终可以回退到 "raw" 递归现有方案。
F# 中是否有一种惯用的方法可以在 list/seq/array 中向前看并使用在当前项目的处理中学到的信息?在我的场景中,还需要改变(或以其他方式存储它已更改的事实)前面的项目,以便依次正确处理它。我正在实施一些相当愚蠢的业务规则,这样的模式或技术会很有用。
现在我正在使用一个累加器来存储信息,然后在我处理每个项目时改变数组的项目。正如您在下面的简化示例中所见,这感觉有点笨拙。我正在解决的问题的实际业务规则更加复杂,所以如果有更好的方法,我宁愿不走这条路。本质上,我想摆脱 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
的值应该显示除 6 月以外的所有月份都达到了产量。这是 F# 中的正确方法还是有更好的方法?
这是我在这里尝试的方法:
- 预先计算每个月的预期金额与实际金额之间的差异,
- 将月份分成三组 - 低于配额、高于配额和恰好达到配额的那些,
- 尝试平衡低于配额的和高于配额的。
前两点对我来说似乎是不言自明的,至于第三点,这是平衡函数的草稿和示例用法:
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" ]
基本上你并行地遍历两个列表,"taking" 从一个列表和 "adding" 到另一个列表,直到你 运行 从任何一个列表中走出来。剩下的就是让事情平衡的最好尝试。
通常,您希望在编写 F# 代码时使用 List 模块等集合 API,但请记住,当您的用例似乎不适合时,您始终可以回退到 "raw" 递归现有方案。