Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding "column-wise" maximum in a two dimensional array

Tags:

arrays

scala

Assume I have a two dimensional Array, for example like this:

val A1 = Array(Array(4,0,0,0),Array(3),Array(3,4,40,1),Array(50,2))

Now I would like to have maximum of items in each position.

If I write the array above in the matrix form then it's obvious what I mean by "column-wise" maximum:

4  0  0 0
3
3  4 40 1
50 2
----------
50 4 40 1 (result)

So the answer in this case would be Array(50,4,40,1) (empty values would be ignored).

I can do it like this:

A1.foldLeft(A1.head)( (x1, x2) =>
  x1.padTo(x2.length, Int.MinValue).zip(x2.padTo(x1.length,Int.MinValue)).
    map { pair => pair._1 max pair._2 }
)

but somehow this feels quite hardcore for a simple thing like this. So I would appreciate a simpler way to do this.

Maybe there is

1) Some function to do this directly?

2) Some way to do this "zipping with default value": x1.padTo(x2.length, Int.MinValue).zip(x2.padTo(x1.length,Int.MinValue)) better?

3) Some other way to improve this?

like image 887
Pekka Avatar asked Sep 10 '26 09:09

Pekka


2 Answers

Use .tranpose to obtain the 'columns' of your Array[Array[Int]], then call .map(_.max) to get the max value of all of those:

scala> val A1 = Array(Array(4,0,0,0),Array(3),Array(3,4,40,1),Array(50,2))
A1: Array[Array[Int]] = Array(Array(4, 0, 0, 0), Array(3), Array(3, 4, 40, 1), Array(50, 2))

scala> A1.transpose
res5: Array[Array[Int]] = Array(Array(4, 3, 3, 50), Array(0, 4, 2), Array(0, 40), Array(0, 1))

scala> A1.transpose.map(_.max)
res6: Array[Int] = Array(50, 4, 40, 1)

Edit: .tranpose may throw an exception if Arrays encountered later in the Array[Array[T]] are longer than the first ones:

scala> Array(Array(1,2,3), Array(1,2,3,4)).transpose
java.lang.ArrayIndexOutOfBoundsException: 3
  at scala.collection.mutable.ArrayOps$$anonfun$transpose$1$$anonfun$apply$1.apply(ArrayOps.scala:102)
  at scala.collection.mutable.ArrayOps$$anonfun$transpose$1$$anonfun$apply$1.apply(ArrayOps.scala:101)
  at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  at scala.collection.mutable.ArrayOps$ofInt.foreach(ArrayOps.scala:234)
  at scala.collection.mutable.ArrayOps$$anonfun$transpose$1.apply(ArrayOps.scala:101)
  at scala.collection.mutable.ArrayOps$$anonfun$transpose$1.apply(ArrayOps.scala:99)
  at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
  at scala.collection.mutable.ArrayOps$class.transpose(ArrayOps.scala:99)
  at scala.collection.mutable.ArrayOps$ofRef.transpose(ArrayOps.scala:186)
  ... 32 elided

scala> Array(Array(1,2,3,4), Array(1,2,3)).transpose
res5: Array[Array[Int]] = Array(Array(1, 1), Array(2, 2), Array(3, 3), Array(4))

If that can happen in your case you could always sort the outer array by the inner arrays length (in descending order):

scala> Array(Array(1,2,3), Array(1,2,3,4)).sortBy(-_.length).transpose
res6: Array[Array[Int]] = Array(Array(1, 1), Array(2, 2), Array(3, 3), Array(4))
like image 198
Marth Avatar answered Sep 11 '26 22:09

Marth


The transpose answer is correct. For the sake of completeness, there exists a zipAll function. The fold+zip version would look like this:

A1.reduceLeft((x1, x2) =>
  x1.zipAll(x2, Int.MinValue, Int.MinValue)
    .map { case (x, y) => x max y }
)

you can write a parallel version easily because max is a commutative monoid and you can use reduce (not left or right)

A1.par.reduce((x1, x2) =>
  x1.zipAll(x2, Int.MinValue, Int.MinValue)
    .map { case (x, y) => x max y }
)

You were on the right track, this version is definitely faster and uses much less memory than the sort+transpose one for large arrays, e.g.

val A1 = Array.fill(100000)(Array.fill(Random.nextInt(100000))(Random.nextInt()))

your idea is definitely the way to go, if you only need to calculate a max you don't want to store intermediate results (i.e. sort, then transpose) in memory. If your matrix was on a disk, you wouldn't even need to load it, you could just iterate once over the rows

like image 39
Giovanni Caporaletti Avatar answered Sep 11 '26 23:09

Giovanni Caporaletti



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!