5

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.

SkyWalker
  • 13,729
  • 18
  • 91
  • 187
  • 2
    In 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 Answers4

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
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
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