3

I need to find out if data[i][j][k] element exists, but I don't know if data[i] or data[i][j] not nil themselves.
If I just data[i][j][k].nil?, it throw undefined method [] for nil:NilClass if data[i] or data[i][j] is nil

So, I am using now

unless data[i].nil? or data[i][j].nil? or data[i][j][k].nil?
    # do something with data[i][j][k]
end

But it is somehow cumbersome.

Is there any other way to check if data[i][j][k] exists without data[i].nil? or data[i][j].nil? or data[i][j][k].nil? ?

Qiao
  • 16,565
  • 29
  • 90
  • 117
  • Use the `defined?` keyword. http://stackoverflow.com/questions/288715/checking-if-a-variable-is-defined-in-ruby – jgburet Jan 05 '14 at 17:28
  • @jigz: No. `defined?` can tell you if a method like `[]` exists but cannot predict whether it will return `nil` or not. Example: `a=[]; defined?(a[2]) #=> "method"` – David Grayson Jan 05 '14 at 19:48
  • If you are not averse to using a gem, you can use my `try_to` gem and do `try_to { a[i][j][k] }` which will return the value or `nil`. You can even specify a default value, like `try_to(42) { a[i][j][k] }` which will return the value or 42. http://rubygems.org/gems/try_to – Michael Kohl Jan 05 '14 at 20:08

6 Answers6

3

I usually do:

unless (data[i][j][k] rescue false)
    # do something
end
pguardiario
  • 53,827
  • 19
  • 119
  • 159
2

You can shorten slightly to

if data[i] && data[i][j] && data[i][j][k]
  # do something with data[i][j][k]
end

You can also you the "andand" gem which allows you to write:

data[i].andand[j].andand[k]

If you are willing to monkey patch Array, you could define a method to enable this, such as:

class Array
  def index_series(*args)
    result = self
    args.each do |key|
      result = result[key]
      return nil if result.nil?
    end
    result
  end
end

which would let you do:

data.index_series(i, j, k)
Peter Alfvin
  • 28,599
  • 8
  • 68
  • 106
2

Here are three different alternatives:

Shorten it

You can shorten it slightly by using "!" instead of .nil?:

!data[i] or !data[i][j] or !data[i][j][k]

You could get rid of the repetition by doing this:

((data[i] || [])[j] || [])[k].nil?

Abstract away these details

Both of the code snippets above are nasty enough that I would probably not write them more than once in a code base.

A three-dimensional array seems complicated enough that you shouldn't be accessing it directly in lots of places in your code. You should consider wrapping it inside an object with an appropriate name:

class My3DWorld
  def initialize
    # set up @data
  end

  # Gets the point or returns nil if it doesn't exist.
  def get_point(i, j, k)
    @data[i] && @data[i][j] && @data[i][j][k]
  end
end

Use a hash instead

However, ultimately, I wonder whether you really need a 3D array. Another more Ruby-like way to implement this data structure would be to use a hash and use i,j,k coordinate tuples as the keys. Unless this is a huge structure and you need the performance characteristics of a 3D array, I recommend looking at my other answer here:

https://stackoverflow.com/a/20600345/28128

Community
  • 1
  • 1
David Grayson
  • 84,103
  • 24
  • 152
  • 189
2

The new feature "refinements" is an option:

module ResponsiveNil
  refine NilClass do
    def [](obj)
      nil 
    end
  end
end

using ResponsiveNil
a = [[1]]
p a[2][3][4]  #=> nil
steenslag
  • 79,051
  • 16
  • 138
  • 171
0

The following permits any amount of nesting, and allows for the possibility that an element of the array has a value of nil:

def element_exists?(arr, *indices)
  if arr.is_a? Array
    return true if indices.empty?
    return false if arr.size <= (i = indices.pop)
    element_exists?(arr[i], *indices)
  else
    indices.empty?
  end
end          

data = [[0,1],[2,nil]]

element_exists?(data)          # => true 
element_exists?(data, 1)       # => true 
element_exists?(data, 2)       # => false 
element_exists?(data, 1, 1)    # => true 
element_exists?(data, 1, 2)    # => false 
element_exists?(data, 1, 1, 1) # => false 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

You can use Array#dig:

item = data.dig(i, j, k)

It will return nil if one of the indices is invalid.

anna328p
  • 129
  • 2
  • 12