0

I'm writing a radix sort implementation in Ruby as a self-teaching exercise, and something very odd is happening here. letters ought to be a 2D array of buckets, one for each letter of the alphabet (0 for space/nil, 1-26 for letters). For some reason, this code is not inserting my word at the one index of letters, however. It's inserting it at every index. There also seems to be some sort of infinite loop which prevents it from terminating, which is also odd.

What am I doing wrong? Here is my code.

def radix_sort(words)
    letters = Array.new(27, [])
    offset = 'a'.ord
    max_length = words.max_by { |word| word.length }.length

    (max_length-1).downto(0) do |i|
        words.each do |word|
            if word[i] != nil then 
                index = word[i].downcase.ord - offset
                letters[index + 1] << word
            else
                letters[0] << word
            end
        end
        words = letters.flatten
        letters = letters.map { |bucket| bucket.clear }
    end

    words
end

w = ["cat", "dog", "boar", "Fish", "antelope", "moose"]

p radix_sort(w)
mu is too short
  • 426,620
  • 70
  • 833
  • 800
Eric Dand
  • 1,106
  • 13
  • 37
  • Can you explain what *precisely* is unclear to you about the documentation of `Array::new`? That way, the Ruby developers can improve the documentation so that future developers don't fall into the same trap as you did. Please, help making the world a better place! – Jörg W Mittag May 29 '19 at 07:55

1 Answers1

3

First of all, there are no 2D arrays, just arrays-of-arrays. Secondly, this:

default_array = [ ]
letters       = Array.new(27, default_array)

simply copies the default_array reference to every one of the 27 automatically created values so letters looks like this:

letters = [
  default_array,
  default_array,
  ...
]

Have a look at letters[0].object_id and letters[1].object_id and you'll see that they're exactly the same. The fine manual even says as much:

new(size=0, obj=nil)
new(array)
new(size) {|index| block }

Returns a new array.

[...] When a size and an optional obj are sent, an array is created with size copies of obj. Take notice that all elements will reference the same object obj.

Emphasis mine in the last sentence.

However, if you say:

letters = Array.new(27) { [ ] }

then the block will be executed once for each of the 27 initial values and each execution of the block will create a brand new array. Check letters[0].object_id and letters[1].object_id you'll see that they really are different.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Perfect! I didn't know about the whole `Array.new(num) { block }` thing. Would I be correct in assuming I can put whatever amount of logic in the block I'd like, and its return value will be assigned to each entry of the array? – Eric Dand Dec 06 '13 at 05:23
  • Yes, you can put whatever you want in the block and the block can even look at the `index` being used. – mu is too short Dec 06 '13 at 05:29
  • 1
    just ran into this, and this answer helped me out. Thanks! – Atsushi Jul 29 '20 at 16:20