Empty Arrays: surprising behaviour

You won't believe what happens when all the animals run away from the zoo.

26 November 2019

26 November 2019 By Tim Ash

By Tim Ash Development

In Ruby, Arrays are ordered lists of objects. For example:

array_of_integers = [1, 2, 3] array_of_strings = ["This", "That", "The Other"] array_of_all_sorts = ["fish", :bicycle, 3.14, 17] empty_array = []

There are a couple of array methods that are often useful, .any? and .all? . .any? will return true if the supplied block returns true for at least one item in the array. For example:

[1, 2, 3, 4].any? { |n| n.odd? }

will return true , while

[2, 4, 6, 8].any? { |n| n.odd? }

will return false .

Similarly, .all? will return true if the supplied block does not return false for any item in the array:

[1, 2, 3, 4].all? { |n| n < 10 }

will return true , while

[1, 2, 3, 4].all? { |n| n.even? }

will return false .

However, it may not be obvious (it wasn’t to me) what .any? and .all? return when called on an empty array.

[].any? { |n| n.even? }

will always return false , as it is not true that the supplied block returns true for at least one item in the array, because there are zero items in the array. Which brings us to the question of what is returned when we call .all? on an empty array.

[].all? { |n| n.even? }

will always return true , because the supplied block does not return false for any of the items in the array, as there are no items in an empty array. Which is logical, after a fashion, but can lead us to some odd places. Imagine we’ve got some code to keep track of the animals in a zoo - to keep things simple, let’s say these animals consist of one lion, and one zebra.

$ animals_in_the_zoo = [:lion, :zebra] => [:lion, zebra]

We’ve no unicorns in the zoo, so when we ask if all the animals are unicorns, .all? returns false :

$ animals_in_the_zoo.all? { |animal| animal == :unicorn } => false

Now let’s say the lion escapes:

$ animals_in_the_zoo.delete(:lion) # the lion escapes!

We’re left with one zebra, and no unicorns, so .all? still returns false.

$ animals_in_the_zoo.all? { |animal| animal == :unicorn } # returns false => false

Now the zebra escapes:

$ animals_in_the_zoo.delete(:zebra) # the zebra escapes, too!

But now .all? behaves in what might appear to be an unexpected way:

$ animals_in_the_zoo.all? { |animal| animal == :unicorn } => true

Now that all the animals in the zoo have escaped, all you have left are unicorns! As we saw above, this is because the supplied block does not return false for any of the items in the array (animals in the zoo), as there are no items in an empty array.

I’d love to hear your thoughts on Ruby arrays, and indeed, unicorns. Why not leave a comment below?

PS: A Book Recommendation

I’ve been asked more than once about which Ruby books I would recommend. Ruby coders of all levels will get a lot out of David Black’s The Well-Grounded Rubyist. It’s one Ruby book I keep coming back to.