I have an array val x : Array[Double] and would like to check as precondition to a function that every x(i) <= x(i+1) for all i. What's the way to do it using functional programming in Scala. I looked for e.g. foldLeft or foldRight but they accumulate rather than visiting every pair of adjacent elements.
Asked
Active
Viewed 787 times
5
SkyWalker
- 13,729
- 18
- 91
- 187
-
2In Haskell, you'd `zip` a list with itself so that you get pairs of adjacent elements. Not sure if it's as elegant in Scala. – Bergi Sep 06 '16 at 06:35
-
Why plain old "def isMonotonic(...)" with while loop inside is "unelegant"? One don't have to use those inefficient zips/sliding to be "functional". As long as your function is pure, it is OK. Practicality should be the goal, not "elegance". – dveim Sep 06 '16 at 08:11
-
OK removed the elegant word .. I was interested in the functional form solution. The reason why functional is elegant to me is a personal thing perhaps but I guess has to do with better communication of intent = elegance – SkyWalker Sep 06 '16 at 08:15
-
1@Bergi `(vs, vs.view.drop(1)).zipped.forall(_ < _)`. The view just manages indexes, and `zipped` doesn't build anything. – som-snytt Sep 06 '16 at 08:47
4 Answers
6
Consider this:
def isMonotonic(arr:Array[Int]) =
if (arr.isEmpty) true
else (arr, arr.tail).zipped.forall {case (a,b) => a <= b}
Simplified solution (thanks to @som-snytt):
def isMonotonic(arr:Array[Int]) =
(arr, arr.drop(1)).zipped.forall (_ <= _)
Nyavro
- 8,806
- 2
- 26
- 33
-
2Avoid intermediate collections: `(vs, vs.view.drop(1)).zipped.forall(_ < _)`. – som-snytt Sep 06 '16 at 08:35
-
Is vs.view.drop(1) equivalent to vs.view.tail? I would prefer the second one – Nyavro Sep 06 '16 at 08:47
-
4
You can use IterableLike.sliding:
val isMonotonic =
Seq(1,2,3,4,5).sliding(2).forall {
case Seq(x, y) => x < y
case _ => true
}
Edit:
Since you're using an Array[Double], you'll need:
val isMonotonic =
Array(1d, 2d, 3d, 4d, 5d).sliding(2).forall {
case Array(x, y) => x < y
case _ => true
}
Yuval Itzchakov
- 146,575
- 32
- 257
- 321
-
-
-
-
@jwvh It would just ignore the tail of the list if it exists, wouldn't it? I see it working in the REPL. – Yuval Itzchakov Sep 06 '16 at 06:39
-
But that's my question. How could there be any tail? How could there be anything to ignore? – jwvh Sep 06 '16 at 06:40
-
Nice solution, but you have to process case when Set contains less than 2 items – Nyavro Sep 06 '16 at 06:41
-
@jwvh It ignores the empty list. I guess one could equally have written `case List(x, y)` – Bergi Sep 06 '16 at 06:42
-
@Bergi, my point exactly. I think your `List()` suggestion is cleaner and easier to read/understand. – jwvh Sep 06 '16 at 06:44
-
-
@Nyavro In that case, the iterator doesn't contain any elements (if I interpret [the docs](http://www.scala-lang.org/api/2.11.1/index.html#scala.collection.IterableLike) correctly) and `forAll` would yield `true` – Bergi Sep 06 '16 at 06:49
-
The problem is in match statement. You should add case _. Try to run on Seq(1) – Nyavro Sep 06 '16 at 06:53
-
@Bergi He's right. In case the sequence has a single element it blows on the match. – Yuval Itzchakov Sep 06 '16 at 06:56
-
When I plugin my `val x: Array[Double]` I get the following error `constructor cannot be instantiated to expected type; found : scala.collection.immutable.::[B] required: Array[Double]` – SkyWalker Sep 06 '16 at 07:04
-
-
@som-snytt Can you elaborate why using array extractor is bad? – Yuval Itzchakov Sep 06 '16 at 08:45
-
-
@som-snytt [No, I don't.](https://gist.github.com/YuvalItzchakov/8922636e2fa8b5b0e060fb6f69adbd0e). This is running Scala 2.11.8 – Yuval Itzchakov Sep 06 '16 at 08:57
-
@som-snytt I also see [other examples](http://stackoverflow.com/questions/6647166/how-do-i-pattern-match-arrays-in-scala) doing exactly that same? – Yuval Itzchakov Sep 06 '16 at 08:58
1
With a couple of fine answers to choose from, the next question is: Can we make it generic?
This is my shot at it.
def isMonotonic[T](ts: Traversable[T])(implicit ev: Ordering[T]): Boolean = {
if (ts.size < 2) true
else if (ev.gt(ts.head, ts.tail.head)) false
else isMonotonic(ts.tail)
}
Appears to work for the following.
isMonotonic(Array('c','a','z')) // false
isMonotonic(Vector(3.1, 2.2, 7.7)) // false
isMonotonic(List[Int]()) // true
isMonotonic(Seq("abc", "bcb", "tz", "xs")) // true
jwvh
- 50,871
- 7
- 38
- 64
1
A slightly different approach
def isMonotonic(xs:List[Int]) = xs.sliding(2)
.collectFirst{case List(x,y) if x > y => 1}
.isEmpty
Works on empty and length-one lists because the partial function is never defined on those. Because it's collectFirst, it bails at the first evidence it's not monotonic The xs zip xs.drop(1) idea can be used if preferred
The Archetypal Paul
- 41,321
- 20
- 104
- 134