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)
我会避免使用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)