2010-11-10 15 views
10

estoy tratando de hacer lo siguiente en tan poco código como sea posible y tan funcionalmente como sea posible:Scala gimnasia de programación funcional

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double 

Obviamente las siguientes obras:

= (floor -> cap) match { 
    case (None, None)  => amt 
    case (Some(f), None) => f max amt 
    case (None, Some(c))  => c min amt 
    case (Some(f), Some(c)) => (f max amt) min c 
    } 

Me esperaba que para algo más elegante y aceptará el uso de la biblioteca Scalaz! Se puede suponer que lo siguiente es cierto:

floor.forall(f => cap.forall(_ > f)) 

Si alguien está interesado, aquí hay un código de prueba de:

object Comparisons { 
    sealed trait Cf { 
    def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double 
    } 

    def main(args: Array[String]) { 
    val cf : Cf = //TODO - your impl here! 
    def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = { 
     val ans = cf.restrict(floor, cap, amt) 
     println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED")) 
    } 
    runtest(Some(3), Some(5), 2, 3) 
    runtest(Some(3), Some(5), 3, 3) 
    runtest(Some(3), Some(5), 4, 4) 
    runtest(Some(3), Some(5), 5, 5) 
    runtest(Some(3), Some(5), 6, 5) 

    runtest(Some(3), None, 2, 3) 
    runtest(Some(3), None, 3, 3) 
    runtest(Some(3), None, 4, 4) 
    runtest(Some(3), None, 5, 5) 
    runtest(Some(3), None, 6, 6) 

    runtest(None, Some(5), 2, 2) 
    runtest(None, Some(5), 3, 3) 
    runtest(None, Some(5), 4, 4) 
    runtest(None, Some(5), 5, 5) 
    runtest(None, Some(5), 6, 5) 

    runtest(None, None, 2, 2) 
    runtest(None, None, 3, 3) 
    runtest(None, None, 4, 4) 
    runtest(None, None, 5, 5) 
    runtest(None, None, 6, 6) 
    } 
} 
+1

¿Qué hace la función de "restringir" se supone que haga? – OscarRyz

+0

Lo sentimos, tiene que devolver un doble basado en el 'amt' provisto pero con los parámetros opcionales tapados o anotados. Es decir, si se proporciona 10 pero un límite de Algunos (8), el método debería devolver 8 –

Respuesta

16

Editar 2:

Mientras pensaba en el método cataX, me di cuenta de que cataX es otra cosa que un pliegue simple y llanamente. Usando eso, podemos obtener una solución de scala pura sin bibliotecas adicionales.

Por lo tanto, aquí está:

((amt /: floor)(_ max _) /: cap)(_ min _) 

que es el mismo que

cap.foldLeft(floor.foldLeft(amt)(_ max _))(_ min _) 

(no es que esto es necesariamente más fácil de entender).

Creo que no puede ser más corto que eso.


Para mejor o peor, sino que también puede resolverlo utilizando scalaz:

floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m)) 

o incluso:

floor.cata(amt max, amt) |> (m => cap.cata(m min, m)) 

Como Scala programador 'normal', uno puede no saber acerca los operadores especiales de Scalaz y los métodos utilizados (|> y Option.cata). Funcionan de la siguiente manera:

value |> function se traduce en function(value) y por lo tanto amt |> (m => v fun m) es igual a v fun amt.

opt.cata(fun, v) se traduce en

opt match { 
    case Some(value) => fun(value) 
    case None => v 
} 

o opt.map(fun).getOrElse(v).

Consulte las definiciones de Scalaz para cata y |>.

Una solución más simétrica sería:

amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m)) 

Editar: este momento, se está haciendo raro ahora, pero yo quería tener una versión libre de punto. El nuevo cataX está al curry. El primer parámetro toma una función binaria; el segundo es un valor.

class CataOption[T](o: Option[T]) { 
    def cataX(fun: ((T, T) => T))(v: T) = o.cata(m => fun(m, v), v) 
} 
implicit def option2CataOption[T](o: Option[T]) = new CataOption[T](o) 

Si o partidos Some volvemos a la función con el valor de o y el segundo parámetro se aplica, si o partidos None volvemos solamente el segundo parámetro.

Y aquí vamos:

amt |> floor.cataX(_ max _) |> cap.cataX(_ min _) 

Tal vez que ya tienen esto en Scalaz ...?

+2

Te mereces todos los puntos: la respuesta del medio está llena de asombro. Podría ser una idea para elucidar qué está pasando –

+1

La muy esperada biblioteca de elucidación de scalaz todavía es un trabajo en progreso. Sin embargo, a modo de adelanto ... creo que en su mayor parte se basa en una prueba matemática poco conocida que demuestra un homomorfismo entre el código fuente y las descripciones claras :) –

+0

El caso es que una vez que comprenda el tipo de firma de '|> ', es obvio, ¿verdad? –

4

¿Qué tal esto?

//WRONG 
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    (floor.getOrElse(amt) max amt) min cap.getOrElse(amt) 

[Editar]

Segundo intento:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    floor.map(f => f max _).getOrElse(identity[Double] _)(
    cap.map(c => c min _).getOrElse(identity[Double] _)(amt)) 

parece un poco demasiado "lispy" para mi gusto, pero pasa las pruebas :-)

[segundo Editar]

La primera versión se puede "reparar", también:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double = 
    (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue) 
+0

Esta es esencialmente la misma respuesta dada por Dave Griffith: no funciona –

+0

La versión de "2ª Edición" funciona muy bien –

4

No es más bonito, no es mucho más corto, ¡y ciertamente no más rápido! pero es más componibles más genérico y más "funcional":

EDITAR: hizo el código totalmente genérico :)

def optWith[T](a: Option[T], b: T)(op:(T,T)=>T) = 
    a map (op(b,_)) getOrElse b 

def optMin[T:Numeric](a: Option[T]) = 
    (b:T) => optWith(a, b)(implicitly[Numeric[T]].min) 

def optMax[T:Numeric](a: Option[T]) = 
    (b:T) => optWith(a, b)(implicitly[Numeric[T]].max) 

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT]) 
    (implicit ev:Numeric[T], fv:FT=>T, cv:CT=>T) = 
    optMin(ceil map cv) compose optMax(floor map fv) apply(x) 

ACTUALIZACIÓN 2: También esta versión, aprovechando mejor Numeric

def optWith[T](a: Option[T])(op:(T,T)=>T) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT]) 
    (implicit n:Numeric[T], fv:FT=>T, cv:CT=>T) = 
    optWith(ceil map cv)(n.min) compose optWith(floor map fv)(n.max) apply(x) 

espero que les guste las firmas de tipos :)

ACTUALIZACIÓN 3: Aquí hay uno que hace lo mismo con los límites

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[T:Numeric, FT <% T, CT <% T] 
(floor:Option[FT], ceil:Option[CT], amt:T) = { 
    val n = implicitly[Numeric[T]]; import n._ 
    optWith(min)(ceil) compose 
    optWith(max)(floor) apply(amt) 
} 

Si nada ... esto demuestra con toda claridad por qué parámetros de importación sería una buena cosa (tm). Imagínese si el siguiente código válido era:

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[import T:Numeric,FT <% T,CT <% T] 
(floor:Option[FT], ceil:Option[CT], amt:T) = { 
    optWith(min)(ceil) compose 
    optWith(max)(floor) apply(amt) 
} 

ACTUALIZACIÓN 4: Pasando la solución al revés aquí. Este ofrece algunas posibilidades más interesantes para futuras extensiones.

implicit def optRhs[T:Ordering](lhs:T) = new Object { 
    val ord = implicitly[Ordering[T]]; import ord._ 

    private def opt(op: (T,T)=>T)(rhs:Option[T]) = 
    rhs map (op(lhs,_)) getOrElse lhs 

    def max = opt(ord.max) _ 
    def min = opt(ord.min) _ 
} 

def restrict[T : Ordering](floor:Option[T], cap:Option[T], amt:T) = 
    amt min cap max floor 

Con un poco de suerte, inspiraré a otra persona a construir una mejor solución de la mía. Así es como estas cosas suelen trabajar fuera ...

+0

Bueno, se funciona pero quería la menor cantidad de líneas de código –

+0

Gran solución en la actualización 4. Bastante ridículo cuántas formas diferentes de resolver este problema puede haber en un idioma. ¡Esta pregunta simplemente no va a ser preguntada sobre Java! –

+0

Verdadero ... En Java simplemente haría comprobaciones nulas explícitas, posiblemente arriesgaría NPE, y tendría que sobrecargar N veces por cada primitiva con la que pudiera terminar trabajando.Incluso entonces, todavía no sería un código válido para ningún otro tipo ordenado que te pueda interesar ... –

5

Voy a comenzar con esto:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    val flooring = floor.map(f => (_: Double) max f).getOrElse(identity[Double] _)  
    val capping = cap.map(f => (_: Double) min f).getOrElse(identity[Double] _)   
    (flooring andThen capping)(amt)              
}                      

Pero tengo la sensación de que me falta alguna oportunidad aquí, así que no puede ser terminado.

15

No es tan terso como la versión scalaz, pero por otro lado, no hay dependencias,

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1) 
+0

Me encanta esta. Todas las otras soluciones hasta ahora te obligan a entender qué hacen dos pruebas, mientras que esta solución toma el centro entre el piso y la tapa. – huynhjl

2

Esto no es realmente mucho más fácil en Scalaz que en Scala normal:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = 
    floor.map(amt max).orElse(Some(amt)).map(x => cap.map(x min).getOrElse(x)).get 

(Agregue _ después de max y min si le hace sentir mejor para ver dónde va el parámetro.)

Scalaz es un poco más fácil de re sin embargo, una vez que comprenda lo que hacen los operadores.

+0

Resulta que la mejor solución (al menos en mi opinión) es la que solo usa '/:' de Debilski –

5

En lugar de ir por pura brevedad, esto muestra cuánto más fácil se vuelve la composición si convierte cap y floor en funciones.

scala> val min = (scala.math.min _).curried           
min: (Int) => (Int) => Int = <function1> 

scala> val max = (scala.math.max _).curried           
max: (Int) => (Int) => Int = <function1> 

scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ∘ f | identity 
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A 

scala> val cap = 5.some; val floor = 1.some           
cap: Option[Int] = Some(5) 
floor: Option[Int] = Some(1) 

scala> val ffloor = orIdentity(floor)(max)           
ffloor: (Int) => Int = <function1> 

scala> val fcap = orIdentity(cap)(min)            
fcap: (Int) => Int = <function1> 

scala> val capAndFloor = fcap ∘ ffloor            
capAndFloor: (Int) => Int = <function1>  

scala> (0 to 8).toSeq ∘ (capAndFloor)  
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5) 

De scalaz, utilizo MA#∘, el mapa funtor, tanto como una forma de usar Option.map y Function1.andThen; y OptionW#| que es un alias para Option.getOrElse.

ACTUALIZACIÓN

Esto es lo que estaba buscando:

scala> import scalaz._; import Scalaz._ 
import scalaz._ 
import Scalaz._ 

scala> val min = (scala.math.min _).curried           
min: (Int) => (Int) => Int = <function1> 

scala> val max = (scala.math.max _).curried           
max: (Int) => (Int) => Int = <function1> 

scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] = 
    | fa.foldMap[Endo[A]](a => f(a))          
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A] 

scala> val cap = 5.some; val floor = 1.some          
cap: Option[Int] = Some(5) 
floor: Option[Int] = Some(1)  

scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ∑ 
capAndFloor: scalaz.Endo[Int] = [email protected] 

scala>(0 to 10).toSeq.map(capAndFloor)            
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5) 

scalaz.Endo[A] es una envoltura alrededor de A => A, hay conversiones implícitas en ambas direcciones. Hay una instancia de Monoid definida para Endo[A], Monoid#plus encadena las funciones y Monoid#zero devuelve la función de identidad. Si tenemos un List de Endo[A], podemos sumar la lista y dar como resultado un único valor, que se puede usar como A => A.

MA#foldMap asigna la función dada sobre un tipo de datos Foldable, y se reduce a un solo valor con un Monoid. foldMapEndo es una comodidad además de esto. Esta abstracción le permite cambiar fácilmente de probar el límite y el piso en Option a cualquier tipo plegable, como List.

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee 
capAndFloor: scalaz.Endo[Int] = [email protected] 

Otra refactorización podría conducir a:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) } 
capAndFloor: scalaz.Endo[Int] = [email protected] 
+1

¡Impresionante como siempre! Este enfoque es obviamente útil si quería escribir cap/piso con otras funciones, o en otros lugares en la base de código: en realidad está en un lugar pequeño –

+0

El enfoque es bastante similar a las respuestas de Kevin y Daniel. Es interesante ver cómo las cosas se vuelven más sencillas para sondear junto con las funciones al curry. Todavía me pregunto si hay una abstracción detrás de 'orIdentity'. – retronym

+0

Supongo que podría haber usado '| + |' en lugar del enfoque 'List.sum'? –

1

Ésta es otra manera de solucionar Landei's first answer

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    val chopBottom = (floor.getOrElse(amt) max amt) 
    chopBottom min cap.getOrElse(chopBottom) 
} 
2

Me parece que cuando una pregunta pide usar un Option para indicar un parámetro opcional , generalmente hay una manera más natural de representar el parámetro faltante. Así que voy a cambiar un poco la interfaz aquí, y usar argumentos por defecto para definir la función y los parámetros nombrados para llamar a la función.

def restrict(amt:Double, 
      floor:Double = Double.NegativeInfinity, 
      cap:Double=Double.PositiveInfinity):Double = 
    (amt min cap) max floor 

A continuación, puede llamar a:

restrict(6) 
restrict(6, floor = 7) 
restrict(6, cap = 5) 

(Another example of the same principle.)

+0

No estoy seguro de estar de acuerdo con usted aquí; ¿Cómo es usar un "truco" más natural que expresar la "verdad" de la situación? Dicho esto, la sintaxis del sitio de llamada es agradable. Voy a agregar una respuesta basada en su solución, que creo que es más apropiada –

+0

@oxbow_lakes, ¿Cómo es el uso de infinito para decir "sin límite" un truco? ¿Cómo es que no es la verdad? (Pensé que si iba a oponerse, sería una objeción al cambio de interfaz, no el uso del infinito como límite). –

+0

Veo su punto; pero hay una diferencia (al menos en mi mente) entre "sin límite" y "un límite de infinito". Reconozcámoslo, "Double.PositiveInfinity" es un truco. –

2

Ésta es based on Ken Bloom's answer:

sealed trait Constrainer { def constrain(d : Double) : Double } 

trait Cap extends Constrainer 
trait Floor extends Constrainer 
case object NoCap extends Cap { def constrain(d : Double) = d } 
case object NoFloor extends Floor { def constrain(d : Double) = d } 
implicit def d2cap(d : Double) = new Cap { def constrain(amt : Double) = d min amt } 
implicit def d2floor(d : Double) = new Floor { def constrain(amt : Double) = d max amt } 

def restrict(amt : Double, cap : Cap = NoCap, floor: Floor = NoFloor) : Double = { 
    cap.constrain(floor.constrain(amt)) 
    //or (cap.constrain andThen floor.constrain) amt 
} 

Se termina con la escritura de código como esto:

restrict(amt, cap = 5D) 
restrict(amt, floor = 0D) 

Creo que es bastante impresionante y no sufre el problema con la solución de Ken (en mi opinión), que es que es un truco !

+0

Sí. Creo que las jerarquías selladas y los objetos únicos se pasan por alto. Por lo general, no tienen mucho sentido para las bibliotecas, pero a menudo se muestran muy bien en las reglas comerciales. –

0

estoy añadiendo otra respuesta que fue inspirado por tanto retronym y Debilski - básicamente equivale a la conversión de la tapa y el suelo a las funciones (Double => Double, si están presentes) y luego el plegado de la función identidad a través de ellos con la composición:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = { 
    (identity[Double] _ /: List(floor.map(f => (_: Double) max f), cap.map(c => (_: Double) min c)).flatten){ _ andThen _ }(amt) 
} 
0

solución directa con la llanura Scala y lambda en el anonimato, sin ningún tipo de asignaciones, pliegues, doble {Min/Max} Valor, y así sucesivamente:.

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    ((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt)) 
0

Me gusta la solución inicial con la caja de fósforos más, además de que no comprendí que amt significa amount (en Alemania, 'amt' significa 'oficina') y solo sabía cap como algo que uso en mi cabeza ...

Ahora aquí es una solución muy poco inspirado, utilizando un método interno:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    def restrict (floor: Double, cap: Double, amt: Double) = 
    (floor max amt) min cap 
    var f = floor.getOrElse (amt)    
    val c = cap.getOrElse (amt) 
    restrict (f, c, amt) 
} 
Cuestiones relacionadas