I only started programming in F# yesterday, so I've not yet discovered what idiomatic F# actually looks like.
The first task I set myself required partitioning a list into smaller lists but where the partitioning is based on the characteristics of the whole group, rather than just being able to find split points based on the current element.
For example, given the list [1;2;3;4;5]
, I might want to group into sub-lists when the sum of the sub-list has reached at least 5. This would result in the output [[1;2;3]; [4;5]]
.
Any remaining elements that don't meet the criteria would be in a final sub-list.
My first attempt is below:
module Test
let rec partitionOn groupComplete list =
let take list n = Seq.take n list
// Take items from the front of the list until they form a complete group
// or there are no more items
let firstGroupAndRemainder itemsList =
let firstGroup =
[0 .. List.length itemsList]
|> Seq.map (take itemsList)
|> Seq.tryFind groupComplete
match firstGroup with
| Some g ->
let groupList = g |> Seq.toList
let remainingList = Seq.skip (List.length groupList) itemsList |> Seq.toList
(groupList, remainingList)
| None -> (itemsList, [])
match list with
| [] -> []
| _ ->
let group, rest = firstGroupAndRemainder list
[group] @ partitionOn groupComplete rest
[<EntryPoint>]
let main argv =
// Groups with sum greater than 5 (plus remaining elements)
let result1 = partitionOn (fun l -> Seq.sum l > 5) [1;5;2;6;2]
printfn "result1: %A" result1 // [[1;5]; [2;6]; [2]]
// Groups with length 3 (plus remaining elements)
let result2 = partitionOn (fun l -> Seq.length l = 3) [1;5;2;6;2]
printfn "result2: %A" result2 // [[1;5;2]; [6;2]]
0
It feels like there should be a simpler way to achieve this but I haven't yet thought of it.
Update
I've discovered the defaultArg
function, so the firstGroupAndRemainder
function might be slightly tidier as:
let firstGroupAndRemainder itemsList =
let firstGroup =
[0 .. List.length itemsList]
|> Seq.map (take itemsList)
|> Seq.tryFind groupComplete
let groupList = (defaultArg firstGroup (List.toSeq itemsList)) |> Seq.toList
let remainingList = Seq.skip (List.length groupList) itemsList |> Seq.toList
(groupList, remainingList)
partitionOn (fun l -> Seq.length l = 3)
is the same asList.chunkBySize 3
\$\endgroup\$Optional.defaultValue
(since F# 4.1) is nicer to use thandefaultArg
because it the argument order is swapped, making it easy to pipe. \$\endgroup\$printf "%A"
everywhere. \$\endgroup\$