Cassidoo’s Interview question of the week | 444

Hello :waving_hand:

This week question is about moving numbers. Here it is.

You have a 2D grid of numbers. Write a function that zooms in by an integer factor k >= 2 by turning each cell into a k x k block with the same value, returning the bigger grid.

Example:

zoom([[1,2],[3,4]], 2)
[
  [1,1,2,2],
  [1,1,2,2],
  [3,3,4,4],
  [3,3,4,4]
]

zoom([[7,8,9]], 3)
[
  [7,7,7,8,8,8,9,9,9],
  [7,7,7,8,8,8,9,9,9],
  [7,7,7,8,8,8,9,9,9]
]

zoom([[1],[2]], 3)
[
  [1,1,1],
  [1,1,1],
  [1,1,1],
  [2,2,2],
  [2,2,2],
  [2,2,2]
]

Happy coding.

1 Like

My (highly inefficient) solution:

require 'minitest/autorun'

def zoom(matrix, factor)
  raise ArgumentError, 'factor must be >= 2' unless factor >= 2

  matrix.map do |vector|
    repeated_items = vector.flat_map { [it] * factor }

    Array.new(factor, repeated_items)
  end.flatten(1)
end

class TestSolution < Minitest::Test
  def test_zoom
    assert_equal(
      [
        [1, 1, 2, 2],
        [1, 1, 2, 2],
        [3, 3, 4, 4],
        [3, 3, 4, 4]
      ],
      zoom([[1, 2], [3, 4]], 2)
    )

    assert_equal(
      [
        [7, 7, 7, 8, 8, 8, 9, 9, 9],
        [7, 7, 7, 8, 8, 8, 9, 9, 9],
        [7, 7, 7, 8, 8, 8, 9, 9, 9]
      ],
      zoom([[7, 8, 9]], 3)
    )

    assert_equal(
      [
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
        [2, 2, 2],
        [2, 2, 2],
        [2, 2, 2]
      ],
      zoom([[1], [2]], 3)
    )
  end
end

1 Like
module Enumerable
  def eachator
    Enumerator.new do |e|
      each{ yield(e, it) }
    end
  end
end

def zoom a, f
  a.eachator do |e, i|
    f.times do
      e << i.eachator{|e, i| f.times{ e << i } }.to_a
    end
  end.to_a
end

zoom([[1],[2]], 3)
3 Likes

Mine is the least efficient solution :see_no_evil_monkey:

def zoom(grid, factor)
  grid
    .map { |row|
      row.flat_map { |col| [col] * factor } # zoom on x axis
    }
    .flat_map { |row| [row] * factor } # zoom on y axis
end
3 Likes

It was a bit hard :slight_smile: I used AI :sweat_smile:

def zoom(grid, k)
  result = []

  grid.each do |row|
    zoomed_row = row.flat_map { |cell| [cell] * k }

    k.times { result << zoomed_row }
  end

  result
end

pp zoom([[1],[2]], 3)

I didn’t heard flat_map before. This week question showed me that one.

Code reads very well :100:

2 Likes

def zoom(grid, integer)
return “error” if k < 2
grid.flat_map do |row|
more_rows = row.flat_map { |n| [n]*k } # Multiply rows by integer
[more_rows] * k # Repeat rows by integer
end
end

How do you make the code all in one block :smiling_face_with_tear:

Hello folks! I got totally hooked up to Cassidy Interview QOTW, and I’m delighted that there is a place where we can share and compare and learn about the solutions in Ruby! :heart:

I never imagined that you could actually provide a one-liner answer to this one (via endless method), I like yours @fpsvogel! But I’m not sure why you and @charlie believe that your solutions are so inefficient? :snail:

@eayurt as I am starting to use AI in my learning process (more like a search on steroids), I would like to know how you used it for this solution? You prompted it with the question or somehow else?

@roasted-oolong you have one typo in your solution: you are using integer as a parameter for the factor in your method, and then inside the method you are using k as a factor. :eyes:
BTW, one way to put a code in markdown code block is to fence it with an open ```ruby and closing ``` tags, or just write ``` in a new line and a code block will appear and you can paste your code inside it. :blush:

So, back to my answer, which is BTW inspired by the recent exposure to Sandi Metz’s Smalltalkism—I really like the idea of sending messages to the objects! :upside_down_face: I was absolutely sure that my n00by, implement-the-first-obvious-pattern, 4-nested-loops (YUCK!!!) solution would be the most inefficient/slowest one, BUT no,@lpogic won that medal, although I’m not sure why, your solution looks so idiomatic and beautiful and technically advanced! :frowning:

So, here it is, my monkey-patched version (with tests), and below you have benchmarks for all provided solutions.

P.S.
Pardon my lengthy post, again, I can’t stress enough how great it is to be able to have a forum place to talk about Ruby! And I was a fan of em dashes before it was (AI) cool (drink). :stuck_out_tongue:

module ZoomifyArray
  def zoom_in_by(factor)
    raise ArgumentError, "Factor MUST be an Integer greater than or equal to 2!" unless factor.is_a?(Integer) && factor >= 2

    number_of_rows = size
    number_of_cols = first.size
    bigger_grid    = Array.new(number_of_rows * factor) { Array.new(number_of_cols * factor) }

    each_with_index do |row, row_index|
      row.each_with_index do |cell, col_index|
        0.upto(factor - 1) do |shift_row_index|
          0.upto(factor - 1) do |shift_col_index|
            bigger_grid[(row_index * factor) + shift_row_index][(col_index * factor) + shift_col_index] = cell
          end
        end
      end
    end

    bigger_grid
  end
end

module ArrayRefinements
  refine Array do
    import_methods ZoomifyArray
  end
end

BENCHMARKS:

==> Benchmarking 10x10 grid with random numbers from 10 to 99 and factor 10
ruby 4.0.1 (2026-01-13 revision e04267a14b) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
izkreny_zoom            364.000 i/100ms
charlie_zoom             5.106k i/100ms
lpogic_zoom             209.000 i/100ms
fpsvogel_zoom            5.167k i/100ms
eayurt_zoom              5.327k i/100ms
roasted_oolong_zoom      5.185k i/100ms
Calculating -------------------------------------
izkreny_zoom              3.463k (± 1.3%) i/s  (288.75 μs/i) -     17.472k in   5.045991s
charlie_zoom             51.352k (± 2.0%) i/s   (19.47 μs/i) -    260.406k in   5.073118s
lpogic_zoom               2.083k (± 1.8%) i/s  (479.98 μs/i) -     10.450k in   5.017523s
fpsvogel_zoom            51.973k (± 1.9%) i/s   (19.24 μs/i) -    263.517k in   5.072110s
eayurt_zoom              53.469k (± 2.5%) i/s   (18.70 μs/i) -    271.677k in   5.084349s
roasted_oolong_zoom      52.711k (± 1.7%) i/s   (18.97 μs/i) -    264.435k in   5.018322s

Comparison:
eayurt_zoom         :    53469.0 i/s
roasted_oolong_zoom :    52710.6 i/s - same-ish: difference falls within error
fpsvogel_zoom       :    51973.2 i/s - same-ish: difference falls within error
charlie_zoom        :    51352.4 i/s - same-ish: difference falls within error
izkreny_zoom        :     3463.2 i/s - 15.44x  slower
lpogic_zoom         :     2083.4 i/s - 25.66x  slower

==> Benchmarking 100x100 grid with random numbers from 100 to 999 and factor 100
ruby 4.0.1 (2026-01-13 revision e04267a14b) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
izkreny_zoom             1.000 i/100ms
charlie_zoom            30.000 i/100ms
lpogic_zoom              1.000 i/100ms
fpsvogel_zoom           31.000 i/100ms
eayurt_zoom             25.000 i/100ms
roasted_oolong_zoom     29.000 i/100ms
Calculating -------------------------------------
izkreny_zoom              0.347 (± 0.0%) i/s     (2.88 s/i) -       2.000 in   5.774075s
charlie_zoom            286.291 (± 4.2%) i/s    (3.49 ms/i) -      1.440k in   5.038385s
lpogic_zoom               0.255 (± 0.0%) i/s     (3.93 s/i) -       2.000 in   7.870985s
fpsvogel_zoom           275.924 (± 7.6%) i/s    (3.62 ms/i) -      1.395k in   5.089186s
eayurt_zoom             284.645 (± 4.6%) i/s    (3.51 ms/i) -      1.425k in   5.017416s
roasted_oolong_zoom     288.974 (± 4.2%) i/s    (3.46 ms/i) -      1.450k in   5.027366s

Comparison:
roasted_oolong_zoom :      289.0 i/s
charlie_zoom        :      286.3 i/s - same-ish: difference falls within error
eayurt_zoom         :      284.6 i/s - same-ish: difference falls within error
fpsvogel_zoom       :      275.9 i/s - same-ish: difference falls within error
izkreny_zoom        :        0.3 i/s - 832.38x  slower
lpogic_zoom         :        0.3 i/s - 1135.40x  slower

4 Likes

@izkreny Interesting, thanks for the benchmarks. And, welcome!

I assumed my solution would be slower because it creates multiple arrays within each iteration ([col] * factor and [row] * factor).

That just goes to show that measuring performance is the only way to be sure about it.

I also have been a fan of em dashes for a long time, but these days I find myself using a semicolon in some places when I would have used an em dash in the days before AI :sweat_smile:

2 Likes

I just wrote do it ahaha :sweat_smile:

1 Like

Hehe, so Yoda style then: “(Just) Do (it) or do not. There is no try.” :magic_wand:

I forgot to thank you for creating a post for each QOTW. If you ever get tired of doing it, just let me know, and I can jump in. :slight_smile:

1 Like

Cheers mate. Probably I don’t :joy: but sometimes you know life.. if you don’t new topic here until wednesday, feel free to open it :raising_hands:

1 Like

sooo who won? :eyes: (post must be at least 20 chars) :sweat_smile:

Well, according to the latest benchmarks, in the case of small grid and BIG factor, solutions from fpsvogel and roasted_oolong are fastest. In case of a bigger initial grid, but a smaller factor, the solution from charlie is fastest, but all others (except mine and from lpogic) are almost equally efficient:

Benchmarking 10x10 grid with random numbers from 10 to 99 and zoom in factor 1000

Comparison:
fpsvogel_zoom       :   5890.2 i/s
roasted_oolong_zoom :   5794.7 i/s - same-ish: difference falls within error
charlie_zoom        :   3859.3 i/s -     1.53x  slower
eayurt_zoom         :   3668.4 i/s -     1.61x  slower
izkreny_zoom        :      0.4 i/s - 15946.97x  slower
lpogic_zoom         :      0.3 i/s - 22714.56x  slower

Benchmarking 1000x1000 grid with random numbers from 1000 to 9999 and zoom in factor 10

Comparison:
charlie_zoom        :   6.4 i/s
eayurt_zoom         :   6.3 i/s -  1.01x  slower
fpsvogel_zoom       :   6.3 i/s -  1.02x  slower
roasted_oolong_zoom :   6.3 i/s -  1.02x  slower
izkreny_zoom        :   0.3 i/s - 19.68x  slower
lpogic_zoom         :   0.3 i/s - 25.34x  slower
1 Like

I got really close to doing this with just chained methods, but couldn’t get the arrays to repeat without melding into one big array!

def zoom(arr, factor)
  result = []
  arr.each do |row|
    new_row = []
    row.each do |item|
      factor.times { new_row << item }
    end

    factor.times { result << new_row }
  end

  result
end

Not displeased with what I came up with but looking at @fpsvogel answer I can see just how painfully close I was to doing it with chained map and flat_map

2 Likes

My take

def zoom(matrix, factor)
  matrix.flat_map do |row|
    Array.new(
      factor,
      row.reduce([]) do |memo, number|
        memo.push(*Array.new(factor, number))
      end
    )
  end
end

Now that i read others’ solutions, it’s not a big news on the table, but that’s what came out :slightly_smiling_face:

4 Likes