在必须计数时无法将某些C#代码转换为F#

时间:2019-05-19 09:39:00

标签: f#

我有以下C#代码:

(无需了解其详细信息,仅用于说明问题)

            long VolumeBeforePrice = 0;
            long Volume = 0;
            var ContractsCount = 0.0;

            var VolumeRequested = Candle.ConvertVolumes(MinVolume);

            // go through all entries
            foreach (var B in Entries)
            {
                // can we add the whole block?
                if (Volume + B.VolumeUSD <= VolumeRequested)
                {
                    // yes, add the block and calculate the number of contracts
                    Volume += B.VolumeUSD;
                    ContractsCount += B.VolumeUSD / B.PriceUSD;
                }
                else
                {
                    // no, we need to do a partial count
                    var Difference = VolumeRequested - Volume;
                    ContractsCount += Difference / B.PriceUSD;
                    Volume = VolumeRequested;   // we reached the max
                }

                VolumeBeforePrice += B.VolumeUSD;

                if (Volume >= VolumeRequested) break;
            }

它遍历交易订单簿的条目并计算可用于特定美元金额的合同数量。

逻辑很简单:对于每个条目,都有一个给定价格的合同块,因此它要么添加整个块,要么在请求中不包含部分块。

由于我是该语言的新手,因此我尝试将其移至F#,并且遇到一些问题:

这是部分实现:

    let mutable volume = 0L
    let mutable volumeBeforePrice = 0L
    let mutable contractsCount = 0.0

    entries |> List.iter (fun e ->
        if volume + e.VolumeUSD <= volumeRequested then
            volume <- volume + e.VolumeUSD;
            contractsCount <- contractsCount + float(e.VolumeUSD) / e.PriceUSD
        else
            let difference = volumeToTrade - volume
            contractsCount <- contractsCount + difference / B.PriceUSD
            volume = volumeRequested // this is supposed to trigger an exit on the test below, in C#
    )

我停在那里,因为它看起来不像是一种F#的方式:)

所以,我的问题是:如何构造List.iter以便:

- I can use counters from one iteration to the next? like sums and average passed to the next iteration
- I can exit the loop when I reached a specific condition and skip the last elements? 

1 个答案:

答案 0 :(得分:5)

我会避免使用mutable并使用纯函数。例如,您可以为结果定义一条记录,例如Totals(您可能会有一个更有意义的名称):

type Totals =
    { VolumeBeforePrice : int64
      Volume : int64
      ContractsCount : float }

然后您可以创建一个函数,该函数将当前总计和一个条目作为输入,并返回一个新的总计作为其结果。为了清楚起见,我在下面的函数中标注了类型,但可以将其删除,因为可以推断出它们:

let addEntry (volumeRequested:int64) (totals:Totals) (entry:Entry) : Totals =
    if totals.Volume >= volumeRequested then
        totals
    elif totals.Volume + entry.VolumeUSD <= volumeRequested then
        { Volume = totals.Volume + entry.VolumeUSD
          ContractsCount = totals.ContractsCount + float entry.VolumeUSD / entry.PriceUSD
          VolumeBeforePrice = totals.VolumeBeforePrice + entry.VolumeUSD }
    else
        let diff = volumeRequested - totals.Volume
        { Volume = volumeRequested
          ContractsCount = totals.ContractsCount + float diff / entry.PriceUSD
          VolumeBeforePrice = totals.VolumeBeforePrice + entry.VolumeUSD }

现在,您可以遍历最后一次总计的列表。幸运的是,有一个内置函数List.fold可以做到这一点。您可以在F# for fun and profit上阅读有关折叠的更多信息。

let volumeRequested = Candle.ConvertVolumes(minVolume)

let zero =
    { VolumeBeforePrice = 0L
      Volume = 0L
      ContractsCount = 0. }

let result = entries |> List.fold (addEntry volumeRequested) zero

请注意,这将为您提供正确的结果,但始终会迭代所有条目。这是否可以接受可能取决于entries列表的大小。如果要避免这种情况,则需要使用递归。像这样:

let rec calculateTotals (volumeRequested:int64) (totals:Totals) (entries:Entry list) : Totals =
    if totals.Volume >= volumeRequested then
        totals
    else
        match entries with
        | [] -> totals
        | entry::remaining ->
            let newTotals =
                if totals.Volume + entry.VolumeUSD <= volumeRequested then
                    { Volume = totals.Volume + entry.VolumeUSD
                      ContractsCount = totals.ContractsCount + float entry.VolumeUSD / entry.PriceUSD
                      VolumeBeforePrice = totals.VolumeBeforePrice + entry.VolumeUSD }
                else
                    let diff = volumeRequested - totals.Volume
                    { Volume = volumeRequested
                      ContractsCount = totals.ContractsCount + float diff / entry.PriceUSD
                      VolumeBeforePrice = totals.VolumeBeforePrice + entry.VolumeUSD }
            calculateTotals volumeRequested newTotals remaining

let result = calculateTotals volumeRequested zero entries