Having trouble converting some C# code to F# when having to do counts

Tho*_*mas 1 f#

I have the following C# code:

(no need to understand the details of it, it's just to illustrate the question)

            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;
            }
Run Code Online (Sandbox Code Playgroud)

it goes through entries of a trading order book and calculates the number of contracts available for a specific usd amount.

the logic is quite simple: for each entry there is a block of contracts at a given price, so it either adds the whole block, or it will add a partial block if it doesn't fit within the request.

I am trying to move this to F# and I am encountering some problems since I'm new to the language:

this is a partial implementation:

    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#
    )
Run Code Online (Sandbox Code Playgroud)

And I stopped there because it doesn't look like a very F# way to do this :)

So, my question is: how can I structure the List.iter so that:

- 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? 
Run Code Online (Sandbox Code Playgroud)

Cha*_*ger 6

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

type Totals =
    { VolumeBeforePrice : int64
      Volume : int64
      ContractsCount : float }
Run Code Online (Sandbox Code Playgroud)

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

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 }
Run Code Online (Sandbox Code Playgroud)

现在,您可以遍历最后一次合计的列表。幸运的是,有一个内置的函数List.fold可以做到这一点。您可以阅读有关F#折叠的更多信息,以获取乐趣和利润

let volumeRequested = Candle.ConvertVolumes(minVolume)

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

let result = entries |> List.fold (addEntry volumeRequested) zero
Run Code Online (Sandbox Code Playgroud)

请注意,这将为您提供正确的结果,但始终会迭代所有条目。这是否可以接受可能取决于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
Run Code Online (Sandbox Code Playgroud)