1

I need to iterate over a list and compare the previous element in the list to current. Being from traditional programming background, I used for loop with a variable to hold the previous value but with Scala I believe there should be a better way to do it.

for (item <- dataList)
{
   // Code to compare previous list element
   prevElement = item
}

The code above works and give correct results but I wanted to explore the functional way of writing the same code.

4
  • What is it you want to do? Filter based on this comparison? Map to a list of booleans? You can use pattern matching for something like this, for example x::y::xs where x and y are always the first 2 elements of the list and you then recurs over the rest. Commented Sep 22, 2018 at 15:18
  • All I want to do is group list of elements based on date range. If the item has overlapping dates then merge them together Commented Sep 22, 2018 at 15:33
  • 2
    to compare current with previous element, use dataList.zip(dataList.tail).map{case (current,previous} => ...} Commented Sep 22, 2018 at 19:08
  • This is the best way to do it, unless you can remove mutation and keeping reference to previous element (without memory explosion, like in zip example) Commented Sep 30, 2018 at 0:18

3 Answers 3

5

There are a number of different possible solutions to this. Perhaps the most generic is to use a helper function:

// This example finds the minimum value (the hard way) in a list of integers. It's
// not looking at the previous element, as such, but you can use the same approach.
// I wanted to make the example something meaningful.
def findLowest(xs: List[Int]): Option[Int] = {

  @tailrec // Guarantee tail-recursive implementation for safety & performance.
  def helper(lowest: Int, rem: List[Int]): Int = {

    // If there are no elements left, return lowest found.
    if(rem.isEmpty) lowest

    // Otherwise, determine new lowest value for next iteration. (If you need the
    // previous value, you could put that here.)
    else {
      val newLow = Math.min(lowest, rem.head)
      helper(newLow, rem.tail)
    }
  }

  // Start off looking at the first member, guarding against an empty list.
  xs match {
    case x :: rem => Some(helper(x, rem))
    case _ => None
  }
}

In this particular case, this can be replaced by a fold.

def findLowest(xs: List[Int]): Option[Int] = xs match {
  case h :: _ => Some(xs.fold(h)((b, m) => Math.min(b, m)))
  case _ => None
}

which can be simplified further to just:

def findLowest(xs: List[Int]): Option[Int] = xs match {
  case h :: _ => Some(xs.foldLeft(h)(Math.min))
  case _ => None
}

In this case, we're using the zero argument of the fold operation to store the minimum value.

If this seems too abstract, can you post more details about what you would like to do in your loop, and I can address that in my answer?

Update: OK, so having seen your comment about the dates, here's a more specific version. I've used DateRange (which you will need to replace with your actual type) in this example. You will also have to determine what overlap and merge need to do. But here's your solution:

// Determine whether two date ranges overlap. Return true if so; false otherwise.
def overlap(a: DateRange, b: DateRange): Boolean = { ... }

// Merge overlapping a & b date ranges, return new range.
def merge(a: DateRange, b: DateRange): DateRange = { ... }

// Convert list of date ranges into another list, in which all consecutive,
// overlapping ranges are merged into a single range.
def mergeDateRanges(dr: List[DateRange]): List[DateRange] = {

  // Helper function. Builds a list of merged date ranges.
  def helper(prev: DateRange, ret: List[DateRange], rem: List[DateRange]):
  List[DateRange] = {

    // If there are no more date ranges to process, prepend the previous value
    // to the list that we'll return.
    if(rem.isEmpty) prev :: ret

    // Otherwise, determine if the previous value overlaps with the current
    // head value. If it does, create a new previous value that is the merger
    // of the two and leave the returned list alone; if not, prepend the
    // previous value to the returned list and make the previous value the
    // head value.
    else {
      val (newPrev, newRet) = if(overlap(prev, rem.head)) {
        (merge(prev, rem.head), ret)
      }
      else (rem.head, prev :: ret)

      // Next iteration of the helper (I missed this off, originally, sorry!)
      helper(newPrev, newRet, rem.tail)
    }
  }

  // Start things off, trapping empty list case. Because the list is generated by pre-pending elements, we have to reverse it to preserve the order.
  dr match {
    case h :: rem => helper(h, Nil, rem).reverse // <- Fixed bug here too...
    case _ => Nil
  }
}
Sign up to request clarification or add additional context in comments.

6 Comments

This is very good explanation. All I want to do is group list of elements based on date range. If the item has overlapping dates then merge them together. Item in this case with have start and end date as properties
@Ankit I updated the answer to address your comment.
@Ankit Can you update your question to show the code you now have?
@Ankit Ah! I'm not sure whether it explains your error, but I missed off the recursive helper call in the last version. Give that a try...
Nopes. newPrev is not of the expected data type
|
0

If you're looking to just compare the two sequential elements and get the boolean result:

val list = Seq("one", "two", "three", "one", "one")
val result = list.sliding(2).collect { case Seq(a,b) => a.equals(b)}.toList

The previous will give you this

List(false, false, false, true)

An alternative to the previous one is that you might want to get the value:

val result = list.sliding(2).collect { 
  case Seq(a,b) => if (a.equals(b)) Some(a) else None
  case _ => None
}.toList.filter(_.isDefined).map(_.get)

This will give the following:

List(one)

But if you're just looking to compare items in list, an option would be to do something like this:

val list = Seq("one", "two", "three", "one")

for {
  item1 <- list
  item2 <- list
  _ <- Option(println(item1 + ","+ item2))
} yield ()

This results in:

one,one
one,two
one,three
one,one
two,one
two,two
two,three
two,one
three,one
three,two
three,three
three,one
one,one
one,two
one,three
one,one

Comments

0

If you are not mutating the list, and are only trying to compare two elements side-by-side. This can help you. It's functional.

def cmpPrevElement[T](l: List[T]): List[(T,T)] = {
  (l.indices).sliding(2).toList.map(e => (l(e.head), l(e.tail.head)))
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.