Reputation: 11751
I've a function that groups a List
of Person
s if their first names are same AND and they are adjacent to each other in the list.
case class Person(first:String,last:String,age:Int)
@annotation.tailrec
def groupPersons(persons: List[Person], results: List[List[Person]] = Nil): List[List[Person]] = persons match {
case Nil => results.reverse
case persons => groupPersons(persons.dropWhile(_.first == persons.head.first), persons.takeWhile(_.first == persons.head.first) :: results)
}
I would like to create a generic version of this function that works other types as well and takes in a function that is passed to dropWhile
and takeWhile
.
Something like:
def group(p: List[P], results: List[List[P]] = Nil, f: (P,P) => Boolean): List[List[P]] = {
p match {
case Nil => results.reverse
case p => group(p.dropWhile(f), p.takeWhile(f) :: results, f)
}
}
Cannot make the above to work? Any ideas how to fix this?
Upvotes: 0
Views: 92
Reputation: 41769
You are most of the way there. Adding a type parameter to group
fixes one problem. Your other problem is that dropWhile
and takeWhile
takas a predicate (P=> Boolean
), but you want to use an f
that takes two parameters. We can fix this by currying f
and partially applying. Finally, span
will be slightly more efficient than your takeWhile
/dropWhile
def group[P](p: List[P],
results: List[List[P]] = Nil,
f: (P,P) => Boolean): List[List[P]] = {
p match {
case Nil => results.reverse
case p => {
val g = f.curried(p.head)
val (matching, rest) = p.span(g)
group(rest, matching :: results, f)
}
}
}
val xs = List(Person("John", "Doe", 42), Person("Jane", "Smith", 50),
Person("Jane", "Piper", 52), Person("Mary", "Morse", 52))
def matchFirstName(a:Person,b:Person) = a.first == b.first
group(xs, Nil, matchFirstName)
//> res0: List[List[exprs.exprs.Person]] = List(List(Person(John,Doe,42)), List(
//| Person(Jane,Smith,50), Person(Jane,Piper,52)), List(Person(Mary,Morse,52)))
Upvotes: 2
Reputation: 21557
I'm not sure if this want you need, but there is a better way:
case class Person(first:String,last:String,age:Int)
val xs = List(Person("John", "Doe", 42), Person("Jane", "Smith", 50), Person("Jane", "Piper", 52), Person("Mary", "Morse", 52))
There is a function groupBy
:
scala> xs.groupBy(_.first)
res0: Map[String,List[Person]] =
Map(Jane -> List(Person(Jane,Smith,50), Person(Jane,Piper,52)),
Mary -> List(Person(Mary,Morse,52)),
John -> List(Person(John,Doe,42)))
you can convert to into a List: xs.groupBy(_.first).toList
and then continue process your data.
Thanks to @Paul, i haven't noted the requirement to be "adjacent to each other". It looks to me like a simple sort application, i.e sortWith
function which takes a function lt: (A, A) => Boolean
just like your f
parameter.
Upvotes: 1