3

What is the most efficient way to iterate over two lists (of differing length) backwards in Scala.

So for two lists

List(a,b,c) and List(1,2)

the pairs would be

(c,2) and (b,1)

Note: I would rather not do a reverse of each list.

ademartini
  • 1,311
  • 1
  • 14
  • 24

4 Answers4

5

A simple way is :

 List('a','b','c').reverse zip List(1,2).reverse

Reversing the list is O(n) however, if you're worried about efficiency.

According to List's scaladoc, using reverseIterator might be more efficient. That way you don't creat a new list like with reverse, but traverse it as you keep iterating. That'd be :

val it = list1.reverseIterator zip list2.reverseIterator  //returns an Iterator you can force
it.toList // List((c,2), (b,1))
Chirlo
  • 5,989
  • 1
  • 29
  • 45
  • `reverseIterator` calls `reverse` internally, which creates a new list, so the savings are not really that great – smac89 Apr 27 '17 at 18:30
1

Using parallel collections,

def parRevZip (a: List[String], b: List[Int]) = {

  val max = Math.max(a.size, b.size)
  val n = Math.abs(a.size - b.size)

  if (a.size > b.size)
    (max to n+1 by -1).par.map { i => (a(i-1), b(i-n-1)) }
  else
    (max to n+1 by -1).par.map { i => (a(i-n-1), b(i-1)) }
}

Taking into account different index values for possibly different sized lists, this approach fetches and pairs the same number of elements starting from the end of each list.

Performance needs careful evaluation; for small lists, a plain reverse and zipping may prove much simpler and efficient; for large lists, on the contrary, this parallel approach may be of interest.

Code Refinement

def parRevZip[A,B] (a: List[A], b: List[B]) = {

  val aSize = a.size
  val bSize = b.size

  val max = Math.max(aSize, bSize)
  val n = Math.abs(aSize - bSize)

  if (aSize > bSize)
    (max-1 to n by -1).par.map { i => (a(i), b(i-n)) }
  else
    (max-1 to n by -1).par.map { i => (a(i-n), b(i)) }
}

Using non recursive collections

Convenient immutable collections here where the computation of size is O(1) (or quasi-constant) (see Recursive collections in Scala such as List) include for instance Array. Hence,

def parRevZip[A,B] (a: Array[A], b: Array[B])

which does not follow any further the requirement of processing lists.

Community
  • 1
  • 1
elm
  • 20,117
  • 14
  • 67
  • 113
  • Finding length of a `List` is an `O(N)` operation. Scala API says: "Note: the execution of length may take time proportial to the length of the sequence." – tuxdna Jan 31 '14 at 07:37
  • @tuxdna, absolutely true :) Indeed a list is a recursive structure (http://stackoverflow.com/a/8197826/3189923); a non-recursive structure must be considered in this answer... perhaps Array ? – elm Jan 31 '14 at 07:42
  • 1
    Also, `a(i)` takes time proportional to `i`. A `Vector` would be more appropriate if you're doing random access. – Chirlo Jan 31 '14 at 10:02
  • Thanks @Chirlo :) so this answer is feasible as far as List is not used :) Vector and Array are good candidates... – elm Jan 31 '14 at 10:05
0

I think you mean this:

val a = List(1, 2, 3)
val b = List(8, 9)

val result = a.reverse zip b.reverse
Rado Buransky
  • 3,252
  • 18
  • 25
  • Is there something more efficient than that? I don't want to copy each list for the reverse. – ademartini Jan 30 '14 at 19:40
  • Scala List is implemented as a one-way linked list and therefore it's not designed to be navigated backwards. To answer efficiency is out of this scope. Are you worried about memory complexity, time complexity, are those lists more or less static, or do they change often, ... try to take a look here: http://www.scala-lang.org/docu/files/collections-api/collections_40.html – Rado Buransky Jan 30 '14 at 19:46
  • It most certainly is in scope. That was the initial question. – ademartini Jan 30 '14 at 19:48
  • Yes, but as I said you have to be much more specific what "efficient" means. What is your use case. Every data structre has its pros and cons. The link I have sent to you should help you to make a good decision. – Rado Buransky Jan 30 '14 at 19:51
0

Here is my attempt at this problem. The original lists a and b are not duplicated. The operation is O(N) due to List.size:

object test extends App {
  val a = List("a", "b", "c")                     //> a  : List[String] = List(a, b, c)
  val b = List(1, 2)                              //> b  : List[Int] = List(1, 2)
  val aSize = a.size                              //> aSize  : Int = 3
  val bSize = b.size                              //> bSize  : Int = 2

  // find which is smaller and which is bigger list
  val (smaller, bigger) = if (aSize < bSize) (a, b) else (b, a)
                                                  //> smaller  : List[Any] = List(1, 2)
                                                  //| bigger  : List[Any] = List(a, b, c)

  // skip the extra entries from head of bigger list
  val truncated = bigger.drop(Math.abs(aSize - bSize))
                                                  //> truncated  : List[Any] = List(b, c)

  val result = if (a == smaller)
    smaller.zip(truncated).reverse
  else
    truncated.zip(smaller).reverse                //> result  : List[(Any, Any)] = List((c,2), (b,1))

}
tuxdna
  • 8,257
  • 4
  • 43
  • 61