I write a code that I increment i, 4 times and I added all e's to an array. It works fine if I print the array without using any methods.(Output is: [1,2,3,4]) But if I use to_s method the output turns into"[ ]\x01\x02\x03\x04" which I understand, it probably counts e. But I want my output be : 1,2,3,4 and I don't have any idea how to do this.
So my simplified code looks like this:
array = [].to_s
4.times do |e|
e = e + 1
array << e
end
p array
How can I get the output = 1,2,3,4?
An easy/simple way to do this I think is as below:
array = []
4.times do |e|
e = e + 1
array << e
end
p array.join(',')
output: "1,2,3,4"
The above will print all the items separated by a comma, also this might be helpful join docs
array = [].to_s puts the string '[]' in the variable array. From that point on, the variable is named "array" but its value is not an array at all.
Change the first line to read array = [] then use Array#join after the loop to concatenate the array values into a string using , as separator:
array = []
4.times do |e|
array << e + 1
end
p array.join ','
Related
If I have a string like this
str =<<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
If a number in the first value shows up again, I want to add their second values together. So the final string would look like this
7312357006,1246.221
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
If the final output is an array that's fine too.
There are lots of ways to do this in Ruby. One particularly terse way is to use String#scan:
str = <<END
7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1
END
data = Hash.new(0)
str.scan(/(\d+),([\d.]+)/) {|k,v| data[k] += v.to_f }
p data
# => { "7312357006" => 1246.221,
# "3214058234" => 3499.2,
# "1324958723" => 232.1,
# "3214173443" => 234.1,
# "6134513494" => 23.2 }
This uses the regular expression /(\d+),([\d.]+)/ to extract the two values from each line. The block is called with each pair as arguments, which are then merged into the hash.
This could also be written as a single expression using each_with_object:
data = str.scan(/(\d+),([\d.]+)/)
.each_with_object(Hash.new(0)) {|(k,v), hsh| hsh[k] += v.to_f }
# => (same as above)
There are likewise many ways to print the result, but here are a couple I like:
puts data.map {|kv| kv.join(",") }.join("\n")
# => 7312357006,1246.221
# 3214058234,3499.2
# 1324958723,232.1
# 3214173443,234.1
# 6134513494,23.2
# or:
puts data.map {|k,v| "#{k},#{v}\n" }.join
# => (same as above)
You can see all of these in action on repl.it.
Edit: Although I don't recommend either of these for the sake of readability, here's more just for kicks (requires Ruby 2.4+):
data = str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.transform_values {|a| a.sum(&:to_f) }
...or, to going straight to a string:
puts str.lines.group_by {|s| s.slice!(/(\d+),/); $1 }
.map {|k,vs| "#{k},#{vs.sum(&:to_f)}\n" }.join
Since repl.it is stuck on Ruby 2.3: Try it online!
You could achieve this using each_with_object, as below:
str = "7312357006,1.121
3214058234,3456
7312357006,1234
1324958723,232.1
3214058234,43.2
3214173443,234.1
6134513494,23.2
7312357006,11.1"
# convert the string into nested pairs of floats
# to briefly summarise the steps: split entries by newline, strip whitespace, split by comma, convert to floats
arr = str.split("\n").map(&:strip).map { |el| el.split(",").map(&:to_f) }
result = arr.each_with_object(Hash.new(0)) do |el, hash|
hash[el.first] += el.last
end
# => {7312357006.0=>1246.221, 3214058234.0=>3499.2, 1324958723.0=>232.1, 3214173443.0=>234.1, 6134513494.0=>23.2}
# You can then call `to_a` on result if you want:
result.to_a
# => [[7312357006.0, 1246.221], [3214058234.0, 3499.2], [1324958723.0, 232.1], [3214173443.0, 234.1], [6134513494.0, 23.2]]
each_with_object iterates through each pair of data, providing them with access to an accumulator (in this the hash). By following this approach, we can add each entry to the hash, and add together the totals if they appear more than once.
Hope that helps - let me know if you've any questions.
def combine(str)
str.each_line.with_object(Hash.new(0)) do |s,h|
k,v = s.split(',')
h.update(k=>v.to_f) { |k,o,n| o+n }
end.reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair }
end
puts combine str
7312357006,1246.22
3214058234,3499.2
1324958723,232.1
3214173443,234.1
6134513494,23.2
Notes:
using String#each_line is preferable to str.split("\n") as the former returns an enumerator whereas the latter returns a temporary array. Each element generated by the enumerator is line of str that (unlike the elements of str.split("\n")) ends with a newline character, but that is of no concern.
see Hash::new, specifically when a default value (here 0) is used. If a hash has been defined h = Hash.new(0) and h does not have a key k, h[k] returns the default value, zero (h is not changed). When Ruby encounters the expression h[k] += 1, the first thing she does is expand it to h[k] = h[k] + 1. If h has been defined with a default value of zero, and h does not have a key k, h[k] on the right of the equality (syntactic sugar1 for h.[](k)) returns zero.
see Hash#update (aka merge!). h.update(k=>v.to_f) is syntactic sugar for h.update({ k=>v.to_f })
see Kernel#sprint for explanations of the formatting directives %s and %g.
the receiver for the expression reduce('') { |s,kv_pair| s << "%s,%g\n" % kv_pair } (in the penultimate line), is the following hash.
{"7312357006"=>1246.221, "3214058234"=>3499.2, "1324958723"=>232.1,
"3214173443"=>234.1, "6134513494"=>23.2}
1 Syntactic sugar is a shortcut allowed by Ruby.
Implemented this solution as hash was giving me issues:
d = []
s.split("\n").each do |line|
x = 0
q = 0
dup = false
line.split(",").each do |data|
if x == 0 and d.include? data then dup = true ; q = d.index(data) elsif x == 0 then d << data end
if x == 1 and dup == false then d << data end
if x == 1 and dup == true then d[q+1] = "#{'%.2f' % (d[q+1].to_f + data.to_f).to_s}" end
if x == 2 and dup == false then d << data end
x += 1
end
end
x = 0
s = ""
d.each do |val|
if x == 0 then s << "#{val}," end
if x == 1 then s << "#{val}\n ; x = 0" end
x += 1
end
puts(s)
What I expect my code to do:
print out
{ "pivot0"=>5, "left0"=>[5,0,1,2,3,4], "right0"=>[6,7,8,9] }
What my code does: print out { "pivot0"=>5}
I've used print to try to debug. Both hash["left0"] and hash["right0"] return [0,1,2,3,4,6,7,8,9]. Yet neither keys are showing up at all inside hash. And even if they did show up, they don't have the right numbers in them. What am I not understanding? As you can tell by the title, I don't even know what is causing the problem. Trying to write similar code with any one of those factors removed (if, hash, block, or push) seems to give me expected results, so I'm thoroughly confused.
source = [5,0,1,2,3,4,6,7,8,9]
hash = Hash.new([])
l=0
hash['pivot' + l.to_s] = source[0]
source.each_with_index do |e, i|
if i > 0
if e <= hash['pivot' + l.to_s]
puts "hit left on #{e}"
hash['left'+l.to_s] << e
else
puts "hit right on #{e}"
hash['right'+l.to_s] << e
end
end
end
print hash
Problem is the way you are constructing your hash object, and assuming that default value of empty array will be automatically assigned to a key on first access.
As the documentation mentions,
If obj is specified, this single object will be used for all default
values
In your case, the statement like below
hash['left'+l.to_s] << e
is effectively below code:
an_array = hash['left'+l.to_s]
an_array << e
This is not what you might have intended. You need to explicitly update the key's value in hash.
As you update a temporary array, its value is never inserted into hash.
You should be doing:
hash['left'+l.to_s] = hash['left'+l.to_s] << e
Or Alternatively, you can use constructor like below and you should get desired output with rest of code unchanged.
hash = Hash.new {|hash, key| hash[key] = [] }
Here, whenever a key is accessed for first time and it has no value associated with it, then the block {|hash, key| hash[key] = [] } will be executed - which basically assign an empty array value for that key.
Or you could use simple hash initialiser {} and handle nil values, as shown below:
source = [5,0,1,2,3,4,6,7,8,9]
hash = {}
l = 0
hash['pivot' + l.to_s] = source[0]
source.each_with_index do |e, i|
if i > 0
if e <= hash['pivot' + l.to_s]
hash['left'+l.to_s] = (hash['left'+l.to_s] || []) << e
else
hash['right'+l.to_s] = (hash['right'+l.to_s] || []) << e
end
end
end
print hash
#=> {"pivot0"=>5, "left0"=>[0, 1, 2, 3, 4], "right0"=>[6, 7, 8, 9]}
Expression like (hash['left'+l.to_s] || []) returns first operand if its not nil, else returns the value of second operand, which in this case happens to be an empty array.
Just out of curiosity:
src = [5,0,1,2,3,4,6,7,8,9]
%i(pivot left right).zip(
[pivot = src.first, *src.partition { |e| e <= pivot }]
).to_h
#⇒ "{:pivot=>5, :left=>[5, 0, 1, 2, 3, 4], :right=>[6, 7, 8, 9]}"
I'm trying to solve this exercise from Ruby Monk website, which says:
Try implementing a method called occurrences that accepts a string
argument and uses inject to build a Hash. The keys of this hash should
be unique words from that string. The value of those keys should be
the number of times this word appears in that string.
I've tried to do it like this:
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
But I always get this error:
TypeError: no implicit conversion of String into Integer
Meanwhile, the solution for this one is quite the same (I think):
def occurrences(str)
str.scan(/\w+/).inject(Hash.new(0)) do |build, word|
build[word.downcase] +=1
build
end
end
Okay so your issue is that you are not returning the correct object from the block. (In your case a Hash)
#inject works like this
[a,b]
^ -> evaluate block
| |
-------return-------- V
In your solution this is what is happening
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1
#=> 1
#second pass uses the result from the first as `a` so `a` is now an integer (1).
#So instead of calling Hash#[] it is actually calling FixNum#[]
#which requires an integer as this is a BitReference in FixNum.Thus the `TypeError`
Simple fix
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1; a }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1; a
#=> {"word" => 1}
Now the block returns the Hash to be passed to a again. As you can see the solution returns the object build at the end of the block thus the solution works.
I have array which I read from excel (using ParseExcel) using the following code:
workbook = Spreadsheet::ParseExcel.parse("test.xls")
rows = workbook.worksheet(1).map() { |r| r }.compact
grid = rows.map() { |r| r.map() { |c| c.to_s('latin1') unless c.nil?}.compact rescue nil }
grid.sort_by { |k| k[2]}
test.xls has lots of rows and 6 columns. The code above sort by column 3.
I would like to output rows in array "grid" to many text file like this:
- After sorting, I want to print out all the rows where column 3 have the same value into one file and so on for a different file for other same value in column3.
Hope I explain this right. Thanks for any help/tips.
ps.
I search through most posting on this site but could not find any solution.
instead of using your above code, I made a test 100-row array, each row containing a 6-element array.
You pass in the array, and the column number you want matched, and this method prints into separate files rows that have the same nth element.
Since I used integers, I used the nth element of each row as the filename. You could use a counter, or the md5 of the element, or something like that, if your nth element does not make a good filename.
a = []
100.times do
b = []
6.times do
b.push rand(10)
end
a.push(b)
end
def print_files(a, column)
h = Hash.new
a.each do |element|
h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])
end
h.each do |k, v|
File.open("output/" + k.to_s, 'w') do |f|
v.each do |line|
f.puts line.join(", ")
end
end
end
end
print_files(a, 2)
Here is the same code using blocks instead of do .. end:
a = Array.new
100.times{b = Array.new;6.times{b.push rand(10)};a.push(b)}
def print_files(a, column)
h = Hash.new
a.each{|element| h[element[2]] ? (h[element[column]] = h[element[column]].push(element)) : (h[element[column]] = [element])}
h.map{|k, v| File.open("output/" + k.to_s, 'w'){|f| v.map{|line| f.puts line.join(", ")}}}
end
print_files(a, 2)
This is probably easy to do! I'm not able envision the loop yet, I was thinking about a nested for loop but not quite sure how to alternate between the two hashes.
Lets say I have a class with a def that containts two hash tables:
class Teststuff
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array=[0,1,2,2] #this represents user input stored in an array valid to 4 elements
array_size = my_array.size #this represents the size of the user inputed array
element_indexer = my_array.size # parellel assignment so I can use same array for array in dex
array_start_index = element_indexer-1 #give me the ability later to get start at index zero for my array
#for loop?? downto upto??
# trying to get loop to grab the first number "0" in element position "0", grab the hash values then
# the loop repeats and grabs the second number "1" in element position "1" grab the hash values
# the loop repeats and grabs the third number "2" in elements position "2" grab the hash values
# the final iteration grabs the fourth number "2" in elements position "3" grab the hash values
# all this gets returned when called. Out put from puts statement after grabing hash values
# is: **A one B two C three C four**
return a_string
end
end
How do I go about returning string output to the screen like this:
**A one B two C three C four**
or simply letter position letter position...
Thanks for the help, put code up so I can try on my editor!
I think I figured out what it is you want, although I still have no idea what array_size, element_indexer, array_start_index and TestStuff are for.
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array = [0, 1, 2, 2]
"**#{my_array.map.with_index {|e, i|
"#{letters[e.to_s].strip} #{position[(i+1).to_s].strip}"
}.join(' ')}**"
end
[I took the liberty of reformatting your code to standard Ruby coding style.]
However, everything would be much simpler, if there weren't all those type conversions, and all those superfluous spaces. Also, the method would be much more useful, if it actually had a way to return different results, instead of always returning the same thing, because at the moment, it is actually exactly equivalent to
def test_stuff
'**A one B two C three C four**'
end
Something along these lines would make much more sense:
def test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
If you don't want to pollute the Object namespace with your method, you could do something like this:
def (TestStuff = Object.new).test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
TestStuff.test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
You can use enumerators, like this:
l = letters.to_enum
p = position.to_enum
a_string = ''
loop do
a_string << l.next[1] << p.next[1]
end
How about :
a_string = ""
my_array.each_with_index { |x, index|
a_string += letters[my_array[index].to_s] + " " + (position.include?((index+1).to_s) ? position[(index+1).to_s] : "nil")
}