Scala:当两个项目之一满足某些条件时进行模式匹配

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2039715/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:51:06  来源:igfitidea点击:

Scala: Pattern matching when one of two items meets some condition

scalapattern-matchingextractor

提问by Alex Black

I'm often writing code that compares two objects and produces a value based on whether they are the same, or different, based on how they are different.

我经常编写代码来比较两个对象,并根据它们是相同还是不同,基于它们的不同来产生一个值。

So I might write:

所以我可能会写:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case (Some(value), None)) => "b"
  case (None, Some(value)) => "b"
  case _ = > "c"
}

Those 2nd and 3rd cases are the same really, so I tried writing:

那些第二和第三个案例实际上是一样的,所以我试着写:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case (Some(value), None)) || (None, Some(value)) => "b"
  case _ = > "c"
}

But no luck.

但没有运气。

I encounter this problem in a few places, and this is just a specific example, the more general pattern is I have two things, and I want to know if one and only one of them meet some predicate, so I'd like to write something like this:

我在几个地方遇到过这个问题,这只是一个具体的例子,更一般的模式是我有两件事,我想知道其中一个并且只有一个满足某个谓词,所以我想写像这样:

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case OneAndOnlyOne(value, v: Option[Foo] => v.isDefined ) => "b"
  case _ = > "c"
}

So the idea here is that OneAndOnlyOne can be configured with a predicated (isDefined in this case) and you can use it in multiple places.

所以这里的想法是 OneAndOnlyOne 可以配置一个谓词(在这种情况下是 isDefined),你可以在多个地方使用它。

The above doesn't work at all, since its backwards, the predicate needs to be passed into the extractor not returned.

以上根本不起作用,因为它向后,谓词需要传递到不返回的提取器中。

How about something like this?

这样的事情怎么样?

val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case new OneAndOnlyOne(v: Option[Foo] => v.isDefined )(value) => "b"
  case _ = > "c"
}

with:

和:

class OneAndOnlyOne[T](predicate: T => Boolean) {
  def unapply( pair: Pair[T,T] ): Option[T] = {
    val (item1,item2) = pair
    val v1 = predicate(item1)
    val v2 = predicate(item2)

    if ( v1 != v2 )
      Some( if ( v1 ) item1 else item2 )
    else
      None
  }
}

But, this doesn't compile.

但是,这不会编译。

Can anyone see a way to make this solution work? Or propose another solution? I'm probably making this more complicated than it is :)

任何人都可以看到使此解决方案起作用的方法吗?或者提出另一种解决方案?我可能使这比它更复杂:)

采纳答案by Thomas Jung

If you have to support arbitrary predicates you can derive from this (which is based on Daniel's idea):

如果您必须支持任意谓词,您可以从中得出(这是基于丹尼尔的想法):

List(v1, v2) filter (_ %2 == 0) match {
    case List(value1, value2) => "a"
    case List(value) => "b"
    case _ => "c"
}

the definition of the function:

函数定义:

def filteredMatch[T,R](values : T*)(f : T => Boolean)(p: PartialFunction[List[T], R]) : R = 
    p(List((values filter f) :_* ))

Now you can use it like this:

现在你可以像这样使用它:

filteredMatch(v1,v2)(_ %2 == 0){
    case List(value1, value2) => "a"
    case List(value) => "b"
    case _ => "c"
}

I'm not so sure if it's a good idea (i.e. readable). But a neat exercise nonetheless.

我不太确定这是否是一个好主意(即可读)。但仍然是一个整洁的练习。

It would be nice if you could match on tuples: case (value1, value2) => ...instead of lists.

如果您可以匹配元组:case (value1, value2) => ...而不是列表,那就太好了。

回答by Rex Kerr

I think you're asking two slightly different questions.

我认为你在问两个略有不同的问题。

One question is how to use "or" in switch statements. || doesn't work; | does. And you can't use variables in that case (because in general they might match different types, which renders the type confusing). So:

一个问题是如何在 switch 语句中使用“或”。|| 不起作用;| 做。在这种情况下你不能使用变量(因为通常它们可能匹配不同的类型,这会使类型变得混乱)。所以:

def matcher[T](a: (T,T)) = {
  a match {
    case (Some(x),Some(y)) => "both"
    case (Some(_),None) | (None,Some(_)) => "either"
    case _ => "none"
  }
}

Another question is how to avoid having to do this over and over, especially if you want to be able to get at the value in the tuple. I've implemented a version here for Option, but you could use an unwrapped tuple and a boolean.

另一个问题是如何避免一遍又一遍地执行此操作,特别是如果您希望能够获取元组中的值。我在这里为 Option 实现了一个版本,但你可以使用一个解包的元组和一个布尔值。

One trick to achieve this is that to prewrap the values before you start matching on it, and then use your own matching constructs that do what you want. For instance,

实现这一点的一个技巧是在开始匹配之前预先包装这些值,然后使用您自己的匹配结构来做您想做的事情。例如,

class DiOption[+T] {
  def trinary = this
}
case class Both[T](first: T, second:T) extends DiOption[T] { }
case class OneOf[T](it: T) extends DiOption[T] { }
case class Neither() extends DiOption[Nothing] { }
implicit def sometuple2dioption[T](t2: (Option[T],Option[T])): DiOption[T] = {
  t2 match {
    case (Some(x),Some(y)) => Both(x,y)
    case (Some(x),None) => OneOf(x)
    case (None,Some(y)) => OneOf(y)
    case _ => Neither()
  }
}

// Example usage
val a = (Some("This"),None)
a trinary match {
  case Both(s,t) => "Both"
  case OneOf(s) => "Just one"
  case _ => "Nothing"
}

回答by Eastsun

How about this:

这个怎么样:

    Welcome to Scala version 2.8.0.r20327-b20091230020149 (Java HotSpot(TM) Client VM, Java 1.6.0_17).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def m(v1: Any,v2: Any) = (v1,v2) match {
     |     case (Some(x),Some(y)) => "a"
     |     case (Some(_),None) | (None,Some(_)) => "b"
     |     case _ => "c"
     | }
m: (v1: Any,v2: Any)java.lang.String

scala> m(Some(1),Some(2))
res0: java.lang.String = a

scala> m(Some(1),None)
res1: java.lang.String = b

scala> m(None,None)
res2: java.lang.String = c

scala>

回答by Mitch Blevins

You should be able to do it if you define it as a val first:

如果您首先将其定义为 val,您应该能够做到:

val MyValThatIsCapitalized = new OneAndOnlyOne(v: Option[Foo] => v.isDefined )
val result = (v1,v2) match {
  case (Some(value1), Some(value2)) => "a"
  case MyValThatIsCapitalized(value) => "b"
  case _ = > "c"
}

As implied by the name, the name of the val containing the extractor object must be capitalized.

正如名称所暗示的那样,包含提取器对象的 val 的名称必须大写。

回答by Daniel C. Sobral

On Scala 2.8:

在 Scala 2.8 上:

val result = List(v1,v2).flatten match {
  case List(value1, value2) => "a"
  case List(value) => "b"
  case _ = > "c"
}

On Scala 2.7, however, you need a type hint to make it work. So, assuming valueis Int, for instance, then:

但是,在 Scala 2.7 上,您需要一个类型提示才能使其工作。所以,假设valueInt,例如,那么:

val result = (List(v1,v2).flatten : List[Int]) match {
  case List(value1, value2) => "a"
  case List(value) => "b"
  case _ = > "c"
}

The funny thing about it is that I misread "first" as "list" on Mitch Blevinsanswer, and that gave me this idea. :-)

有趣的是,我在Mitch Blevins 的回答中将“第一”误读为“列表” ,这给了我这个想法。:-)

回答by user unknown

Since you already matched against (Some(x), Some(y)), you may match against (None, None) explicitly, and the remaining cases are (Some(x), None) and (None, Some(y)):

由于您已经匹配了 (Some(x), Some(y)),您可以明确匹配 (None, None),剩下的情况是 (Some(x), None) 和 (None, Some(y)) :

def decide [T](v1: Option[T], v2:Option[T]) = (v1, v2) match {
  case (Some (x), Some (y)) => "a"
  case (None, None)         => "c"
  case _                    => "b"
}

val ni : Option [Int] = None 
decide (ni, ni)            // c
decide (Some (4), Some(3)) // a
decide (ni, Some (3))      // b
decide (Some (4), ni)      // b