2010-01-11 4 views
9

A menudo escribo código que compara dos objetos y produce un valor según si son iguales o diferentes en función de cómo son diferentes.Scala: coincidencia de patrón cuando uno de los dos elementos cumple alguna condición

Así que podría escribir:

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

Esos casos 2 y 3 son los mismos de verdad, así que traté de escribir:

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

Pero no hubo suerte.

Encuentro este problema en algunos lugares, y este es solo un ejemplo específico, el patrón más general es que tengo dos cosas, y quiero saber si una y solo una de ellas cumple algún predicado, entonces yo ' Quisiera escribir algo como esto:

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

Así que la idea aquí es que OneAndOnlyOne se puede configurar con un predicado (IsDefined en este caso) y se puede utilizar en múltiples lugares.

Lo anterior no funciona en absoluto, ya que es hacia atrás, el predicado debe pasar al extractor no devuelto.

¿Qué tal algo como esto?

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

con:

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 
    } 
} 

Pero, esto no compila.

¿Alguien puede ver la manera de hacer que esta solución funcione? ¿O proponer otra solución? Probablemente estoy haciendo esto más complicado de lo que es :)

+0

¿Tiene un caso de uso que muestra por qué quiere hacer esto? Quizás exista una solución mejor usando 'Oither' –

+0

Caso de uso: en general solo quiero evitar escribir los dos casos, p. (algunos, ninguno) y (ninguno, algunos). Mi caso de uso general es que estoy comparando dos productos en una característica en particular, y tal vez ambos productos tienen la característica, o solo uno de ellos, o si los dos tienen, tal vez uno tiene un buen valor para esa característica y uno es pobre valor. –

+0

posible duplicado de [Coincidencia de varias clases de casos en Scala] (http://stackoverflow.com/questions/1837754/match-multiple-cases-classes-in-scala) – nawfal

Respuesta

6

Si tiene que apoyar predicados arbitrarias se pueden derivar de esta (que se basa en Daniel's idea):

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

la definición de la función:

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

Ahora puede utilizar de esta manera:

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

no estoy tan seguro de que sea una buena idea (es decir, legible). Pero un ejercicio limpio, no obstante.

Sería bueno si pudiera coincidir en tuplas: case (value1, value2) => ... en lugar de listas.

+0

¡Parece que funcionaría bien, Thomas! –

+0

Hombre, me encanta Scala. No podría ser más legible, y es una * función *. Y un one-liner, en eso. –

6

¿Qué tal esto:

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> 
+0

Parece bastante simple, creo que lo intenté en Scala 2.7 y no compiló, lo intentaré de nuevo. –

+0

Acabo de comprobar: funciona perfectamente con Scala 2.7.5 – paradigmatic

4

Usted debe ser capaz de hacerlo si se define como un val primera:

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" 
} 

Tal como lo implica el nombre, el nombre del val que contiene el objeto extractor debe estar en mayúscula.

+0

Las expresiones regulares como casos se utilizan de la misma manera. – retronym

+0

Gracias Mitch, de hecho lo intenté, y creo que falló porque no tenía mi val en mayúsculas ... interesante. –

17

Creo que estás haciendo dos preguntas ligeramente diferentes.

Una pregunta es cómo usar "o" en las instrucciones de cambio. || no funciona; | hace. Y no puede usar variables en ese caso (porque en general pueden coincidir con diferentes tipos, lo que hace que el tipo sea confuso). Por lo tanto:

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

Otra cuestión es cómo evitar tener que hacer esto una y otra vez, especialmente si usted quiere ser capaz de obtener el valor de la tupla. Implementé una versión aquí para Opción, pero podría usar una tupla desenvuelta y una booleana.

Un truco para lograr esto es preenvolver los valores antes de comenzar a combinarlos, y luego usar sus propias construcciones correspondientes que hacen lo que usted desea. Por ejemplo,

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" 
} 
+0

Hola Rex, que parece prometedor, pero no (todavía) se extiende a otras situaciones, p. donde OneOf no está pidiendo que se defina uno de ellos, sino que uno de ellos diga "ser un número par" o "tener un valor> 10". –

+0

No: caso (Algunos (x), Algunos (y)) if ((x> 10) && (y isEven)) => "both" funcionan? –

+0

No estoy seguro de estar siguiendo a Jim, pero: sí, eso funciona, pero no creo que eso sea lo que estoy diciendo. Me gustaría conceptualmente hacer esto: Case (oneisEven) => case (bothAreEven) =>. P.ej. si son pares o no es solo una prueba diferente de si ambos son o no ninguno. –

3

En Scala 2.8:

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

En Scala 2.7, sin embargo, necesita un toque tipo para hacer que funcione. Por lo tanto, asumiendo value es Int, por ejemplo, entonces:

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

Lo curioso de esto es que leí mal "primero" como "lista" en la Mitch Blevins respuesta, y eso me dio esta idea. :-)

+0

Gracias por la respuesta Daniel, que se ve bien pero solo funciona para el predicado "x => x.isDefined", no pude usarlo para decir "x => x% 2 == 0" para verificar si solo uno de los dos artículos era par. –

+1

@Alex - Puede expresarlo así: 'List (v1, v2) filter (_% 2 == 0) match {case List (value1, value2) =>" a "case List (value) =>" b "case _ =>" c "}' –

+0

filtra la lista primero. Creo que la solución de Daniel es la más avanzada. – IttayD

0

Dado que ya coincidió con (Some (x), Some (y)), puede hacer coincidir (None, None) explícitamente, y los casos restantes son (Some (x), None) y (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 
Cuestiones relacionadas