If you're forced to use a var or mutable collection (beyond your needs for optimization), you're probably not thinking about the problem correctly.
Suppose we had a map m:
Map(1 -> "abc", 2 -> "xyz")
Now, we can use recursion to solve this problem (and I've done it in a tail recursive form here):
type Keys = Vector[String]
type Data = Vector[String]
def keyData(m: Map[Int, String]): (Keys, Data) = {
def go(keys: Keys, data: Data, m: List[(Int, String)]): (Keys, Data) =
m match {
case (k, v) :: ks if v endsWith("abc") =>
go(v +: keys, data, ks)
case (k, v) :: ks if v endsWith("xyz") =>
go(keys, v +: data, ks)
case k :: ks =>
go(keys, data, ks)
case _ => (keys, data)
}
go(Vector.empty[String], Vector.empty[String], m.toList)
}
This will take a map and produce a pair of vectors holding the string data that matches the predicates you listed. Now, suppose we wanted to abstract and partition our map elements into vectors satisfying any two predicates p: Int => Boolean or q: Int => Boolean. Then, we'd have something that looks like this:
type Keys = Vector[String]
type Data = Vector[String]
def keyData(m: Map[Int, String], p: Int => Boolean, q: Int => Boolean): (Keys, Data) = {
def go(keys: Keys, data: Data, m: List[(Int, String)]): (Keys, Data) =
m match {
case (k, v) :: ks if p(v) =>
go(v +: keys, data, ks)
case (k, v) :: ks if q(v) =>
go(keys, v +: data, ks)
case k :: ks =>
go(keys, data, ks)
case _ => (keys, data)
}
go(Vector.empty[String], Vector.empty[String], m.toList)
}
Now, we can abstract this for any key and value types K and V:
def partitionMapBy[K, V](m: Map[K, V], p: V => Boolean, q: V => Boolean): (Vector[V], Vector[V]) = {
def go(keys: Vector[V], data: Vector[V], m: List[(K, V)]): (Vector[V], Vector[V]) =
m match {
case (k, v) :: ks if p(v) =>
go(v +: keys, data, ks)
case (k, v) :: ks if q(v) =>
go(keys, v +: data, ks)
case k :: ks =>
go(keys, data, ks)
case _ => (keys, data)
}
go(Vector.empty[V], Vector.empty[V], m.toList)
}
You'll notice that there's nothing fancy going on with the recursion here. This means we can use a fold to accomplish the same thing. Here's an implementation using foldLeft:
def partitionMapBy[K, V](m: Map[K, V])(p: V => Boolean)(q: V => Boolean): (Vector[V], Vector[V]) =
m.foldLeft[(Vector[V], Vector[V])]((Vector.empty[V], Vector.empty[V])) {
case (acc @ (keys: Vector[V], data: Vector[V]), (_, v: V)) =>
if(p(v)) (v +: keys, data)
else if(q(v)) (keys, v +: data)
else acc
}
And you can see, for m, we get the this where, if you let p be _ endsWith("abc") and q be _ endsWith("xyz"), then you'll have exactly what you want.
`