How to refactor and reduce complexity - ruby

I have the following code:
#model = "ford"
#attribute = "name"
def grouped
grouped = {}
#array.each do |r|
field = r.car.send(#model)
if field.is_a? ActiveRecord::Associations::CollectionProxy
field.each do |obj|
key = obj.send(#attribute)
grouped[key] = [] unless grouped.has_key?(key)
grouped[key].push(r)
end
else
key = field.send(#attribute)
grouped[key] = [] unless grouped.has_key?(key)
grouped[key].push(r)
end
end
grouped
end
The result is:
{ford: [a, b, c]}
The codeclimate says that it has cognitive complexity.
How can I refactor this method to something cleaner?

def grouped
#array.each_with_object(Hash.new { |h, k| h[k] = [] }) do |r, grouped|
case field = r.car.send(#model)
when ActiveRecord::Associations::CollectionProxy
field.each do |obj|
grouped[obj.send(#attribute)] << r
end
else
grouped[field.send(#attribute)] << r
end
end
end

Related

Chaining methods issue [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
So I have a CSV that looks like this;
This is a test file
,,,,,,,,,,,
1122,Foo,Bar,FooBar
22321,Bar,Bar,Foo
11223,Foo,Foo,Foo,,,,,,,,,
12312/2423/1245,Foo,Foo,,,,,,,,
I want to parse it and have the following result in my array;
1122,Foo,Bar,FooBar
11223,Foo,Foo,Foo
22321,Bar,Bar,Foo
12312/2423/1245,Foo,Foo
My code;
class ReadCSVToArray
def initialize(file)
#array = CSV.read(file)
end
def compact_multi
y = []
#array.each { |i| i.compact! ; y << i unless i.blank? }
end
def item_rows
y = []
#array.each { |o|
if o[0].include? '/'; y << o ; end
if o[0].is_number? ; y << o ; end
}
end
end
the_list = ReadCSVToArray.new('/Users/davidteren/Desktop/read_test.csv')
the_list.compact_multi.item_rows.sort.each { |i| p i }
So as per above I'd like to chain several methods to get my results.
I have tried various things like;
class ReadCSVToArray
def initialize(file)
#array = CSV.read(file)
end
def compact_multi
y = []
#array.each { |i| i.compact! ; y << i unless i.blank? }
self
end
def item_rows
y = []
#array.each { |o|
if o[0].include? '/'; y << o ; end
if o[0].is_number? ; y << o ; end
}
self
end
end
No matter what I try I can't get it to work.
There's a problem with this line:
the_list.compact_multi.item_rows.sort.each { |i| p i }
This chain breaks down to 4 method calls:
the_list.compact_multi
the_list.item_rows
the_list.sort
the_list.each { |i| p i }
By returning self in your compact_multi and item_rows methods, you're ensuring that the next method in the chain is sent to your ReadCSVToArray instance. But there are no ReadCVSToArray#sort or ReadCSVToArray#each methods. You probably want to call them on instance variable #array.
result = CSV.parse 'This is a test file
,,,,,,,,,,,
1122,Foo,Bar,FooBar
22321,Bar,Bar,Foo
11223,Foo,Foo,Foo,,,,,,,,,
12312/2423/1245,Foo,Foo,,,,,,,,'
result.map(&:compact).reject { |l| l.size < 2 }
#⇒ [["1122", "Foo", "Bar", "FooBar"], ["22321", "Bar", "Bar", "Foo"],
# ["11223", "Foo", "Foo", "Foo"], ["12312/2423/1245", "Foo", "Foo"]]
Please note, that rejecting empties probably should be done more accurate. Hope it helps.
To get this:
the_list.compact_multi.item_rows.sort.each { |i| p i }
to work, compact_multi should return self and item_rows should return #array.
You seem to have two issues, the method chaining and getting the right result...
Your right result issue seems to be that you're assigning and building out this y array but you're not doing anything with it in any of those methods...
def compact_multi
y = []
#array.each { |i| i.compact! ; y << i unless i.blank? }
self
end
def item_rows
y = []
#array.each { |o|
if o[0].include? '/'; y << o ; end
if o[0].is_number? ; y << o ; end
}
self
end
what exactly is your goal? if its to maintain a result through the y array then make that an instance variable and initialize it in the initialize method...
As far as your chaining method issue if it's what I think you're trying to do which is unclear by your question then your methods should look like this...
class ReadCSVToArray
def initialize(file)
#result = []
#csv = CSV.read(file)
end
def compact_multi
#csv.each { |i| i.compact! ; #result << i unless i.blank? }
self
end
def item_rows
#csv.each { |o|
if o[0].include? '/'; #result << o ; end
if o[0].is_number? ; #result << o ; end
}
#result
end
end

Check if two linked lists are equal in Ruby?

I have the following implementation of a linked list in Ruby:
class Node
attr_accessor :data, :next
def initialize(data = nil)
#data = data
#next = nil
end
end
class LinkedList
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def iterate
return nil if #head.nil?
entry = #head
until entry.nil?
yield entry
entry = entry.next
end
end
def equal?(other_list)
#How do I check if all the data for all the elements in one list are the same in the other one?
end
end
I have tried using the .iterate like this:
def equals?(other_list)
other_list.iterate do |ol|
self.iterate do |sl|
if ol.data != sl.data
return false
end
end
end
return true
end
But this is doing a nested approach. I fail to see how to do it.
You can't do it easily with the methods you have defined currently, as there is no way to access a single next element. Also, it would be extremely useful if you implemented each instead of iterate, which then gives you the whole power of the Enumerable mixin.
class LinkedList
include Enumerable # THIS allows you to use `zip` :)
class Node # THIS because you didn't give us your Node
attr_accessor :next, :value
def initialize(value)
#value = value
#next = nil
end
end
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def each
return enum_for(__method__) unless block_given? # THIS allows block or blockless calls
return if #head.nil?
entry = #head
until entry.nil?
yield entry.value # THIS yields node values instead of nodes
entry = entry.next
end
end
def ==(other_list)
# and finally THIS - get pairs from self and other, and make sure all are equal
zip(other_list).all? { |a, b| a == b }
end
end
a = LinkedList.new([1, 2, 3])
b = LinkedList.new([1, 2, 3])
c = LinkedList.new([1, 2])
puts a == b # => true
puts a == c # => false
EDIT: I missed this on the first run through: equal? is supposed to be referential identity, i.e. two variables are equal? if they contain the reference to the same object. You should not redefine that method, even though it is possible. Rather, == is the general common-language meaning of "equal" as in "having the same value", so I changed it to that.
I think there is something wrong with your initialize method in LinkedList, regardless could this be what you need
...
def equal?(other_list)
other_index = 0
cur_index = 0
hash = Hash.new
other_list.iterate do |ol|
hash[ol.data.data] = other_index
other_index += 1
end
self.iterate do |node|
return false if hash[node.data.data] != cur_index
return false if !hash.has_key?(node.data.data)
cur_index += 1
end
return true
end
...
Assuming this is how you use your code
a = Node.new(1)
b = Node.new(2)
c = Node.new(3)
listA = [a,b,c]
aa = Node.new(1)
bb = Node.new(2)
cc = Node.new(3)
listB = [aa,bb,cc]
linkA = LinkedList.new(listA)
linkB = LinkedList.new(listB)
puts linkA.equal?(linkB)

Ruby hash of hash of hash

How can I have a hash of hash of hash?
My test returns
undefined method `[]' for nil:NilClass (NoMethodError)
Any tips?
found = Hash.new()
x = 1;
while x < 4 do
found[x] = Hash.new()
y = 1
while y < 4 do
found[x][y] = Hash.new()
found[x][y]['name1'] = 'abc1'
found[x][y]['name2'] = 'abc2'
found[x][y]['name3'] = 'abc3'
y += 1
end
x += 1
end
found.each do |k, v, y|
puts "k : #{k}"
puts " : #{v[y['name1']]}"
puts " : #{v[y['name2']]}"
puts " : #{v[y['name3']]}"
puts
end
I think you want something like this:
First of all create the data structure. You want nested hashes so you need to define default values for each hash key.
found = Hash.new do |hash,key|
hash[key] = Hash.new do |hash,key|
hash[key] = Hash.new
end
end
Run the search
(1..3).each do |x|
(1..3).each do |y|
found[x][y]['name1'] = 'abc1'
found[x][y]['name2'] = 'abc1'
found[x][y]['name3'] = 'abc1'
end
end
Then display the results
found.each do |x, y_hash|
y_hash.each do |y, name_hash|
name_hash.each do |name, value|
puts "#{x} => #{y} => #{name} => #{value}"
end
end
end
The way you build the hash seems to be functional. What probably causes the error is this loop:
found.each do |k, v, y|
Hash#each yields key/value pairs, so y will be assigned nil, thus causing the error two lines below. What you probably meant is a nested loop like
found.each do |x, h1|
h1.each do |y, h2|
puts h2['name1']
end
end
You should also be aware that you can write these kinds of counting loops more concisely in Ruby:
found = Hash.new { |h,k| h[k] = {} }
1.upto(3) do |x|
1.upto(3) do |y|
found[x][y] = {
'name1' => 'abc1',
'name2' => 'abc2',
'name3' => 'abc3',
}
end
end

Ruby Inserting Key, Value elements in Hash

I want to add elements to my Hash lists, which can have more than one value. Here is my code. I don't know how I can solve it!
class dictionary
def initialize(publisher)
#publisher=publisher
#list=Hash.new()
end
def []=(key,value)
#list << key unless #list.has_key?(key)
#list[key] = value
end
end
dic = Dictionary.new
dic["tall"] = ["long", "word-2", "word-3"]
p dic
Many thanks in advance.
regards,
koko
I think this is what you're trying to do
class Dictionary
def initialize()
#data = Hash.new { |hash, key| hash[key] = [] }
end
def [](key)
#data[key]
end
def []=(key,words)
#data[key] += [words].flatten
#data[key].uniq!
end
end
d = Dictionary.new
d['tall'] = %w(long word1 word2)
d['something'] = %w(anything foo bar)
d['more'] = 'yes'
puts d.inspect
#=> #<Dictionary:0x42d33c #data={"tall"=>["long", "word1", "word2"], "something"=>["anything", "foo", "bar"], "more"=>["yes"]}>
puts d['tall'].inspect
#=> ["long", "word1", "word2"]
Edit
Now avoids duplicate values thanks to Array#uniq!.
d = Dictionary.new
d['foo'] = %w(bar baz bof)
d['foo'] = %w(bar zim) # bar will not be added twice!
puts d.inspect
#<Dictionary:0x42d48c #data={"foo"=>["bar", "baz", "bof", "zim"]}>
Probably, you want to merge two Hashes?
my_hash = { "key1"=> value1 }
another_hash = { "key2"=> value2 }
my_hash.merge(another_hash) # => { "key1"=> value1, "key2"=> value2 }

Recursively merge multidimensional arrays, hashes and symbols

I need a chunk of Ruby code to combine an array of contents like such:
[{:dim_location=>[{:dim_city=>:dim_state}]},
:dim_marital_status,
{:dim_location=>[:dim_zip, :dim_business]}]
into:
[{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]},
:dim_marital_status]
It needs to support an arbitrary level of depth, though the depth will rarely be beyond 8 levels deep.
Revised after comment:
source = [{:dim_location=>[{:dim_city=>:dim_state}]}, :dim_marital_status, {:dim_location=>[:dim_zip, :dim_business]}]
expected = [{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]}, :dim_marital_status]
source2 = [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]
def merge_dim_locations(array)
return array unless array.is_a?(Array)
values = array.dup
dim_locations = values.select {|x| x.is_a?(Hash) && x.has_key?(:dim_location)}
old_index = values.index(dim_locations[0]) unless dim_locations.empty?
merged = dim_locations.inject({}) do |memo, obj|
values.delete(obj)
x = merge_dim_locations(obj[:dim_location])
if x.is_a?(Array)
memo[:dim_location] = (memo[:dim_location] || []) + x
else
memo[:dim_location] ||= []
memo[:dim_location] << x
end
memo
end
unless merged.empty?
values.insert(old_index, merged)
end
values
end
puts "source1:"
puts source.inspect
puts "result1:"
puts merge_dim_locations(source).inspect
puts "expected1:"
puts expected.inspect
puts "\nsource2:"
puts source2.inspect
puts "result2:"
puts merge_dim_locations(source2).inspect
I don't think there's enough detail in your question to give you a complete answer, but this might get you started:
class Hash
def recursive_merge!(other)
other.keys.each do |k|
if self[k].is_a?(Array) && other[k].is_a?(Array)
self[k] += other[k]
elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
self[k].recursive_merge!(other[k])
else
self[k] = other[k]
end
end
self
end
end

Resources