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
Related
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
I can sort a list of floats, no problem. But if I'm trying to compare floats in an object to sort objects in a list, I get this error:
`sort': comparison of HateRuby with HateRuby failed (ArgumentError)
Here's some code:
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
end
puts "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}\n\n"
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
puts "x contents:"
x.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
x.sort { |a,b| a.aFloat <=> b.aFloat }
y = x.sort
puts "y contents:"
y.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
This produces:
[path]/Ruby/rb3D54.tmp:21:in `sort': comparison of HateRuby with HateRuby failed (ArgumentError)
from [path]/Ruby/rb3D54.tmp:21:in `<main>'
1.9.3-p125
x contents:
3.30: Float
2.20: Float
1.10: Float
Complete(1)
I don't really hate Ruby, of course, but I am annoyed...
Thanks to anyone listening.
y = x.sort causing the error, as #sort compares object using the method <=>. But you didn't define the method. There is no HateRuby#<=> method in your class HateRuby.
While you would write collection_object.sort, an implicit block has been supplied, like collection_object.sort { |a,b| a <=> b }. This way sorting is being done.
Now see it is working :
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
def <=>(ob)
# just for clarity I use `self`, you can remove it. as it is implicit.
self.aFloat <=> ob.aFloat
end
end
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
x.sort
# => [#<HateRuby:0x9e73d2c #aFloat=1.1>,
# #<HateRuby:0x9e73d40 #aFloat=2.2>,
# #<HateRuby:0x9e73d54 #aFloat=3.3>]
You have to implement the method <=> from the Mixin Comparable:
include Comparable
and
def <=> (other)
You were very close. The sort method does not sort the array itself, it delivers a sorted copy. You have to assign a variable to it. This is your code with one line changed and one line gone:
class HateRuby
attr_reader :aFloat
attr_writer :aFloat
def initialize(f)
#aFloat = f
end
end
puts "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}\n\n"
x = []
x << HateRuby.new(3.3)
x << HateRuby.new(2.2)
x << HateRuby.new(1.1)
puts "x contents:"
x.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
y = x.sort { |a,b| a.aFloat <=> b.aFloat } # <== This line changed
# y = x.sort # <== This line removed
puts "y contents:"
y.each { |f| puts "#{'%.2f' % f.aFloat}: #{f.aFloat.class}" }
In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }
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
ruby
i have the following
p = 0
[s1.size,s2.size].max.times { |c| if s1[c] == s2[c]; p = c; else break; end };
matched_part = s1[0..p]
but i dont know how i can this for multiple strings (more than 2) at the same time.
Alright, how's this?
class String
def self.overlap(s1,s2,*strings)
strings += [s2]
strings.min { |s| s.size }.size.times do |n|
return s1[0,n] unless strings.all? { |string| s1[n]==string[n] }
end
s1
end
end
class String
def self.overlap(first,second,*others)
s1 = first
others = [second] + others
others.each do |s2|
p = 0
s1.length.times { |c| if s1[c] == s2[c] then p = c else break end }
s1 = s1[0..p]
end
s1
end
end
puts String.overlap "marry had a little lamb", "marry had a bug dog", "marry had a cat", "marry had a bird OUT:", "marry had something" #=> marry had
In one line:
strings[0].slice(0,(0...strings[0].size).find {|i| strings.map {|s| s[i..i]}.uniq.size > 1})