hash.key(my_key) not accessing keys in nested hash in ruby - ruby

1- I have little experience in ruby and I am coming from C/Python.
2- In the following snippet:
CSV.foreach(filename, quote_char: '"', col_sep: "\t") do |row|
if header_seen == true
k = row[primer]
c = row[counts].to_i
p = row[prfreq].to_f
e = row[err].to_f
print k," ",table.keys," ",table,"\n"
if table.key(k) == true
table[k]['counts'] << c
table[k]['err'] << e
table[k]['prfreq'] << p
else
puts "WHY IT IS ALWAYS GETTING HERE???"
table[k] = {'counts'=>[c],
'err'=>[e],
'prfreq' => [p]
}
end
elsif header_seen == false and row[0] == "BARCODE"
counts = row.index('PRCOUNT')
primer = row.index('PRIMER')
prfreq = row.index('PRFREQ')
err = row.index('ERROR')
header_seen = true
end
end
Printing table.keys (line 8), I can see the keys. However, if table.key(k) == true never comes true.
What am I doing wrong?

Assuming that table is a Hash (which we don't know for sure from your code), table.key(k) returns the value for the key k, if the key exists in your hash, or nil if it does not. Since the values of your Hash are seemingly not boolean values, the comparision with true will always be false.
If you just want to test, whether the key exists, use the method key? instead of key:
if table.key?(k)
...
end
(explicit comparision with true could be done in this case, but is redundant, so I left it out).
If your Hash has been constructed to use the standard value for it's default value (i.e. nil), you can shorten this to
if table[k]
...
end
The main difference between table.key(k) and table[k] is that if k is missing in the Hash, the former always returns nil, while the latter returns the default value for this particular Hash which you provided in the constructor.
See here for details.

It should be table.key?(k) instead of table.key(k)

Related

Conditional Boolean If statement syntax in ruby

I want the push elements of an API into my array if the condition for an attribute is true. So k_api_hash['awsaccounts'][i]['sandman'] will return true or false and, if the condition is true, I want to push the account number in to an array; (k_api_hash['awsaccounts'][i]['accountnumber']) will return an account number.
Code:
k_api_hash = JSON.parse(kens_api)
aws_account_size = k_api_hash["awsaccounts"].size
aws_account_array = []
i = 0
while i < aws_account_size
if k_api_hash['awsaccounts'][i]['sandman'] == "true"
aws_account_array.push(k_api_hash['awsaccounts'][i]['accountnumber'])
i += 1
else
i += 1
end
end
puts "Here are all the aws accounts"
puts aws_account_array.inspect
The problem is that it is returning null value, but successfully pushes the account numbers into the array if the if condition is taken out.
Output:
Here are all the aws accounts
[]
The if is returning false every time because you are comparing "true" (String) with true (TrueClass) / false (FalseClass); consider the following code:
true == "true"
#=> false
false == "true"
#=> false
true != "true"
#=> true
To make it work simply remove == "true"1 from your if:
if k_api_hash['awsaccounts'][i]['sandman']
1 Remember that any expression in an if that returns true/false (or a truthy/falsey value for that matter) needs no further comparison.
Not related to the error, but your code could be optimized by using a different iterator and removing unnecessary variables; for example, the following code will yield the same output:
k_api_hash = JSON.parse(kens_api)
aws_account_array = k_api_hash["awsaccounts"].each_with_object([]) do |account, r|
r << aws_account_array.push(account['accountnumber']) if account['sandman']
end
To understand better what is going on, take a look at Enumerable#each_with_object.
I think this would do the job:
k_api_hash = JSON.parse(kens_api)
awsaccounts = k_api_hash['awsaccounts']
aws_account_array = awsaccounts.reduce([]) { |array, acc|
array << acc['accountnumber'] if acc['sandman']
array
}
If the API is returning string booleans like "true" or "false" you could do a hash like:
bools = {true=>true, 'true'=>true}
and call it using what api returns as key inside the reduce
array << acc['accountnumber'] if bools[acc['sandman']]

Ruby Enumerable: get the first which is not nil, but the result must be what's inside the block

The Enumerable#find method works by evaluating until the it finds an element which matches the condition in the block. Is there something similar for returning the first time that the block is not evaluated to nil? imagining one would have a collection of hashes:
value = nil
options.each do |o|
break if value = o[:desired]
end
value ||= DEFAULT
isn't there a method which already accomplishes this?
No point in making a lot of transformations to the collection, i'd like to minimize the number of allocations, so any solution which allocates a new Array will not be good for me.
find method will work for finding first element which has :desired key with minimum iterations.
I think you wish to get the value of desired key from the block instead of element itself - there is no method in Enumerable that behaves like a mixture of find and map - you will have to use the outer variable to which value is assigned inside the block as shown below.
options = [{foo: 1}, {desired: 2}, {bar: 3}]
value = nil
options.find do |o|
value = o[:desired]
break if value
end
p value
#=> 2
It more or less looks like your code, which should also work just fine.
Below is one way which you can use if you want to use Enumerable methods, but it will iterate over all elements.
p value = options.map { |o| value = o[:desired] }.compact.first
You can use reduce:
value = options.reduce(nil){|memo, entry| memo || entry[:desired] } || DEFAULT
As of Ruby 2.0, this can be accomplished by combining #map and #find with lazy enumerables:
value = options.lazy.map { |o| o[:desired] }.find { |x| !x.nil? } # or find(&:present?) with ActiveSupport
value ||= DEFAULT
This came up for me today: I think we can use break with a value from reduce
treasure = [1,2,3].reduce(nil) do |memo, value|
break memo if memo
foo(value)
end
How about
options.reduce{ |_,o|
break o if o[:desired]
DEFAULT
}
or
catch do |tag|
options.each{ |_,o| o[:desired] and throw tag, o }
DEFAULT
end
The latter allows for recursion.

Check if array element data[i][j][k] exists

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? ?
I usually do:
unless (data[i][j][k] rescue false)
# do something
end
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
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
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)
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

is it OK to use ruby's implicit return value from []=

I am wandering if it is OK to use ruby's implicit return value when using []= method
[]= uses rb_hash_aset and it is returning val - http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-5B-5D-3D
here is a little code to demonstrate what I mean:
require 'benchmark'
CACHE = {}
def uncached_method(key)
warn "uncached"
rand(100)
end
def cached(key)
CACHE[key] || (CACHE[key] = uncached_method(key))
end
def longer_cached(key)
return CACHE[key] if CACHE[key]
CACHE[key] = uncached_method(key)
CACHE[key]
end
Benchmark.bm(7) do |x|
y = rand(10000)
cached(y)
x.report("shorter:") { 10000000.times do cached(y) end }
x.report("longer:") { 10000000.times do longer_cached(y) end }
end
of course longer_cached is slower because it does two hash lookups to return cached value, but when you read it line by line it makes more sense then the cached method.
I think using implicit returns is one of the things that makes ruby awesome, but I have always questioned their use when setting values.
So my question is: would you use implicit return from (hash[key] = val)?
You can also use the ||= operator in this case.
CACHE[key] ||= uncached_method(key)
This is a very common idiom.
Just because nobody mentioned it so far: you are not relying on the return value of Hash#[]=. That return value gets ignored anyway:
class ReturnFortyTwo
def []=(*)
return 42
end
end
r = ReturnFortyTwo.new
r[23] = 'This is the value that is going to be returned, not 42'
# => 'This is the value that is going to be returned, not 42'
In Ruby, assignment expressions always evaluate to the value that is being assigned. No exception. That is guaranteed by the Language Specification. So, I don't see anything wrong with relying on that.
In this case, shorter one is preferrable. Ussually = used in a subexpression is frowned upon, but here it's OK.
I'd keep it as simple and clean as possible (it's faster, too):
def cached(key)
value = CACHE[key]
unless value
value = uncached_method(key)
CACHE[key] = value
end
value
end

Ruby array equality

I have an array of arrays, called guid_pairs:
[['a','b','c'],['c','g'],['z','f','b']]
I also have an array, called array_to_check:
['c','a','b']
How can I determine if the array guid_pairs has an element that is equal to array_to_check. Equality should not consider the position of the array elements.
In this example, the check should return true because guid_pairs contains the element ['a','b','c'], which matches ['c','a','b'].
I have tried this, but it seems to always return false even when it should return true:
guid_pairs.any?{|pair| pair.eql?(array_to_check)}
I am using Ruby 1.9.2
There is a set class in the standard library and using sets nicely matches your intent:
require 'set'
a = ['c','a','b']
aa = [['a','b','c'],['c','g'],['z','f','b']]
find_this = Set.new(a)
the_match = aa.find { |x| find_this == Set.new(x) }
That will leave the matching element element of aa in the_match. If you're only interested in existence then you can simply check the truthiness of the_match; or use any? (thanks for the reminder Michael Kohl, I often forget about some of the things in Enumerable):
aa.any? { |x| find_this == Set.new(x) }
No tricks, no magic, and using Set makes it clear that you are, in fact, comparing the arrays as sets.
BTW, your attempted solution:
guid_pairs.any? { |pair| pair.eql?(array_to_check) }
doesn't work because arrays compare element-by-element in order so two arrays are equal if and only if they have equal elements in the same order. The documentation for eql? could be clearer:
Returns true if self and other are the same object, or are both arrays with the same content.
But the == documentation is nice and clear:
Two arrays are equal if they contain the same number of elements and if each element is equal to (according to Object.==) the corresponding element in the other array.
We can look to Object#eql? for some clarification though:
The eql? method returns true if obj and anObject have the same value. Used by Hash to test members for equality. For objects of class Object, eql? is synonymous with ==. Subclasses normally continue this tradition, but there are exceptions.
So == and eql? should behave the same way unless there is a good reason for them to be different.
To see if two arrays contain the same elements, regardless of order, you can use the XOR (exclusive or) operation. It will return an array which contains only elements that are in one array and not the other. If the length of the XOR is zero then the input arrays contain the same elements.
def xor(a, b)
(a | b) - (a & b)
end
guid_pairs.any? { |pair| xor(pair, array_to_check).length != 0 }
A possible solution is to sort the arrays before comparing (or even during comparing):
guid_pairs.any?{|pair| pair.sort.eql?(array_to_check.sort)}
Note that this may not be an optimal solution - it would be more appropriate to have your arrays sorted (nevertheless they are sets in your use case).
for equality of two arrays A and B i normally use:
if(((A-B) + (B-A)).blank?)
puts "equal"
else
"unequal"
end
You can use the following:
sorted_array_to_check = array_to_check.sort
guid_pairs.any?{|pair| pair.sort.eql?(sorted_array_to_check)}
Three solutions:
class Array
def check1 other; other.any?{|e| self - e == e - self} end
def check2 other; other.any?{|e| self | e == self and e | self == e} end
def check3 other; other.any?{|e| self & e == self and e & self == e} end
end
array_to_check.check1(guid_pairs) # => true
array_to_check.check2(guid_pairs) # => true
array_to_check.check3(guid_pairs) # => true
Without defining a method (following Josha's suggestion):
array_to_check.instance_eval{guid_pairs.any?{|e| self - e == e - self}} # => true
array_to_check.instance_eval{guid_pairs.any?{|e| self | e == self and e | self == e}} # => true
array_to_check.instance_eval{guid_pairs.any?{|e| self & e == self and e & self == e}} # => true

Resources