I was using zipAll
for two unequal sized Lists, as shown below:
val prices = List(5, 10, 15, 20)
val fruits = List("Guava", "Banana", "Papaya", "Apple", "Mango")
I am using zipAll
as shown below:
fruits zipAll (prices, "Undefined Fruit", "Price Unavailable")
Which yields following result:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable))
What I am interested in is passing dynamic default fillers; so in this case, I am expecting something like:
fruits zipAll (prices, "Undefined Fruit", s"Price Unavailable for $fruitName")
Which should yield:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable for Mango))
CodePudding user response:
Since the stdlib doesn't provide such functionality, you need to implement it yourself like this:
def zipAllDynamic[A, B](left: List[A], right: List[B])(leftDefault: B => A, rightDefault: A => B): List[(A, B)] =
left
.map(l => Option(l))
.zipAll(right.map(r => Option(r)), None, None)
.collect {
case (Some(l), Some(r)) => (l, r)
case (Some(l), None) => (l, rightDefault(l))
case (None, Some(r)) => (leftDefault(r), r)
}
Although, this is very expensive.
You may increase the efficiency of the operation using a tail-recursive function; or you can just use the magic of lazynes:
def zipAllDynamic[A, B](left: List[A], right: List[B])(leftDefault: B => A, rightDefault: A => B): List[(A, B)] =
left
.iterator.map(l => Option(l))
.zipAll(right.iterator.map(r => Option(r)), None, None)
.collect {
case (Some(l), Some(r)) => (l, r)
case (Some(l), None) => (l, rightDefault(l))
case (None, Some(r)) => (leftDefault(r), r)
}.toList
Then you can use it like this:
val prices = List(5, 10, 15, 20)
val fruits = List("Guava", "Banana", "Papaya", "Apple", "Mango")
val result = zipAllDynamic(fruits, prices.map(_.toString))(_ => "Undefined Fruit", fruitName => s"Price Unavailable for ${fruitName}")
// result: List[(String, String)] = List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable for Mango))
Note: Remember Scala is a statically and strongly typed language, as such mixing
Int
andString
as yu wanted would have resulted in aList[(String, Any)]
which you never want, rather I converted all the prices toStrings
before the zip.
(How to convert this auxiliary function to an extension method is left as an exercise to the reader)
You can see the code running here.
PS: If you use cats you can rather use align
and the map
to have better control over everything.
import cats.data.Ior
import cats.syntax.all._
val result = (prices align fruits).map {
case Ior.Both(price, fruit) => s"${fruit} price is: ${price}"
case Ior.Right(fruit) => s"Price Unavailable for ${fruit}"
case Ior.Left(_) => "Undefined Fruit"
}
// result: List[String] = List("Guava price is: 5", "Banana price is: 10", "Papaya price is: 15", "Apple price is: 20", "Price Unavailable for Mango")
Thanks to Jasper to point out we can combine the previous align
map
with just:
val result = prices.alignWith(fruits) {
case Ior.Both(price, fruit) => s"${fruit} price is: ${price}"
case Ior.Right(fruit) => s"Price Unavailable for ${fruit}"
case Ior.Left(_) => "Undefined Fruit"
}