【问题标题】:F# - How can I divide an integer into (mostly equal) ‘buckets’ better than this?F# - 我怎样才能将一个整数分成(大部分相等)比这更好的“桶”?
【发布时间】:2020-11-02 17:36:47
【问题描述】:

我有一个整数,我想将它分成一个大小尽可能相等的整数“桶”列表。 一些例子:

  • 将 10 除以 2 应该会给我一个列表,如“[ 5; 5 ]” (5+5=10),或者,
  • 将 20 除以 3 应该会给我一个列表,如“[ 7; 7; 6 ]” (7+7+6=20),或者,
  • 将 15 除以 4 应该会给我一个列表,如“[ 4; 4; 4; 3 ]” (4+4+4+3) 之类的。

较大的bucket是在末端还是中间还是混入都没有关系——它是划分一个进程并发处理,顺序无关紧要。

我写了一些代码,看起来还可以,但看起来太乱了,很难推理:

let integerBuckets total divider =
    match total, divider with
    | 0, _  -> [ 0 ]
    | 1, _ -> [ 1 ]
    | _, 0 -> [ total ] // Should  never happen, but checked just in case.
    | _, 1 -> [ total ]
    | _ ->
        let result = (double total) / (double divider)
        let remainder = total % divider
        [ for _ in 0 .. (remainder - 1) -> int (ceil result) ]
            @ [ for _ in 0 .. (divider - remainder - 1) -> int (floor result) ]

integerBuckets 15 4

我真的不喜欢 for 循环中的数学;很容易出错和/或意外更改。但我不确定的不仅仅是数学。

谁能给我一些指导,告诉我如何将它“整理”成更容易阅读的更好的 F#?

我不是要求某人提供更好的代码 sn-p,而是要求我指出我应该研究的领域,以便自己学习使代码变得更好。

【问题讨论】:

  • 不确定你在第二部分要做什么,但你可以在同一个表达式match total,divider with | 0,0 -> 中同时匹配总计和除数,这会缩短一点
  • 感谢您提及@onemorecupofcoffee。按照您的建议进行重构向我展示了一个已经(我希望)纠正的逻辑缺陷。我已经根据需要更新了上面的代码。代码的其余部分是计算“桶”的部分,是我想要建议的主要内容。

标签: list f# integer divide


【解决方案1】:

我认为下面的方法会奏效。基本上,我生成一个商列表,然后沿着列表分配 1 个单位的余数,直到它被完全消耗掉。

let integerBuckets total divider =
    let rem = total % divider
    let quo = total / divider
    let dividerList = [1..divider]
    [ for _ in dividerList do yield quo ]  //create list of quotients
    |> List.map2 (+) [for i in dividerList do if i <= rem then 1 else 0] //distribute remainder

编辑:

上面描述的功能可以总结如下:

let integerBuckets2 total divider =
    let rem,quo = total % divider,total / divider
    [ for i in [1..divider] do yield if i <= rem then quo + 1 else quo ]

【讨论】:

  • 谢谢。里面有一些我还没有遇到过的代码(我还是个初学者),所以我需要时间来理解它,但它看起来比我原来的要好得多。乍一看,我认为我最喜欢第二段代码的外观,但我需要了解两者。再次感谢。
  • 这两种方法都比我原来的方法好得多,而且现在我(在一定程度上)理解了它们在做什么,它们都更容易阅读。第一个版本,即使用 map2 的版本,执行时间大约是原来的两倍——五次单独运行中 1,000,000 次重复的平均时间,大约1.1 秒对 0.6 秒——所以我想我会选择时间敏感代码的第二个版本。不管怎样,代码很好。再次感谢。
【解决方案2】:

我建议您对问题采取更加面向数据的方法来拆分作业以进行处理。如果您将要处理的项目存储在一个数组中,那么您可以使用数组函数来操作它们。以下是一些可能的方法:

let chunkByChunkCount array chunkCount =
    let chunkSize = 
        (float (Array.length array) / float chunkCount)
        |> ceil
        |> int
    array |> Array.chunkBySize chunkSize

chunkByChunkCount [| 1 .. 15 |] 4
// [|[|1; 2; 3; 4|]; [|5; 6; 7; 8|]; [|9; 10; 11; 12|]; [|13; 14; 15|]|]



let dealIntoChunks array chunkCount =
    array
    |> Array.indexed
    |> Array.groupBy (fun (i, _) -> i % chunkCount)
    |> Array.map snd
    |> Array.map (Array.map snd)

dealIntoChunks [| 1 .. 15 |] 4
// [|[|1; 5; 9; 13|]; [|2; 6; 10; 14|]; [|3; 7; 11; 15|]; [|4; 8; 12|]|]

【讨论】:

  • 谢谢。与之前的答案一样,在更大程度上,我有很多东西要接受并尝试理解。我不完全确定我将如何进行处理,但我认为您的答案可能更接近我最终需要的答案。整个过程会非常复杂(至少对于初学者来说),所以我现在正在尝试“小步骤”。再次感谢您给我另一种看待这个问题的方式,非常感谢。
猜你喜欢
  • 1970-01-01
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多