For convenience, I've substitued Ints for Dates, but the prinicpal is the same.
val data = List(
("a","a", 10),
("a","b", 30),
("a","b", 11),
("a","b", 33),
("s","c", 37),
("a","c", 26),
("a","d", 22),
("m","a", 18),
("t","a", 15)
)
val sortedData = data.sortWith ((a,b)=> a._3 < b._3)
println(s"$sortedData")
val splitPass = sortedData.foldLeft(List[(String, String,Int)](), List[List[(String, String,Int)]](), sortedData.head._3){
case ((cl, acc, ld),nt) =>
if (nt._3-ld>3)
(List(nt), cl.reverse ::acc, nt._3)
else
(nt:: cl, acc, nt._3)
}
val (fl, fa, _) = splitPass
val res = (if (fl.isEmpty) fa else fl :: fa).reverse
println(s"$res")
This gives the sorted list:
List((a,a,10), (a,b,11), (t,a,15), (m,a,18), (a,d,22), (a,c,26), (a,b,30), (a,b,33), (s,c,37))
and the result list:
List(List((a,a,10), (a,b,11)), List((t,a,15), (m,a,18)), List((a,d,22)), List((a,c,26)), List((a,b,30), (a,b,33)), List((s,c,37)))
What this does is a single pass through the sorted list, building an accumulator consisting of (List of items in the current group, List of completed groups, Int[Date] of last added item). This is seeded with two empty lists and the Date of the first item in the list.
For each item in the source list, if it's close to the previous one, it gets added to the Current group, if it's far from the previous item, the current group is closed and added to the completed gorups list, and the current item becoes the first item in a new list, and the current item's date becomes the reference date for the next check. If you wanted to break where the date was different to far from the earliest in the current group, it is easy to change the else branch to pass ld instead of nt._3.
At the end, you need to add any uncompleted group to the final collection of groups.
The two '.reverse's are necessary because the lists are, in the typcial functional style, built backwards (becuase it's cheaper that way) and reversed at completion.
sortBy(_._3)instead ofsortWith