Iterate two or three arrays in parallel?


#1

Greetings,

from the documentation on “Control Structures”, Breslav mentioned that the support for parallel iteration  of two or more arrays is planned, but not in the stdlib yet. That was in May 23, 2012, and I saw that the Pair<A, B> and Triple<A, B, C> structures now exist. Do the equivalent forEach and map functions also exist? I wrote my own and I’m not sure if I’m just duplicating work I don’t need to.

For Pair:

fun  Pair, out Iterable>.forEach(f : (A, B) -> Unit) {
  val ia = first.iterator()
  val ib = second.iterator()

  while (ia.hasNext() && ib.hasNext()) {
  val va = ia.next()
  val vb = ib.next()

  &nbsp;&nbsp;f(va, vb)

  }
}

fun  Pair, out Iterable>.map(f : (A, B) -> R) : List {
  val ia = first.iterator()
  val ib = second.iterator()

  var collect = ArrayList()
  while (ia.hasNext() && ib.hasNext()) {
  val va = ia.next()
  val vb = ib.next()

  &nbsp;&nbsp;collect.add(f(va, vb))

  }

  return collect
}


For Triple:

fun  Triple, out Iterable, out Iterable>.forEach(f : (A, B, C) -> Unit) {
  val ia = first.iterator()
  val ib = second.iterator()
  val ic = third.iterator()

  while (ia.hasNext() && ib.hasNext() && ic.hasNext()) {
  val va = ia.next()
  val vb = ib.next()
  val vc = ic.next()

  &nbsp;&nbsp;f(va, vb, vc)

  }
}

fun  Triple, out Iterable, out Iterable>.map(f : (A, B, C) -> R) : List {
  val ia = first.iterator()
  val ib = second.iterator()
  val ic = third.iterator()

  var collect = ArrayList()
  while (ia.hasNext() && ib.hasNext() && ic.hasNext()) {
  val va = ia.next()
  val vb = ib.next()
  val vc = ic.next()

  &nbsp;&nbsp;collect.add(f(va, vb, vc))

  }

  return collect
}


#2

Maybe it makes more sense to convert a pair of iterable to an iterable of pair (lazily) and then just have all functions available on iterables for free?


#3

I'm sorry, I have to admit that I don't know how to do that conversion in Kotlin. The documentation seems very sparse when it comes to Iterators or how to convert to and from iterators/iterable.

I’m assuming you want me to write something like

fun <A, B> Pair<out Iterable<A>, out Iterable<B>>.iterator() : Iterator<Pair<A,B>> {
  return … ?
}

But the exact implementation alludes me. Do I have to create an anonymous subclass of Iterator like so:

return object : Iterator<Pair<A,B>> {
  public override fun next() {

  }

  public override fun hasnext() {

  }
}

and if so, what do I write?


#4

Subclassing Iterator looks reasonable. Inside you can write the same kind of code you wrote before:

override fun next(): Pair<A, B> {   return Pair(a.next(), b.next()) }

override fun hasNext(): Boolean = a.hasNext() && b.hasNext()


#5

Thanks for the help! I think I got it now:

fun <A, B, C> Triple<out Iterable<A>, out Iterable<B>, out Iterable<C>>.iterator() : Iterator<Triple<A,B,C>> {
    val ia = first.iterator()
    val ib = second.iterator()
    val ic = third.iterator()

  return object : Iterator<Triple<A,B,C>> {
  public override fun next() : Triple<A, B, C> {
           return Triple(ia.next(), ib.next(), ic.next())
  }

  &nbsp;&nbsp;public override fun hasNext() : Boolean {

           return (ia.hasNext() && ib.hasNext() && ic.hasNext())
  }
  }
}

Which lets me call my function like so:

Triple(unprocessedTracks, processedTracks, processedTrackStorageUrls).iterator().forEach {
    triple -> persist(triple)
}

As opposed to my .map extension function:

Triple(unprocessedTracks, processedTracks, processedTrackStorageUrls).forEach {
    (unprocessedTrack, processedTrack, storageUrls) -> persist(unprocessedTrack, processedTrack, storageUrls)
}

I like that the map version already unpacks the triple parameters for me. I believe this aids in code readability than if I were to use my triple and then unpack at the function site. Of course, I could unpack it during the function call, but first, second and third are completely nondescript. That would mean I should create an intermediate data class to hold those variables for me. What are your thoughts on this?

Furthermore, should I open an issue to extend Pair and Triple (possibly Tuple) with an iterator function?


#6

We are going to support this "unpacking" to be trasparent in the language. Some syntax changes are required, so it won't be too soon, but it will happen.

Fo now, you can use this as a middle-ground workaround:

Triple(unprocessedTracks, processedTracks, processedTrackStorageUrls).forEach {
    t -> val(unprocessedTrack, processedTrack, storageUrls)=t
    persist(unprocessedTrack, processedTrack, storageUrls)
}

It's a little more character, and not so clean, but more or less readable


#7

Can we do this using zip?

aIterator.asSequence().zip(bIterator.asSequence()).forEach{ (a, b) -> //code }