In some test code, I have a Seq[A]
that I'm filtering down to items matching a given property. There should be at most one such element in the sequence. If it has more, it means I set up something wrong in the test.
val expected: Seq[MyType] = values.filter(_.something)
I would like this to be an Option[MyType]
, throwing an exception indicating a test setup failure if there are two or more elements in the filtered sequence.
The following works, but it feels like there's likely to be a simpler, more idiomatic way in Scala.
val expected: Option[MyType] = values.filter(_.something) match {
case v if v.size > 1 => throw new AssertionError("Error in test setup")
case v => v.headOption
}
How can I cleanly filter a Seq[A]
to an Option[A]
, throwing an exception if there is more than one element in the sequence?
In some test code, I have a Seq[A]
that I'm filtering down to items matching a given property. There should be at most one such element in the sequence. If it has more, it means I set up something wrong in the test.
val expected: Seq[MyType] = values.filter(_.something)
I would like this to be an Option[MyType]
, throwing an exception indicating a test setup failure if there are two or more elements in the filtered sequence.
The following works, but it feels like there's likely to be a simpler, more idiomatic way in Scala.
val expected: Option[MyType] = values.filter(_.something) match {
case v if v.size > 1 => throw new AssertionError("Error in test setup")
case v => v.headOption
}
How can I cleanly filter a Seq[A]
to an Option[A]
, throwing an exception if there is more than one element in the sequence?
3 Answers
Reset to default 2I think this is reasonably clear
val found = values.filter(<predicate>)
assert(found.size <= 1)
val expected: Option[MyType] = found.headOption
This is less efficient but one line shorter
assert(values.count(<predicate>) < 2)
val expected = values.find(<predicate>)
The reduceOption
method can be used, throwing an exception if the reduction operator is ever called (which will happen only when there are two or more elements).
val expected: Option[MyType] = values
.filter(_.something)
.reduceOption((_, _) => throw new AssertionError("Error in test setup"))
Since it is a reasonable solution (and so it's not just hiding in the question itself), there's the option of using match
as suggested in the question:
val expected: Option[MyType] = values.view.filter(_.something) match {
case v if v.sizeCompare(1) > 0 => throw new AssertionError("Error in test setup")
case v => v.headOption
}
This is reasonably clear and not terribly verbose.
assert(expected.size <= 1)
? – Tim Commented Mar 19 at 23:36foldLeft
where the accumulator is anOption[A]
with an initial value ofNone
, when something satisfies the predicate, then you check the accumulator, if it is aNone
you give it aSome(a)
but if it was already aSome
then youthrow
. – Luis Miguel Mejía Suárez Commented Mar 19 at 23:37assert
or similar will be the most straightforward thing for a future reader of the code. – Gaël J Commented Mar 20 at 6:23Seq
that should have 0 or 1 elements to anOption
. – M. Justin Commented Mar 20 at 17:24val expected: Option[MyType] = values.foldLeft(Option.empty[MyType])((a,b) => if (b.something && a.isEmpty) Some(b) else throw new AssertionError("Error in test setup"))
– Luis Miguel Mejía Suárez Commented Mar 20 at 17:37