From 6a84cda22b57e0a40ff3f0690d5fd1f646296f67 Mon Sep 17 00:00:00 2001 From: "joel.vonderwe" <joel.von-der-weid@etu.hesge.ch> Date: Thu, 26 Mar 2020 16:00:23 +0100 Subject: [PATCH] Monoid exos --- base_tp/src/monoid/monoids.scala | 132 +++++++++++++++++++++++++++++++ base_tp/test/8.monoidTest.scala | 63 +++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 base_tp/src/monoid/monoids.scala create mode 100644 base_tp/test/8.monoidTest.scala diff --git a/base_tp/src/monoid/monoids.scala b/base_tp/src/monoid/monoids.scala new file mode 100644 index 0000000..7cb87b2 --- /dev/null +++ b/base_tp/src/monoid/monoids.scala @@ -0,0 +1,132 @@ +package ch.hepia.tpscala.monoid + +trait Monoid[A] { + def op( a1: A, a2: A ): A + def zero: A +} + +object Monoid { + def apply[A]( z: A )( f: (A,A)=>A ) = new Monoid[A] { + def op( a1: A, a2: A ): A = f(a1,a2) + def zero: A = z + } + + object Laws { + def checkAssociativity[A]( + mon: Monoid[A], a1: A, a2: A, a3: A + ) = { + import mon.op + op(a1,op(a2,a3)) == op(op(a1,a2),a3) + } + def checkNeutral[A]( + mon: Monoid[A], a: A + ) = { + import mon._ + op(zero,a) == a && op(a,zero) == a + } + } +} + +object Monoids { + + val intSum = Monoid(0)( _ + _ ) + val intProd = Monoid(1)( _ * _ ) + + val doubleSum = Monoid( 0.0 )( _ + _ ) + val doubleMax = Monoid( Double.MinValue )( _ max _ ) + + def listFlat[A] = Monoid[List[A]]( Nil )( _ ++ _ ) + + def setUnion[A] = Monoid( Set.empty[A] )( _ ++ _ ) + + def pipeline[A] = Monoid[A=>A]( identity )( _ andThen _ ) + + def optionM[A]( op: (A,A)=>A ) = + Monoid[Option[A]]( None ){ (lhs,rhs) => + (lhs,rhs) match { + case (None,_) => rhs + case (_,None) => lhs + case (Some(a),Some(b)) => Some( op(a,b) ) + } + } + + val optIntProd = optionM( intProd.op ) + val optDoubleMax = optionM( doubleMax.op ) + + + def tuple2M[A,B]( MA: Monoid[A], MB: Monoid[B] ) = + Monoid[(A,B)]( (MA.zero,MB.zero) ){ (ab1,ab2) => + ( MA.op( ab1._1, ab2._1 ), MB.op( ab1._2, ab2._2 ) ) + } + + + def mapM[K,V]( M: Monoid[V] ) = { + def merge( lhs: Map[K,V], rhs: Map[K,V] ): Map[K,V] = { + val ks = lhs.keySet ++ rhs.keySet + ks.map{ k => + val v = M.op( + lhs.getOrElse(k,M.zero), + rhs.getOrElse(k,M.zero) ) + k -> v + }.toMap + } + Monoid[Map[K,V]]( Map() )( merge ) + } +} + +object Examples { + + import Monoids._ + + def mapReduce[A,B]( as: List[A] )(f: A=>B, M: Monoid[B]): B = { + def rec( rem: List[A], acc: B ): B = rem match { + case Nil => acc + case h :: t => rec( t, M.op(acc, f(h) ) ) + } + rec( as, M.zero ) + } + def reduce[A]( as: List[A] )( M: Monoid[A] ): A = + mapReduce( as )( identity, M ) + + //Donné à titre d'exemple + def averageM[A]( MA: Monoid[A] ): Monoid[(A,Int)] = + tuple2M( MA, intSum ) + val avgDouble = averageM( doubleSum ) + def average( as: List[Double] ): Option[Double] = + if( as.isEmpty ) None + else { + val (sum,n) = + mapReduce( as )( a => (a,1), avgDouble ) + Some( sum/n ) + } + + //Série 3: and + def and( xs: List[Boolean] ): Boolean = { + reduce(xs)(Monoid(true)( _ && _ )) + } + + //Retourne le min et le max d'une liste de doubles en un seul parcours + def minMax( as: List[Double] ): (Double,Double) = { + mapReduce(as)(a => (a,a), tuple2M(Monoid(Double.MaxValue)(_ min _), Monoid(Double.MinValue)(_ max _))) + } + + //Série 3: Even + def even[A]( as: List[A] ): Boolean = { + mapReduce(as)(a => true, Monoid(true)((a,b) => !a)) + } + + //Compte le nombre d'occurence de chaque objet de la liste + def count[A]( as: List[A] ): Map[A,Int] = { + mapReduce(as)((a: A) => Map(a -> 1), mapM[A,Int](Monoid(0)(_ + _))) + } + + //Compte le nombre d'occurence de chaque pair d'objet + def count2[A,B]( as: List[(A,B)] ): Map[A,Map[B,Int]] = { + mapReduce(as)((a: (A,B)) => Map[A,Map[B,Int]](a._1 -> Map[B,Int](a._2 -> 1)), mapM[A,Map[B,Int]](mapM[B,Int](Monoid(0)(_ + _)))) + } + + //Série 4: forall + def forall[A]( ps: List[(A)=>Boolean] ): (A)=>Boolean = { + reduce(ps)(Monoid((a:A) => true)((f,g) => (a:A) => f(a) && g(a))) + } +} diff --git a/base_tp/test/8.monoidTest.scala b/base_tp/test/8.monoidTest.scala new file mode 100644 index 0000000..736232e --- /dev/null +++ b/base_tp/test/8.monoidTest.scala @@ -0,0 +1,63 @@ +package ch.hepia.tpscala.monoid + +import org.scalatest.funsuite.AnyFunSuite + +import Examples._ + +class Monoid8Suite extends AnyFunSuite { + + + test( "minMax" ) { + assert( minMax( List(1,2,3,4,5,6) ) == (1,6) ) + assert( minMax( List(1) ) == (1,1) ) + assert( minMax( List(2,2,2,2,2,2,2,2) ) == (2,2) ) + assert( minMax( List(100,-100) ) == (-100, 100) ) + } + + test( "and" ) { + assert( and(Nil) && and(List(true)) == true ) + assert( and(List( true, true, true )) == true ) + assert( and(List( true, true, false )) == false ) + } + + test( "even" ) { + assert( even( Nil ) == true ) + assert( even( List(0) ) == false ) + assert( even( List(0,0) ) == true ) + assert( even( List(0,0,0) ) == false ) + assert( even( List(0,0,0,0) ) == true ) + assert( even( List(0,0,0,0,0) ) == false ) + } + + + test( "count" ) { + assert( count( List("y", "n", "y", "y", "n", "y") ) == Map( "y"->4, "n"->2 ) ) + assert( count(Nil) == Map() ) + assert( count( List(1,2,3,4,5) ) == Map( 1->1, 2->1, 3->1, 4->1, 5->1 ) ) + } + + test( "count2" ) { + assert( + count2( List( ("a","y"), ("a", "y"), ("b","y"), ("b","n"), ("c","y") ) ) == + Map( + "a" -> Map("y"->2), + "b" -> Map ("y"->1, "n"->1), + "c" -> Map("y"->1 ) + ) + ) + } + + test( "forall" ) { + val big = (_:Int) >= 100 + val even = (_:Int) % 2 == 0 + val mul3 = (_:Int ) % 3 == 0 + val ps = forall( List( big, even, mul3 ) ) + assert( ps( 402 ) ) + assert( ! ps( 200 ) ) + assert( forall( Nil )( 402 ) ) + assert( forall( Nil )( 200 ) ) + } + + + +} -- GitLab