Checking map with numeric value

22 views
Skip to first unread message

Matthew Pocock

unread,
Sep 17, 2013, 5:37:38 PM9/17/13
to specs...@googlegroups.com
Hi,

I've got a spec that checks some arithmetic on values of maps. I'm getting this failure:

'Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.29000000000000004),Map(OutputNode(X) -> 0.78))'
 is not equal to 
'Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.29),Map(OutputNode(X) -> 0.78))'

The reason is that I'm checking for 0.29 in the test value, but the computed value has rounding error so is not quite equal to this. Is there a way to tell must_== to do a numerical comparison with a user-configurable error margin? Or is there another way to express this?

Thanks,
Matthew

--
Dr Matthew Pocock
Turing ate my hamster LTD

Integrative Bioinformatics Group, School of Computing Science, Newcastle University

skype: matthew.pocock
tel: (0191) 2566550

etorreborre

unread,
Sep 18, 2013, 9:19:46 AM9/18/13
to specs...@googlegroups.com
Hi Matthew,

I don't think that much can be done using specs. However in specs2, with the new Traversable matchers you would write something like:

// assuming that there's a way to get a Seq of pairs
weigthings.toMap.toSeq contains(exactly(aPair(InputNode(a) -> 0.1 +/- 0.1), aPair(HiddenNode(1) -> 0.29000000000000004 +/- 0.1),aPair(OutputNode(X) -> 0.78 +/- 0.1)))

where aPair is a Matcher[(K, V)] built from BeTypedEqualMatcher[K] and BeCloseToMatcher[V].

Something like that. If you think that it is worth pursuing, I'll try to make it work on a compiling example.

E.

Bill Venners

unread,
Sep 18, 2013, 1:56:14 PM9/18/13
to specs-users
Hi Matthew,

If you haven't ported to specs2 yet, you may also want to take a look at ScalaTest, which I believe has what you're asking for. You can customize the meaning of equality for Weighting so that it will compare the Double values in the nested maps with a tolerance. An example of that is shown on the documentation page for Equality:

http://www.artima.com/docs-scalatest-2.0.M8/#org.scalautils.Equality

In your case, I whipped up:

case class InputNode(c: Char)
case class HiddenNode(i: Int)
case class OutputNode(c: Char)

case class Weighting(inMap: Map[InputNode, Double], hidMap: Map[HiddenNode, Double], outMap: Map[OutputNode, Double])

val x = Weighting(Map(InputNode('a') -> 0.1), Map(HiddenNode(1) -> 0.29000000000000004), Map(OutputNode('X') -> 0.78))
val y = Weighting(Map(InputNode('a') -> 0.1), Map(HiddenNode(1) -> 0.29), Map(OutputNode('X') -> 0.78))

import org.scalautils._
import org.scalatest._
import Matchers._

x should equal (y)

If you paste this into the REPL you'll get a similar error message to your specs one:

scala> x should equal (y)
org.scalatest.exceptions.TestFailedException: Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.29000000000000004),Map(OutputNode(X) -> 0.78)) did not equal Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.29),Map(OutputNode(X) -> 0.78))

What you can do to soften that is to define an implicit Equality[Weighting], so it will be picked up by the equal matcher:

implicit val WeightingEquality = {

  new Equality[Weighting] {

    val Tol = 0.00001

    // Helper method to tolerantly compare Maps with Double values
    def approxEquals[K](leftMap: Map[K, Double], rightMap: Map[K, Double]): Boolean =
      (leftMap.size == rightMap.size) && {
      try {
        leftMap forall {
          case (k, leftV) =>
            rightMap.get(k) match {
              case Some(rightV) => leftV === rightV +- Tol
              case _ => false
            }
        }
      }
    }

    // Custom equals method for Weighting
    override def areEqual(left: Weighting, r: Any): Boolean =
      r match {
        case right: Weighting =>
          approxEquals(left.inMap, right.inMap) &&
          approxEquals(left.hidMap, right.hidMap) &&
          approxEquals(left.outMap, right.outMap)
        case _ => false
      }
  }
}

Paste that into the REPL and now your two Weightings are considered equal:

scala> x should equal (y)

Just to verify it is really checking with a tolerance, here's what happens when the difference between two of the doubles exceeds the tolerance:

scala> val z = Weighting(Map(InputNode('a') -> 0.1), Map(HiddenNode(1) -> 0.30), Map(OutputNode('X') -> 0.78))
z: Weighting = Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.3),Map(OutputNode(X) -> 0.78))

scala> x should equal (z)
org.scalatest.exceptions.TestFailedException: Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.29000000000000004),Map(OutputNode(X) -> 0.78)) did not equal Weighting(Map(InputNode(a) -> 0.1),Map(HiddenNode(1) -> 0.3),Map(OutputNode(X) -> 0.78))

Bill


--
You received this message because you are subscribed to the Google Groups "specs-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to specs-users...@googlegroups.com.
To post to this group, send email to specs...@googlegroups.com.
Visit this group at http://groups.google.com/group/specs-users.
For more options, visit https://groups.google.com/groups/opt_out.



--
Bill Venners
Artima, Inc.
http://www.artima.com

etorreborre

unread,
Sep 19, 2013, 12:37:27 AM9/19/13
to specs...@googlegroups.com
Hi Bill,

In this code:

    rightMap.get(k) match {
              case Some(rightV) => leftV === rightV +- Tol
              case _ => false
            }

I suppose that `leftV === rightV +- Tol` is converted to a Boolean via an implicit conversion which throws an Exception if the value is false?

Matthew, if you have such an "approximate" equality function available you can use it in specs/specs2 to define a matcher.

// in specs

case class equalWeighting(weigthing2: Weigthing) extends Matcher[Weigthing] {
  def apply(weighting1: =>Weigthing) =
   (approximateEqual(weigthing1, weigthing2), s"$weigthing1 is equal to $weighting2, s"$weigthing1 is not equal to $weighting2")
}

// in specs2

def equalWeighting(weigthing2: Weigthing): Matcher[Weigthing] = (weighting1: Weigthing) =>
  (approximateEqual(weigthing1, weigthing2), s"$weigthing1 is not equal to $weighting2)

This is less elegant than Bill's solution with an Equality typeclass but that should work.

E.

Bill Venners

unread,
Sep 19, 2013, 1:21:25 AM9/19/13
to specs-users
Hi Eric,


On Wed, Sep 18, 2013 at 9:37 PM, etorreborre <etorr...@gmail.com> wrote:
Hi Bill,

In this code:

    rightMap.get(k) match {
              case Some(rightV) => leftV === rightV +- Tol
              case _ => false
            }

I suppose that `leftV === rightV +- Tol` is converted to a Boolean via an implicit conversion which throws an Exception if the value is false?

No it is just a Boolean:

scala> import org.scalautils._
import org.scalautils._

scala> import Tolerance._
import Tolerance._

scala> import TripleEquals._
import TripleEquals._

scala> 1.0 === 0.9 +- 0.01
res0: Boolean = false

scala> 1.0 === 0.9 +- 0.1
res1: Boolean = true

In ScalaTest 1.x === returned an Option[String], so we have set of Legacy...TripleEquals traits in 2.0 that offer === that do that in case someone has written code expecting Option[String], but it is deprecated and will not be the default. The default === will return Boolean, with good error messages supplied by an assert macro.

Bill

etorreborre

unread,
Sep 19, 2013, 9:02:02 PM9/19/13
to specs...@googlegroups.com
> with good error messages supplied by an assert macro

Ok, that was the part I was missing.

etorreborre

unread,
Sep 19, 2013, 9:02:45 PM9/19/13
to specs...@googlegroups.com
> with good error messages supplied by an assert macro

Ok, that was the part I was missing.
Reply all
Reply to author
Forward
0 new messages