Zip a multidimensional array with itself

I have a chain where, at some point, .map returns a two-dimensional array, like this:

...
.transpose
.map(&.sort!)

The output would looks like [[1,3][2,4].
I would like to directly zip the first sub-array with the second one:

.transpose
.map(&.sort!)[0].zip(zip with what ? &.[1] ?)  Doesn't work.

That would be a bit like using .tap

.tap { |a| = a[0].zip(a[1]) }

but with the .tap block returning the modified value and not self (which, obviously, is not what .tap is intended for).
I tried playing with ‘itself’ also without success.
And (of course) all of this without using intermediary variables, the chain should continue after the ‘zipping’.
Is it achievable ?

Ah. .try instead of .tap …
Is it the correct approach ?

Don’t suppose you have a runnable example showing what you want?

Sure I have :)

File.read_lines("01")
.map(&.split)
.map(&.map(&.to_i))
.transpose
.map(&.sort!)
.try { |a| a[0].zip(a[1]) }
.map(&.reduce { |a,i| (a - i).abs })
.sum

Exploratory, purely out of curiosity, just wasting some time revisiting AoC in a more compact way…

Crystal does not have a built-in, high-level representation of multidimensional matrices like Python’s NumPy or Ruby’s NArray. However, suppose you’re thinking in terms of a Pandas DataFrame, an Excel sheet, or a 2D matrix.

a = [[1, 2], [3, 4]]
b = a.transpose
c = b.map(&.sort)
d0 = c[0]
d1 = c[1]
e = d0.zip(d1).map { |x, y| x + y }

If you want to sort by row (or column) and then aggregate by column (or row)—similar to operations in Pandas—the simplest approach is to use transpose twice:

crystal
e = a.transpose       # First transpose: rows become columns
     .map(&.sort)     # Sort each column
     .transpose       # Second transpose: return to original row orientation
     .map { |row| row.sum }  # Sum each row

However, this method may be costly because it allocates memory for the entire matrix twice. That’s why you might prefer using zip.
In such cases, reduce can be an alternative:

crystal
a.transpose
 .map(&.sort)
 .reduce(Array.new(a.size, 0)) { |acc, col|
    acc.zip(col).map { |x, y| x + y }
 }

While reduce may run just fine on my computer, my brain doesn’t seem to come with enough RAM to reason clearly about such method chains. So I prefer a more explicit and split-up version:

sorted = a.transpose.map(&.sort)
col0 = sorted[0]
col1 = sorted[1]
result = col0.zip(col1).map { |x, y| x + y }

Even the Reduce version is not perfect in terms of efficiency.

  • Reuse Array.new(size, 0) as a preallocated buffer instead of creating a new array each time.
  • Avoid zip and instead use an indexed for loop with explicit i access.

I wrote this answer while asking ChatGPT, but ChatGPT also got it wrong twice, so I used my brain to correct it, but I’m not sure if this is still completely correct. it seems AI is not good at reduce either.

I posted the answer before reading the sample code above.
After reading it, I realized that my initial guess wasn’t quite accurate.

@kojix2 yet nevertheless useful.
And I learned about NArray.
As an old wisdom wizard said to me once “when you have a lot of time ahead of you, you have so much to lose as a result of your actions, when time becomes short, you worry less” ;)
ありがとうございます

1 Like