Reputation: 12107
let's consider the following SORTED data:
[1; 2; 3; 5; 6; 20; 21; 22; 23]
and I would like to get:
[ [1; 2; 3]; [5; 6]; [20; 21; 22; 23] ]
what would be the best way to achieve this? (the list is at most 1000 entries).
Upvotes: 2
Views: 163
Reputation: 462
Here is an implementation which folds the input list into a new list, grouping as required.
let group l =
let addNextInt (i : int) (l : List<List<int>>) =
match l with
| [] -> [[i]]
| (x::xs)::tail ->
if x - i = 1 then (i::x::xs)::tail
else [i]::(x::xs)::tail
| _ -> failwith "Unreachable"
List.foldBack addNextInt l []
// Input: [], Output: []
// Input: [1; 2], Output: [[1; 2]]
// Input: [1; 3], Output: [[1]; [3]]
// Input: [1; 3; 5; 6], Output: [[1]; [3]; [5; 6]]
// Input: [1; 2; 3; 5; 6; 20; 21; 22; 23], Output: [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]
Upvotes: 1
Reputation: 2459
Here's a version which is perhaps a bit more idiomatic and, to me at least, a bit more readable. It relies only on the standard function foldBack
in the List
module and avoids mutability entirely.
let group ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if n = l+1
then (l::n::res)::ns
else [l]::(n::res)::ns)
It may also be refactored into a more general function
let chunkBy cond ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if cond l n
then (l::n::res)::ns
else [l]::(n::res)::ns)
let group = chunkBy (fun prev next -> prev + 1 = next)
Notice that this leverages the cons operator ::
which is only implemented on lists. The cons operator prepends an item to a list, which is why the function uses foldBack
.
Upvotes: 4
Reputation: 11060
let group ls =
[
let mutable last = 0
let mutable current = []
for x in ls do
if x > (last + 1) then
if not current.IsEmpty then
yield current |> List.rev
current <- [x]
else
current <- x :: current
last <- x
if not current.IsEmpty then
yield current |> List.rev
]
group [1; 2; 3; 5; 6; 20; 21; 22; 23]
val it : int list list = [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]
Upvotes: 1
Reputation: 5741
So you're implying you want to group by the condition that the difference between two consecutive values should be 1. Or viewed the other way, to split when the difference is other than 1.
Examples for grouping lists by predicate can be found here and here. Considering an input sequence as opposed to a list, we might get some mileage out of the fact that we are able to consume it lazily.
let splitWhen condition (source : seq<'T>) : seq<'T[]> = seq {
let stateref, acc = ref None, ResizeArray()
use ie = source.GetEnumerator()
while ie.MoveNext() do
let current = ie.Current
match !stateref with
| Some previous when condition previous current ->
yield acc.ToArray()
acc.Clear()
| _ -> ()
acc.Add current
stateref := Some current
if acc.Count > 0 then
yield acc.ToArray() }
[1; 2; 3; 5; 6; 20; 21; 22; 23]
|> splitWhen (fun x0 x1 -> x1 - x0 <> 1)
// val it : seq<int []> = seq [[|1; 2; 3|]; [|5; 6|]; [|20; 21; 22; 23|]]
Upvotes: 0