是否可以在 Scala 中默认替换 ZipAll 时传递动态值?
Is it possible to pass dynamic values in default replacement of ZipAll in Scala?
我对两个大小不等的列表使用 zipAll
,如下所示:
val prices = List(5, 10, 15, 20)
val fruits = List("Guava", "Banana", "Papaya", "Apple", "Mango")
我正在使用 zipAll
如下图:
fruits zipAll (prices, "Undefined Fruit", "Price Unavailable")
产生以下结果:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable))
我感兴趣的是传递动态默认填充符;所以在这种情况下,我期待的是:
fruits zipAll (prices, "Undefined Fruit", s"Price Unavailable for $fruitName")
哪个应该产生:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable for Mango))
由于stdlib没有提供这样的功能,所以需要自己实现:
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)
}
虽然,这个很贵。
您可以使用尾递归函数提高操作效率;或者你可以使用 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
那么你可以这样使用它:
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
and String
as yu wanted would have resulted in a List[(String, Any)]
which you never want, rather I converted all the prices to Strings
before the zip.
(如何将此辅助函数转换为扩展方法留作reader的练习)
可以看到代码运行 here.
PS:如果你使用 cats,你可以使用 align
和 map
来更好地控制一切。
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")
感谢 Jasper 指出我们可以将前面的 align
+ map
与
结合起来
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"
}
我对两个大小不等的列表使用 zipAll
,如下所示:
val prices = List(5, 10, 15, 20)
val fruits = List("Guava", "Banana", "Papaya", "Apple", "Mango")
我正在使用 zipAll
如下图:
fruits zipAll (prices, "Undefined Fruit", "Price Unavailable")
产生以下结果:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable))
我感兴趣的是传递动态默认填充符;所以在这种情况下,我期待的是:
fruits zipAll (prices, "Undefined Fruit", s"Price Unavailable for $fruitName")
哪个应该产生:
List((Guava,5), (Banana,10), (Papaya,15), (Apple,20), (Mango,Price Unavailable for Mango))
由于stdlib没有提供这样的功能,所以需要自己实现:
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)
}
虽然,这个很贵。
您可以使用尾递归函数提高操作效率;或者你可以使用 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
那么你可以这样使用它:
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.
(如何将此辅助函数转换为扩展方法留作reader的练习)
可以看到代码运行 here.
PS:如果你使用 cats,你可以使用 align
和 map
来更好地控制一切。
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")
感谢 Jasper 指出我们可以将前面的 align
+ map
与
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"
}