How to retrieve values within an array - ruby

def self.foo
[
["a","aa"],
["b","bb"],
]
end
Given "a", I should be able to retrieve "aa"
Given "bb", I should be able to retrieve "b"
How do I do this?

assoc and rassoc are your friends:
ar = [
["a","aa"],
["b","bb"],
]
p ar.assoc("a").last #=> "aa"
p ar.rassoc("bb").first #=> "b"

Hash[self.foo].invert["bb"] #=> "b"
Hash[self.foo]["a"] #=> "aa"
Hash[] turns array into hash
Hash#invert inverts the hash so all values map to the keys
If you want to do both:
Hash[self.foo]["bb"] or Hash[self.foo].invert["bb"] #=> "b"

I would create my own "bimap" implementation, perhaps something like:
class Bimap < Hash
alias :__put__ :[]=
def []=(key,value)
__put__(key,value)
__put__(value,key)
end
alias :__size__ :size
def size
__size__ / 2
end
# ...any other Hash methods to reimplement?
end

Related

How to generate the expected output by using split method used in my code?

Question:
Create a method for Array that returns a hash having 'key' as length of the element and value as an array of all the elements of that length. Make use of Array#each.
Returned Hash should be sorted by key.
I have tried to do it through Hash sorting over length. I have almost resolved it using another method but I want to use split and hash to achieve expected output.
Can anyone suggest any amendments in my code below?
Input argument:
array-hash.rb "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
Expected output:
{1=>["x", "5"], 3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"]}
class String
def key_length(v2)
hash = {}
v2.each do |item|
item_length = item.to_s.length
hash[item_length] ||= []
hash[item_length].push(item)
end
Hash[hash.sort]
end
end
reader = ''
if ARGV.empty?
puts 'Please provide an input'
else
v1 = ARGV[0]
v2 = v1.tr("'[]''",'').split
p reader.key_length(v2)
end
Actual output:
{35=>["abc,def,1234,234,abcd,x,mnop,5,zZzZ"]}
Given the array (converted from string, note integers as string between ""):
ary = str[1..-2].delete('\'').split(',')
ary #=> ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
The most "idiomatic" way should be using group_by:
ary.group_by(&:size)
If you want to use each, then you could use Enumerable#each_with_object, where the object is an Hash#new with an empty array as default:
ary.each_with_object(Hash.new{ |h,k| h[k] = []}) { |e, h| h[e.size] << e }
Which is the same as
res = Hash.new{ |h,k| h[k] = []}
ary.each { |e| res[e.size] << e }
Not sure why you need to monkeypatch* array here, is this a school exercise or something?
I think your bug is you need to pass in the comma delimiter arg to split.
I would solve the underlying problem as a reduce/inject/fold thing, myself.
s = "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
splits = s.tr("'[]''",'').split(',') # need to pass in the comma for the split
Hash[splits.inject({}) { |memo,s| memo[s.length] ||= []; memo[s.length] << s; memo }.sort] # doesn't use Array.each but?
{1=>["x", "5"], 3=>["def", "234"], 4=>["1234", "abcd", "mnop"],
5=>["['abc"], 6=>["zZzZ']"]}

remove the duplicates in an array of object

How to remove the duplicate objects in an array of objects.
for ex:
[#< #a:1, #b:2>, #< #a:3, #b:3>, #<#a:3, #b:3>] => [ #< #a:1, #b:2>, #< #a:3, #b:3>].
Also my understanding is no two objects can be same..correct me if i am wrong.
You can use Array#uniq, but you'll need to make a small change to your class. Per the docs, uniq "compares values using their hash and eql? methods for efficiency," so you'll need to define hash and eql? such that they will identify two objects of your class as duplicates. For example, if two objects with the same a and b attributes are duplicates, you might do this:
class Foo
attr_reader :a, :b
def initialize(a, b)
#a, #b = a, b
end
def hash
[ a, b ].hash
end
def eql?(other)
hash == other.hash
end
end
arr = [ Foo.new(1, 2), Foo.new(3, 3), Foo.new(3, 3) ]
p arr.uniq
# => [#<Foo:0x007f686ac36700 #a=1, #b=2>, #<Foo:0x007f686ac366b0 #a=3, #b=3>]
Alternatively, if you don't want to or can't define hash and eql? methods you can use the block form of uniq:
class Bar
attr_reader :a, :b
def initialize(a, b)
#a, #b = a, b
end
end
arr2 = [ Bar.new(1, 2), Bar.new(3, 3), Foo.new(3, 3) ]
p arr2.uniq {|obj| [ obj.a, obj.b ] }
# => [#<Bar:0x007fe80f7b6750 #a=1, #b=2>, #<Bar:0x007fe80f7b6700 #a=3, #b=3>]
You can see both of these on repl.it.
Use Array#uniq. Example from the docs:
a = [ "a", "a", "b", "b", "c" ]
a.uniq
# => ["a", "b", "c"]
Note that this method uses their hash or eql? methods to identify duplicates. That said: If uniq doesn't work like expected, then you probably make sure that hash or eql? are implemented appropriately.

Sort Ruby Hash by order in array of keys

I have a hash:
sample = { bar: 200, foo: 100, baz: 100 }
How do I sort sample using the order of keys in sort_order:
sort_order = [:foo, :bar, :baz, :qux, :quux]
Expected result:
sample #=> { foo: 100, bar: 200, baz: 100 }
All I can come up with is
new_hash = {}
sort_order.each{|k| new_hash[k] = sample[k] unless sample[k].nil? }
sample = new_hash
There's got to be a better way. Hints?
Keys without values should not be present, i.e. number of keys remain the same, which isn't the case with Sort Hash Keys based on order of same keys in array
A functional approach using the intersection of keys:
new_sample = (sort_order & sample.keys).map { |k| [k, sample[k]] }.to_h
#=> {:foo=>100, :bar=>200, :baz=>100}
As #Stefan noted, the abstraction Hash#slice from ActiveSupport's pretty much does the job:
require 'active_support/core_ext/hash'
new_sample = sample.slice(*sort_order)
#=> {:foo=>100, :bar=>200, :baz=>100}
Please, see my this answer:
sort_order = [:foo, :bar, :baz, :qux, :quux, :corge, :grault,
:garply, :waldo, :fred, :plugh, :xyzzy, :thud]
sample = { bar: 200, foo: 100, baz: 100 }
sample.sort_by {|k, _| sort_order.index(k)}.to_h
=> {:foo=>100, :bar=>200, :baz=>100}
The code below does this. Note that I used has_key? because you want the output hash to contain all the keys in the input hash, even if their values are nil.
#!/usr/bin/env ruby
def sorted_hash(input_hash, key_sort_order)
new_hash = {}
key_sort_order.each do |key|
if input_hash.has_key?(key)
new_hash[key] = input_hash[key]
end
end
new_hash
end
sort_order = [:foo, :bar, :baz, :qux, :quux]
sample = { bar: 200, foo: 100, baz: 100 }
puts sorted_hash(sample, sort_order)
# Outputs: {:foo=>100, :bar=>200, :baz=>100}
A simplification is to use each_with_object:
def sorted_hash_two(input_hash, key_sort_order)
key_sort_order.each_with_object({}) do |key, result_hash|
if input_hash.has_key?(key)
result_hash[key] = input_hash[key]
end
end
end
puts sorted_hash_two(sample, sort_order)
# Outputs: {:foo=>100, :bar=>200, :baz=>100}
I like #tokland's idea of array intersection (&) better because it elmiinates the need for an if condition:
def sorted_hash_ewo_intersection(input_hash, key_sort_order)
(key_sort_order & input_hash.keys).each_with_object({}) do |key, result_hash|
result_hash[key] = input_hash[key]
end
end # produces: {:foo=>100, :bar=>200, :baz=>100}
Here is one more way this can be done:
(sort_order & sample.keys).zip([nil]).to_h.merge(sample)
#=> {:foo=>100, :bar=>200, :baz=>100}
Explanation:
First we create a hash that contains only desired keys in the right order.
(sort_order & sample.keys).zip([nil]).to_h
#=> {:foo=>nil, :bar=>nil, :baz=>nil}
And then, we merge this hash with sample to get the values from sample.

Array in value of hash

How to push inputs into a value of a hash? My problem is that I got multiple keys and all of them reference arrays.
{"A"=>["C"], "B"=>["E"], "C"=>["D"], "D"=>["B"]}
How can I push another String onto one of these? For example I want to add a "Z" to the array of key "A"?
Currently I either overwrite the former array or all data is in one.
Its about converting a Array ["AB3", "DC2", "FG4", "AC1", "AF4"] into a hash with {"A"=>["B", "C", "F"]}.
Any command <<, push, unshift will do a job
if h["A"]
h["A"] << "Z"
else
h["A"] = ["Z"]
end
You said your original problem is converting the array ["AB3", "DC2", "FG4", "AC1", "AF4"] into the hash {"A"=>["B", "C", "F"]}, which can be done like this:
Hash[a.group_by { |s| s[0] }.map { |k, v| [k, v.map { |s| s[1] }] }]
Or like this:
a.inject(Hash.new{|h, k| h[k]=[]}) { |h, s| h[s[0]] << s[1] ; h }
Note that Hash.new{|h, k| h[k]=[]} creates an array with a default value of [] (an empty array), so you'll always be able to use << to add elements to it.
Better approach:
Add a new class method in Hash as below:
class Hash
def add (k,v)
unless self.key?k
self[k] = [v]
else
self[k] = self[k] << v
end
self
end
end
h={}
h.add('A','B') #=> {"A"=>["B"]}
h.add('A','C') #=> {"A"=>["B", "C"]}
h.add('B','X') #=> {"A"=>["B", "C"], "B"=>["X"]}
Done.
This can be even more idiomatic according to your precise problem; say, you want to send multiple values at once, then code can be DRY-ed to handle multiple arguments.
Hope this helps.
All the best.

How to uniq an array case insensitive

As far as i know, the result of
["a", "A"].uniq
is
["a", "A"]
My question is:
How do I make ["a", "A"].uniq give me either ["a"] or ["A"]
There is another way you can do this. You can actually pass a block to uniq or uniq! that can be used to evaluate each element.
["A", "a"].uniq { |elem| elem.downcase } #=> ["A"]
or
["A", "a"].uniq { |elem| elem.upcase } #=> ["A"]
In this case though, everything will be case insensitive so it will always return the array ["A"]
Just make the case consistent first.
e.g:
["a","A"].map{|i| i.downcase}.uniq
Edit: If as mikej suggests, the elements returned must be exactly the same as in the original array, then this will do that for you:
a.inject([]) { |result,h| result << h unless result.map{|i| i.downcase}.include?(h.downcase); result }
Edit2 Solution which should satisfy mikej :-)
downcased = []
a.inject([]) { |result,h|
unless downcased.include?(h.downcase);
result << h
downcased << h.downcase
end;
result}
you may build a mapping (Hash) between the case-normalized (e.g. downcased) values and the actual value and then take just the values from the hash:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element ; h }\
.values
selects the last occurrence of a given word (case insensitive):
["A", "b", "C"]
if you want the first occurrence:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element unless h[element.downcase] ; h }\
.values
["a", "A"].map{|x| x.downcase}.uniq
=> ["a"]
or
["a", "A"].map{|x| x.upcase}.uniq
=> ["A"]
If you are using ActiveSupport, you can use uniq_by.
It doesn't affect the case of the final output.
['A','a'].uniq_by(&:downcase) # => ['A']
A bit more efficient and way is to make use of uniq keys in hashes, so check this:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] = j; hash}.values
will return the last element, in this case
["A"]
whereas using ||= as assign operator:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] ||= j; hash}.values
will return first element, in this case
["a"]
especially for big Arrays this should be faster as we don't search the array each time using include?
cheers...
A more general solution (though not the most efficient):
class EqualityWrapper
attr_reader :obj
def initialize(obj, eq, hash)
#obj = obj
#eq = eq
#hash = hash
end
def ==(other)
#eq[#obj, other.obj]
end
alias :eql? :==
def hash
#hash[#obj]
end
end
class Array
def uniq_by(eq, hash = lambda{|x| 0 })
map {|x| EqualityWrapper.new(x, eq, hash) }.
uniq.
map {|x| x.obj }
end
def uniq_ci
eq = lambda{|x, y| x.casecmp(y) == 0 }
hash = lambda{|x| x.downcase.hash }
uniq_by(eq, hash)
end
end
The uniq_by method takes a lambda that checks the equality, and a lambda that returns a hash, and removes duplicate objects as defined by those data.
Implemented on top of that, the uniq_ci method removes string duplicates using case insensitive comparisons.

Resources