Array member becomes the same after push in my Ruby code - ruby

In my Ruby code, a long hex string is parsed and pushed into a data array (avl_data_array.push(avl_data)). After pushing, when I use puts to display the array, all the elements are the same...
The code is:
data_hex_string = '03D8CAFE01D0000F383630323634303535353134323532081100000180BB016382005B29ED2AEF935C0D00000000000000000C0616004701F0001502C800EF000643273A440000B50012B60010422F2B180000000000000180BACA745E005B29ED2AEF935C0D003000000B0000000C0616004703F0001503C800EF00064326A2440000B5000CB60008422F41180000000000000180BA938536005B29ED2AEF935C0D00270000090000000C0616004703F0001503C800EF0006432705440000B5000CB60008422F19180000000000000180BA5C960E005B29ED2AEF935C0D003200000B0000000C0616004703F0001504C800EF00064328A4440033B5000BB60008422EEC180000000000000180BA1F8371005B29ED2AEF935C0D000000000000000008041600F000C801EF00044326B7440000422F73180000000000000180B9E89182005B29ED2AEF935C0D000000000000000008041600F000C801EF000443268B440000422F30180000000000000180B9B19F93005B29ED2AEF935C0D000000000000000008041600F000C801EF0004432690440000422EED180000000000000180B97AB11B005B29ED2AEF935C0D00000000000000000C0616004703F0001503C801EF0006432672440000B50000B60000422F31180000000000000180B943C385005B29ED2AEF935C0D00000000000000000C0616004703F0001503C801EF0006432681440000B50000B60000422EDA180000000000000180B90CD40F005B29ED2AEF935C0D00000000000000000C0616004703F0001504C801EF0006432681440000B50000B60000422F26180000000000000180B8D5E778005B29ED2AEF935C0D00000000000000000C0616004703F0001504C801EF0006432677440000B50000B60000422F30180000000000000180B89EF589005B29ED2AEF935C0D000000000000000008041600F000C801EF0004432681440000422F30180000000000000180B868034C005B29ED2AEF935C0D00000000000000000C0616004703F0001503C801EF000643267C440000B50000B60000422F0A180000000000000180B83111AB005B29ED2AEF935C0D000000000000000008041600F000C801EF000443267C440000422F3A180000000000000180B7FA1ECA005B29ED2AEF935C0D00000000000000000C0616004704F0001503C801EF0006432681440000B50000B60000422F1D180000000000000180B7C332F5005B29ED2AEF935C0D00000000000000000C0616004704F0001504C801EF0006432686440000B50000B60000422F30180000000000000180B78C431E005B29ED2AEF935C0D000000000000000008041600F000C801EF0004432686440000422F8F180000000011'
avl_data_header = {}
avl_data = {}
avl_data_array = []
avl_data_footer = {}
avl_data_header[:packet_len] = data_hex_string[0..3]
avl_data_header[:packet_id] = data_hex_string[4..7]
avl_data_header[:not_usable_byte] = data_hex_string[8..9]
avl_data_header[:avl_packet_id] = data_hex_string[10..11]
avl_data_header[:imei_len] = data_hex_string[12..15]
imei_len_dec = data_hex_string[12..15].to_i(16)
current_add = 16
offset = imei_len_dec * 2
avl_data_header[:imei] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2
avl_data_header[:codec_id] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2
avl_data_header[:number_data1] = data_hex_string[current_add..(current_add+offset-1)]
data_num = avl_data_header[:number_data1].to_i(16)
for a in 0..(data_num-1) do
current_add = current_add+offset
offset = 2 * 8
avl_data[:timestamp] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2
avl_data[:priority] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 4
avl_data[:longitude] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 4
avl_data[:latitude] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 2
avl_data[:altitude] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 2
avl_data[:angle] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2
avl_data[:satellites] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 2
avl_data[:speed] = data_hex_string[current_add..(current_add+offset-1)]
avl_data_array.push(avl_data)
end
current_add = current_add+offset
offset = 2
avl_data_footer[:number_data2] = data_hex_string[current_add..(current_add+offset-1)]
current_add = current_add+offset
offset = 2 * 4
avl_data_footer[:crc_16] = data_hex_string[current_add..(current_add+offset-1)]
puts "header: #{avl_data_header}"
puts "data:"
puts avl_data_array
puts "footer: #{avl_data_footer}"
The result is:
header: {:packet_len=>"03D8", :packet_id=>"CAFE", :not_usable_byte=>"01", :avl_packet_id=>"D0", :imei_len=>"000F", :imei=>"383630323634303535353134323532", :codec_id=>"08", :number_data1=>"11"}
data:
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
{:timestamp=>"ED18000000000000", :priority=>"01", :longitude=>"80B97AB1", :latitude=>"1B005B29", :altitude=>"ED2A", :angle=>"EF93", :satellites=>"5C", :speed=>"0D00"}
footer: {:number_data2=>"00", :crc_16=>"00000000"}
The element in array data should be different. I am Ruby newbie, but it seems the grammer in my code is correct. Why this happens?

Why this happens?
You never change avl_data. It is always the same object. Hence, you push the same object to the Array in avl_data_array over and over again.
Here is a reduced version of your code demonstrating the problem more clearly:
single_array_element = {}
array = []
3.times do |i|
single_array_element[:i] = i
array << single_array_element
end
array.all? {|el| single_array_element.object_id == el.object_id}
#=> true
array
#=> [{ i: 2 }, { i: 2 }, { i: 2 }]
I am Ruby newbie, but it seems the grammer in my code is correct.
The syntax is indeed correct, otherwise the program would not even run – the parser would abort with a SyntaxError. But the semantics aren't: The code you wrote is 100% valid Ruby code, it is not an error.
Ruby is doing exactly what you are telling her to do, you just told it to do the wrong thing.

Related

Memory efficient 2D bit storage in Ruby (100M items)

I want to use a large data structure to represent a bit (0 or 1) or boolean (true/false) in Ruby.
In below example code, I am using a 2D array of size 10^4 * 10^4, to store a boolean data.
require 'get_process_mem'
num = 10000
twod = Array.new(num) { Array.new(num, false)}
for i in 0..num-1 do
for j in 0..num-1 do
twod[i][j] = i>j
end
end
mem = GetProcessMem.new
puts mem.inspect
The output shows that it uses ~778 MB.
#<GetProcessMem:0x00000078 #mb=777.93359375 #gb=0.7597007751464844 #kb=796604.0 #bytes=0.815722496e9>
I just tried changing the data type to integer in the code, and memory usage reports the same value.
Updated code:
require 'get_process_mem'
num = 10000
twod = Array.new(num) { Array.new(num, 500)}
for i in 0..num-1 do
for j in 0..num-1 do
twod[i][j] = 1000+i+j
end
end
mem = GetProcessMem.new
puts mem.inspect
Output:
#<GetProcessMem:0x00000078 #mb=777.6015625 #gb=0.7593765258789062 #kb=796264.0 #bytes=0.815374336e9>
I was expecting that boolean array would use less memory than integer array, which does not seem to be the case!
Is there any other optimized way to store bit or boolean values?
You can build a byte array in a FFI:Buffer to get good size/performance
https://rubydoc.info/github/ffi/ffi/FFI/Buffer
and do bit operations on each byte:
#!/usr/bin/env ruby
require 'get_process_mem'
require 'ffi'
BIT_OP = {
get: 1,
set: 2,
clear: 3,
}
class BitStore
attr_accessor :buf, :size
def initialize(shape) # ex [10000,10000] [10,20,30], etc
#shp = shape
#size = 1;
shape.each do | sz |
#size *= sz
end
#size = max(next8(#size)/8, 8) + 1 # min size needed
clear = true
#buf = FFI::Buffer.new(#size,1,clear)
end
def max(x,y)
x > y ? x : y
end
def next8(val)
val % 8 == 0 ? val+8 : ((val / 8).to_i + 1)*8
end
def write_buf_to_file(fname)
fout = File.open(fname, "wb")
fout.write(#buf.get_bytes(0,#size))
fout.close
end
def check_position(coord_arr)
if coord_arr.size != #shp.size
raise "coord_arr size != shape size"
end
coord_arr.each_with_index do | coord, i |
if coord_arr[i] > #shp[i]-1
raise "coord index out of bounds "
end
end
end
def get_position(coord_arr)
position = coord_arr[0]
(1..coord_arr.size-1).each do | i |
position += coord_arr[i] * #shp.reverse[i]
end
return position
end
def bit_op(coord_arr, op)
check_position(coord_arr)
position = get_position(coord_arr)
offset = (position / 8).to_i
bit_pos = (position % 8)
bit_val = 1 << bit_pos
val = #buf.get_string(offset, 1)
numeric_value = val == "" ? 0 : val.unpack("C")[0]
case op
when BIT_OP[:get]
numeric_value = numeric_value & bit_val
return numeric_value > 0 ? "set" : "clear"
when BIT_OP[:set]
numeric_value = numeric_value | bit_val
when BIT_OP[:clear]
numeric_value = numeric_value & (bit_val ^ 0xff)
end
#buf.put_string(offset,[numeric_value].pack("C"))
return ""
end
end
def main
begin
rows = 10000
cols = 10000
b = BitStore.new([rows,cols])
puts "setting [3,0]"
b.bit_op([3,0],BIT_OP[:set])
is_set = b.bit_op([3,0],BIT_OP[:get])
puts is_set
puts "setting [8000,8000]"
b.bit_op([8000,8000],BIT_OP[:set])
is_set = b.bit_op([8000,8000],BIT_OP[:get])
puts is_set
puts "clearing [8000,8000]"
b.bit_op([8000,8000],BIT_OP[:clear])
is_set = b.bit_op([8000,8000],BIT_OP[:get])
puts is_set
mem = GetProcessMem.new
puts mem.inspect
rescue NoMemoryError => e
puts "NoMemoryError"
p e
p e.backtrace.inspect
end
end
main
Memory use is 26MB:
user1#debian10 /home/user1/rubynew > ./pack_testb.rb
setting [3,0]
set
setting [8000,8000]
set
clearing [8000,8000]
clear
#<GetProcessMem:0x000000a0 #mb=26.6953125 #gb=0.02606964111328125 #kb=27336.0 #bytes=0.27992064e8>
The file structure on disk:
b = BitStore.new([7,6])
puts "setting [3,0]"
b.bit_op([3,0],BIT_OP[:set])
is_set = b.bit_op([3,0],BIT_OP[:get])
puts is_set
b.write_buf_to_file("/tmp/the_buf.bin")
system("xxd /tmp/the_buf.bin")
puts "setting [6,5]"
b.bit_op([6,5],BIT_OP[:set])
is_set = b.bit_op([6,5],BIT_OP[:get])
puts is_set
b.write_buf_to_file("/tmp/the_buf.bin")
system("xxd /tmp/the_buf.bin")
setting [3,0]
00000000: 0800 0000 0000 0000 00
setting [6,5]
00000000: 0000 0000 0002 0000 00

Ruby multiple threads and processes get stuck

I want to compare multiple threads and processes. I have to send the data which bubble-sorted in different child processes back to parent process then merge data. When data size is <= 10000, it succeeds, but when I input larger data ( 50000, 100000, 500000, max to 1000000):
Data example: 4 152 15 9523 526 1514 2623 ....
(Fisrt number is 4 for now.The others are just random numbers between 0~10000)
Case 3 will get stuck at the line like this:wt1.write... when size is over 50000. If size of data is 500000, even case 1 will be stuck.
Can anybody tell my what's problem in my code ?
This is my code :
def bubble_sort(list)
return list if list.size <= 1
swapped = true
while swapped do
swapped = false
0.upto(list.size-2) do |i|
if list[i] > list[i+1]
list[i], list[i+1] = list[i+1], list[i] # swap the values
swapped = true
end
end
end
return list
end
def mergesort(list)
return list if list.size <= 1
mid = list.size / 2
left = list[0, mid]
right = list[mid, list.size-mid]
merge(mergesort(left), mergesort(right))
end
def merge(left, right)
sorted = []
until left.empty? or right.empty?
if left.first <= right.first
sorted << left.shift # take out left first and push back to sorted
else
sorted << right.shift
end
end
return sorted.concat(left).concat(right)
end
#------------------------------------------------------------------method
#------------------------------------------------------------------main
#print "Input File Name: "
print "Filename is : "
fName = STDIN.gets.chomp() # file name
until File.exist?(fName)
print "Wrong file name! Type again :"
fName = STDIN.gets.chomp()
end
list = File.read(fName).split(' ').map{ |n| n.to_i} # File.read() will return string then split by space.
fName.insert(-5,"_output")
k = list[0].to_i # K is 4 for now
list.delete_at(0) # delete the K
SizeofInput = list.length # get the size of input
copylist = list.clone # copy from list
p = (SizeofInput / k ).to_i # every part size of input
print "Function Number is (1~4, 0 to exit ): "
fNum = STDIN.gets.chomp().to_i
outFile = File.open(fName,'a+') # new an output file n
until fNum === 0
copylist = list.clone
case fNum
when 1 # Case 1-----------------------------------------
puts "#{Time.now}"
start1 = Time.now
bubble_sort(copylist)
stop1 = Time.now
outFile.write(copylist)
outFile.write("\nFunc 1 spend time: #{stop1-start1} seconds Size of input : #{SizeofInput} " + "\n" *5)
puts "Original Time : #{stop1-start1} seconds. Size of input : #{SizeofInput} "
when 2 # Case 2------------------------------------------
sList = Array.new(k) { Array.new()} # sorted list
puts "#{Time.now}"
start2 = Time.now
threads = (0..k-1).map do |i|
if i == k-1
numbers = copylist[i*p..SizeofInput-1].clone# copy the origin array by converting into byte stream
else
numbers = copylist[i*p,p].clone# copy the origin array
end
Thread.new(i) do |i|
bubble_sort(numbers)
sList[i] = numbers.clone
end
end
threads.each{|t| t.join} # => This line will spend lot of time?!
# => main thread waits for child threads
mList = Array.new(k/2) {Array.new()}
thr = (0...k/2).map do |i|
Thread.new(i) do |j|
mList[j] = merge(sList[j*2],sList[(j*2 )+ 1]).clone
end
end
thr.each{|t| t.join}
copylist = merge(mList[0],mList[1]).clone
stop2 = Time.now
outFile.write(copylist)
outFile.write("\nFunc 2 spend time: #{stop2-start2} seconds Size of input : #{SizeofInput} " + "\n" *5)
puts "Spent time: #{stop2 - start2 } seconds. Size of Input : #{copylist.length}"
when 3 # Case 3-------------------------------------------
sList = Array.new(k) { Array.new()}
rd1,wt1 = IO.pipe # reader & writer
rd2,wt2 = IO.pipe
rd3,wt3 = IO.pipe
rd4,wt4 = IO.pipe
rd5,wt5 = IO.pipe
rd6,wt6 = IO.pipe
rd7,wt7 = IO.pipe
puts "#{Time.now}"
start2 = Time.now
pid1 = fork {
puts "1 starts"
rd1.close
numbers = copylist[0,p].clone
bubble_sort(numbers)
sList[0] = numbers.clone
wt1.write sList[0] #Marshal.dump(sList[0])
puts "1 is ok"
Process.exit!(true)
}
pid2 = fork {
puts "2 starts"
rd2.close
numbers = copylist[p,p].clone
bubble_sort(numbers)
sList[1] = numbers.clone
wt2.write sList[1] #Marshal.dump(sList[1])
puts "2 is ok"
Process.exit!(true)
exit
}
pid3 = fork {
puts "3 starts"
rd3.close
numbers = copylist[2*p,p].clone
bubble_sort(numbers)
sList[2] = numbers.clone
wt3.write sList[2] #Marshal.dump(sList[2])
puts "3 is ok"
Process.exit!(true)
}
pid4 = fork {
puts "4 starts"
rd4.close
numbers = copylist[3*p..SizeofInput-1].clone
bubble_sort(numbers)
sList[3] = numbers.clone
wt4.write sList[3] #Marshal.dump(sList[3])
puts "4 is ok"
Process.exit!(true)
}
mList = Array.new(k/2) {Array.new()}
Process.waitpid(pid1)
Process.waitpid(pid2)
wt1.close
wt2.close
puts "Finish Pid1 & 2"
pid5 = fork {
rd5.close
a = ( rd1.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} # Marshal.load(rd1.read)
b = ( rd2.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} # Marshal.load(rd2.read)
mList[0] = merge(a,b).clone
wt5.write mList[0] #Marshal.dump(mList[0])
Process.exit!(true)
}
Process.waitpid(pid3)
Process.waitpid(pid4)
wt3.close
wt4.close
puts "Finish Pid3 & 4"
pid6 = fork {
rd6.close
a = (rd3.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} #Marshal.load(rd3.read)
b = (rd4.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} #Marshal.load(rd4.read)
mList[1] = merge(a,b).clone
wt6.write mList[1] #Marshal.dump(mList[1])
Process.exit!(true)
}
Process.waitpid(pid5)
Process.waitpid(pid6)
wt5.close
wt6.close
puts "Finish Pid5 & 6"
pid7 = fork {
rd7.close
a = (rd5.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} #Marshal.load(rd5.read)
b = (rd6.read.delete '[]').split(%r{,\s*}).map{ |n| n.to_i} #Marshal.load(rd6.read)
copylist = merge(a,b)
puts "7 is ok"
wt7.write copylist #Marshal.dump(copylist)
Process.exit!(true)
}
Process.waitpid(pid7)
puts "Finish Pid7"
wt7.close
stop2 = Time.now
outFile.write(rd7.read) #Marshal.load(rd7.read))
outFile.write("\nFunc 3 spend time: #{stop2-start2} seconds Size of input : #{SizeofInput} " + "\n" *5)
puts "Spent time: #{stop2 - start2 } seconds. Size of Input : #{copylist.length}"
when 4 # Case 4-------------------------------------------
sList = Array.new(4) { Array.new() } # sort list
start3 = Time.now
(0..3).map do |i|
if i === 3
numbers = copylist[i*p..SizeofInput-1].clone # copy the origin array
else
numbers = copylist[i*p,p].clone
end
sList[i] = bubble_sort(numbers)
end
mList = Array.new(2) { Array.new() } # merge lsit
(0..1).map do |t|
mList[t] = merge(sList[t*2],sList[t*2+1]).clone
end
copylist = merge(mList[0],mList[1]).clone
stop3 = Time.now
outFile.write(copylist)
outFile.write("\nFunc 4 spend time: #{stop3-start3} seconds Size of input : #{SizeofInput} " + "\n" *5)
puts "Spent time: #{stop3 - start3 } seconds. Size of Input : #{copylist.length}"
else puts "Wrong Number!! Try Again!"
end
print "Function Number is (1~4, 0 to exit ): "
fNum = STDIN.gets.chomp().to_i
end # end of until loop
outFile.close()
puts "See you again~"

How can I sum corresponding elements of several arrays?

I have several arrays of various lengths, each with a 2-item array within it. For example:
[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
or
[["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11], ["04:00", 8.9], ["05:00", 18.187]]
I want to loop through, and add the corresponding elements of each array, into a new array. The output array will be the length of the longest array we're adding together.
So the two arrays above summed together would output the following:
[["12:00", 51.3], ["01:00", 5.55], ["02:00", 14.34], ["03:00", 102.61], ["04:00", 8.9], ["05:00", 18.187]]
I don't think I can use reduce() or inject() since I don't want to collapse the array, nor is the array a simple array of elements.
I really have no idea how I might approach this problem.
You can do it in one line with Hash.merge. Use a block to sum the values during the merge.
def sum_arrays(a, b)
Hash[a].merge(Hash[b]){|k, i, j| i + j}.to_a
end
Output:
a = [["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
b = [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11], ["04:00", 8.9], ["05:00", 18.187]]
sum_arrays(a,b)
=> [["12:00", 51.3], ["01:00", 5.55], ["02:00", 14.34], ["03:00", 102.61], ["04:00", 8.9], ["05:00", 18.187]]
To sum more than two arrays, add one more line:
def sum_many_arrays(*a)
a.reduce{|s, i| sum_arrays(s, i)}
end
Output:
sum_many_arrays([[:a,1],[:b,2]],[[:a,2],[:b,2],[:c,1]],[[:a,5],[:b,2]])
=> [[:a, 8], [:b, 6], [:c, 1]]
I recommend #Oleg's approach, but there's always another way:
arr = [[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]],
[["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11],
["04:00", 8.9]],
[["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]]
keys = arr.reduce([]) { |keys,a| keys | a.map(&:first) }
arr.map { |a| a.to_h.values_at(*keys) }
.transpose
.map { |e| [keys.shift, e.reduce(0) { |tot,x| tot + x.to_f }] }
#=> [["12:00", 64.6], ["01:00", 27.8], ["02:00", 16.18],
# ["03:00", 102.61], ["04:00", 8.9]]
The steps:
keys = arr.reduce([]) { |keys,a| keys | a.map(&:first) }
#=> ["12:00", "01:00", "02:00", "03:00", "04:00"]
b = arr.map { |a| a.to_h.values_at(*keys) }
#=> [[ 7.0, 3.3, 11.9, 56.5, nil],
# [44.3, 2.25, 2.44, 46.11, 8.9],
# [13.3, 22.25, 1.84, nil, nil]]
c = b.transpose
#=> [[7.0, 44.3, 13.3],
# [3.3, 2.25, 22.25],
# [11.9, 2.44, 1.84],
# [56.5, 46.11, nil],
# [nil, 8.9, nil]]
c.map { |e| [keys.shift, e.reduce(0) { |tot,x| tot + x.to_f }] }
#=> [["12:00", 64.6], ["01:00", 27.8], ["02:00", 16.18],
# ["03:00", 102.61], ["04:00", 8.9]]
Notice that NilClass#to_f converts nil to 0.0.
To elaborate the calculation of b above:
d = arr.map
#=> #<Enumerator: [[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9],
# ["03:00", 56.5]],
# [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44],
# ["03:00", 46.11], ["04:00", 8.9]],
# [["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]]:map>
a = d.next
#=> [["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
e = a.to_h
#=> {"12:00"=>7.0, "01:00"=>3.3, "02:00"=>11.9, "03:00"=>56.5}
f = e.values_at(*keys)
#=> e.values_at(*["12:00", "01:00", "02:00", "03:00", "04:00"] )
#=> [7.0, 3.3, 11.9, 56.5, nil]
a = d.next
#=> [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44],
# ["03:00", 46.11], ["04:00", 8.9]]
e = a.to_h
#=> {"12:00"=> 44.3, "01:00"=>2.25, "02:00"=>2.44,
# "03:00"=>46.11, "04:00"=> 8.9}
f = e.values_at(*keys)
#=> [44.3, 2.25, 2.44, 46.11, 8.9]
a = d.next
#=> [["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]
e = a.to_h
#=> {"01:00"=>22.25, "02:00"=>1.84, "12:00"=>13.3}
f = e.values_at(*keys)
#=> [13.3, 22.25, 1.84, nil, nil]
c.map is computed as follows:
keys = ["12:00", "01:00", "02:00", "03:00", "04:00"]
d = c.map
#=> #<Enumerator: [[ 7.0, 44.3, 13.3], [3.3, 2.25, 22.25],
# [11.9, 2.44, 1.84], [56.5, 46.11, nil],
# [nil, 8.9, nil]]:map>
e = d.next
#=> [7.0, 44.3, 13.3]
f = keys.shift
#=> "12:00"
keys
#=> ["01:00", "02:00", "03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 64.6
e = d.next
#=> [3.3, 2.25, 22.25]
f = keys.shift
#=> "01:00"
keys
#=> ["02:00", "03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 27.8
e = d.next
#=> [11.9, 2.44, 1.84]
f = keys.shift
#=> "02:00"
keys
#=> ["03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 16.18
e = d.next
#=> [56.5, 46.11, nil]
f = keys.shift
#=> "03:00"
keys
#=> ["04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 102.61
e = d.next
#=> [nil, 8.9, nil]
f = keys.shift
#=> "04:00"
keys
#=> []
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 8.9

Iterate an array of hashes and output each iteration in a row

I have an array of hashes as in example:
[{:number=>2131, :owner=>"Mark"},
{:number=>223, :owner=>"Mark"},
{:number=>546, :owner=>"Mark"},
{:number=>765454, :owner=>"Tom"},
{:number=>845378, :owner=>"Jack"},
{:number=>75, :owner=>"Jack"},
{:number=>2342, :owner=>"Jack"}]
How can I output number values of the owners sorted line by line to get this:
# ["Jack", "Mark", "Tom"]
75 223 765454
2342 546 -
845378 2131 -
Each column with sorted numbers belongs to an owner, each row contains numbers from each owner.
What about
a = [{:number=>2131, :owner=>"Mark"},
{:number=>223, :owner=>"Mark"},
{:number=>546, :owner=>"Mark"},
{:number=>765454, :owner=>"Tom"},
{:number=>845378, :owner=>"Jack"},
{:number=>75, :owner=>"Jack"},
{:number=>2342, :owner=>"Jack"}]
a1 = a.group_by { |h| h[:owner] }
#=> {"Mark"=>[{:number=>2131, :owner=>"Mark"}, {:number=>223, :owner=>"Mark"}, {:number=>546, :owner=>"Mark"}], "Tom"=>[{:number=>765454, :owner=>"Tom"}], "Jack"=>[{:number=>845378, :owner=>"Jack"}, {:number=>75, :owner=>"Jack"}, {:number=>2342, :owner=>"Jack"}]}
a2 = a1.map { |k, v| [k, v.sort_by { |v| v[:number] } ] }
#=> [["Mark", [{:number=>223, :owner=>"Mark"}, {:number=>546, :owner=>"Mark"}, {:number=>2131, :owner=>"Mark"}]], ["Tom", [{:number=>765454, :owner=>"Tom"}]], ["Jack", [{:number=>75, :owner=>"Jack"}, {:number=>2342, :owner=>"Jack"}, {:number=>845378, :owner=>"Jack"}]]]
a3 = a2.sort_by { |(v0)| v0 }.to_h
#=> {"Jack"=>[{:number=>75, :owner=>"Jack"}, {:number=>2342, :owner=>"Jack"}, {:number=>845378, :owner=>"Jack"}], "Mark"=>[{:number=>223, :owner=>"Mark"}, {:number=>546, :owner=>"Mark"}, {:number=>2131, :owner=>"Mark"}], "Tom"=>[{:number=>765454, :owner=>"Tom"}]}
max_values_size = a3.values.max_by { |v| v.size }.size
#=> 3
a4 = max_values_size.times.map do |i|
a3.keys.map do |k|
a3[k][i] ? a3[k][i][:number] : '-'
end
end
#=> [[75, 223, 765454], [2342, 546, "-"], [845378, 2131, "-"]]
a4.each { |v| puts v.join(' ') }
#=>
75 223 765454
2342 546 -
845378 2131 -
It should be quite self-explanatory, ask if unclear

Assigning values to words based on position of letters in alphabet

I have an array of names. Each letter gets a value of 1 to 26 for the alphabet. Then the letter is multiplied by its place in the list. I came up with the following algorithm:
score = 0
names.each_with_index do |name, index|
temp = 0
letters = name.to_s.scan(/(.)/)
letters.each do |letter|
temp += 1 if letter.to_s.match(/A/)
temp += 2 if letter.to_s.match(/B/)
temp += 3 if letter.to_s.match(/C/)
temp += 4 if letter.to_s.match(/D/)
temp += 5 if letter.to_s.match(/E/)
temp += 6 if letter.to_s.match(/F/)
temp += 7 if letter.to_s.match(/G/)
temp += 8 if letter.to_s.match(/H/)
temp += 9 if letter.to_s.match(/I/)
temp += 10 if letter.to_s.match(/J/)
temp += 11 if letter.to_s.match(/K/)
temp += 12 if letter.to_s.match(/L/)
temp += 13 if letter.to_s.match(/M/)
temp += 14 if letter.to_s.match(/N/)
temp += 15 if letter.to_s.match(/O/)
temp += 16 if letter.to_s.match(/P/)
temp += 17 if letter.to_s.match(/Q/)
temp += 18 if letter.to_s.match(/R/)
temp += 19 if letter.to_s.match(/S/)
temp += 20 if letter.to_s.match(/T/)
temp += 21 if letter.to_s.match(/U/)
temp += 22 if letter.to_s.match(/V/)
temp += 23 if letter.to_s.match(/W/)
temp += 24 if letter.to_s.match(/X/)
temp += 25 if letter.to_s.match(/Y/)
temp += 26 if letter.to_s.match(/Z/)
end
score += (index+1) * temp
end
puts score
This is quite slow code. I hope someone can explain me a better and faster way to accomplish this task.
This is how I would do it.
Assumptions
the only characters are upper- and lower-case letters and whitespace.
after converting all characters of each element of the array to lower case, you want the letter offsets from a for each word to be summed and then multiplied by the (base 1) position of the word in the array.
Code
def totals_by_name(names)
names.each.with_index(1).with_object({}) { |(name,i),tots|
tots[name] = i*(name.downcase.each_char.reduce(0) { |tot,c|
tot + c.ord - 'a'.ord + 1 }) }
end
def total(names)
totals_by_name(names).values.reduce(:+)
end
Example
names = %w{ Joanne Jackie Joe Jethro Jack Jill }
#=> ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]
total(names)
#=> 914
Explanation
For the method totals_by_name and the array names above:
e0 = names.each
#=> #<Enumerator: ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]:each>
We can see the values this enumerator will pass on by converting it to an array:
e0.to_a
#=> ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]
Continuing,
e1 = e0.with_index(1)
#=> #<Enumerator: #<Enumerator: ["Joanne", "Jackie", "Joe", "Jethro",
# "Jack", "Jill"]:each>:with_index(1)>
e1.to_a
#=> [["Joanne", 1], ["Jackie", 2], ["Joe", 3], ["Jethro", 4], ["Jack", 5], ["Jill", 6]]
e2 = e1.with_object({})
#=> #<Enumerator: #<Enumerator: #<Enumerator: ["Joanne", "Jackie", "Joe",
# "Jethro", "Jack", "Jill"]:each>:with_index(1)>:with_object({})>
e2.to_a
#=> [[["Joanne", 1], {}], [["Jackie", 2], {}], [["Joe", 3], {}],
# [["Jethro", 4], {}], [["Jack", 5], {}], [["Jill", 6], {}]]
We can think of e2 and e3 as compound enumerators.
Enumerator#each passes elements of e2 to the block and assigns values to the block variables. We can use Enumerator#next to see what happens:
(name,i),tots = e2.next
#=> [["Joanne", 1], {}]
name #=> "Joanne"
i #=> 1
tots #=> {}
The block calculation is:
e3 = name.downcase.each_char
#=> #<Enumerator: "joanne":each_char>
e3.to_a # have a look
#=> ["j", "o", "a", "n", "n", "e"]
e3.reduce(0) { |tot,c| tot + c.ord - 'a'.ord + 1 }
#=> 59
For
c = "j"
this block calculation is:
tot + c.ord - 'a'.ord + 1
#=> 0 + 106 - 97 + 1
#=> 10
Therefore:
tots[name] = i*(name.downcase.each_char.reduce(0) { |tot,c|
tot + c.ord - 'a'.ord + 1 })
#=> tots["Joanna"] = 1*(59)
tots
#=> {"Joanne"=>59}
Values for other names are calculated similarly. The method tots is straightforward.
I would most likely go with:
class String
def points
each_char.map.with_index do |char, index|
(char.downcase.ord - 96) * (index + 1)
end.inject(0, :+)
end
end
'Hello'.points #=> 177

Resources