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

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

Related

How do I fix a problem to call a function in Ruby?

I'm trying to use some ruby code that I've found in Github. I've downloaded the code and did the necessary imports the "requires" and tried to run it as it is described in the readme file on github repository. The code is the following:
In the file pcset_test.rb the code is the following:
require './pcset.rb'
require 'test/unit'
#
# When possible, test cases are adapted from
# Introduction to Post-Tonal Theory by Joseph N. Straus,
# unless obvious or otherwise noted.
#
class PCSetTest < Test::Unit::TestCase
def test_init
#assert_raise(ArgumentError) {PCSet.new []}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3, 'string']}
assert_raise(ArgumentError) {PCSet.new "string"}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3.6, 4]}
assert_equal([0, 1, 2, 9], PCSet.new([0, 1, 2, 33, 13]).pitches)
assert_equal([3, 2, 1, 11, 10, 0], PCSet.new_from_string('321bac').pitches)
assert_equal([0,2,4,5,7,11,9], PCSet.new([12,2,4,5,7,11,9]).pitches)
assert_nothing_raised() {PCSet.new []}
end
def test_inversion
end
def test_transposition
end
def test_multiplication
end
#
# set normal prime forte #
# 0,2,4,7,8,11 7,8,11,0,2,4 0,1,4,5,7,9 6-31
# 0,1,2,4,5,7,11 11,0,1,2,4,5,7 0,1,2,3,5,6,8 7-Z36
# 0,1,3,5,6,7,9,10,11 5,6,7,9,10,11,0,1,3 0,1,2,3,4,6,7,8,10 9-8
#
def test_normal_form
testPC = PCSet.new [0,4,8,9,11]
assert_kind_of(PCSet, testPC.normal_form)
assert_equal([8,9,11,0,4], testPC.normal_form.pitches)
assert_equal([10,1,4,6], PCSet.new([1,6,4,10]).normal_form.pitches)
assert_equal([2,4,8,10], PCSet.new([10,8,4,2]).normal_form.pitches)
assert_equal([7,8,11,0,2,4], PCSet.new([0,2,4,7,8,11]).normal_form.pitches)
assert_equal([11,0,1,2,4,5,7], PCSet.new([0,1,2,4,5,7,11]).normal_form.pitches)
assert_equal([5,6,7,9,10,11,0,1,3], PCSet.new([0,1,3,5,6,7,9,10,11]).normal_form.pitches)
end
def test_prime_form
assert_equal([0,1,2,6], PCSet.new([5,6,1,7]).prime.pitches)
assert_equal([0,1,4], PCSet.new([2,5,6]).prime.pitches)
assert_equal([0,1,4,5,7,9], PCSet.new([0,2,4,7,8,11]).prime.pitches)
assert_equal([0,1,2,3,5,6,8], PCSet.new([0,1,2,4,5,7,11]).prime.pitches)
assert_equal([0,1,2,3,4,6,7,8,10], PCSet.new([0,1,3,5,6,7,9,10,11]).prime.pitches)
end
def test_set_class
testPcs = PCSet.new([2,5,6])
testPrime = testPcs.prime
assert_equal([
[2,5,6], [3,6,7], [4,7,8], [5,8,9], [6,9,10], [7,10,11],
[8,11,0],[9,0,1], [10,1,2],[11,2,3],[0,3,4], [1,4,5],
[6,7,10],[7,8,11],[8,9,0], [9,10,1],[10,11,2],[11,0,3],
[0,1,4], [1,2,5], [2,3,6], [3,4,7], [4,5,8], [5,6,9]
].sort, PCSet.new([2,5,6]).set_class.map{|x| x.pitches})
assert_equal(testPcs.set_class.map{|x| x.pitches}, testPrime.set_class.map{|x| x.pitches})
end
def test_interval_vector
assert_equal([2,1,2,1,0,0], PCSet.new([0,1,3,4]).interval_vector)
assert_equal([2,5,4,3,6,1], PCSet.new([0,1,3,5,6,8,10]).interval_vector)
assert_equal([0,6,0,6,0,3], PCSet.new([0,2,4,6,8,10]).interval_vector)
end
def test_complement
assert_equal([6,7,8,9,10,11], PCSet.new([0,1,2,3,4,5]).complement.pitches)
assert_equal([3,4,5], PCSet.new([0,1,2], 6).complement.pitches)
end
#
# Test values from (Morris 1991), pages 105-111
# Citation:
# Morris. Class Notes for Atonal Music Theory
# Lebanon, NH. Frog Peak Music, 1991.
#
def test_invariance_vector
assert_equal([1,0,0,0,5,6,5,5],PCSet.new([0,2,5]).invariance_vector)
assert_equal([2,2,2,2,6,6,6,6],PCSet.new([0,1,6,7]).invariance_vector)
assert_equal([6,6,6,6,6,6,6,6],PCSet.new([0,2,4,6,8,10]).invariance_vector)
assert_equal([1,0,0,0,0,0,0,0],PCSet.new([0,1,2,3,4,5,8]).invariance_vector)
assert_equal([1,0,0,1,0,0,0,0],PCSet.new([0,1,2,3,5,6,8]).invariance_vector)
assert_equal([12,12,12,12,0,0,0,0],PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).invariance_vector)
end
#
# Test values from (Huron 1994). Huron rounds, thus the 0.01 margin of error.
# Citation:
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
#
def test_huron
h1 = PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).huron
assert_in_delta(-0.2, h1[0], 0.01)
assert_in_delta(0.21, h1[1], 0.01)
h2 = PCSet.new([0,2,4,5,7,9,11]).huron
assert_in_delta(4.76, h2[0], 0.01)
assert_in_delta(0.62, h2[1], 0.01)
end
def test_coherence
end
end
And in the file pcset.rb the folloing code:
#
# => PCSet Class for Ruby
# => Beau Sievers
# => Hanover, Fall 2008.
#
#
# TODO: Make this a module to avoid namespace collisions.
# Lilypond and MusicXML output
#
include Math
def choose(n, k)
return [[]] if n.nil? || n.empty? && k == 0
return [] if n.nil? || n.empty? && k > 0
return [[]] if n.size > 0 && k == 0
c2 = n.clone
c2.pop
new_element = n.clone.pop
choose(c2, k) + append_all(choose(c2, k-1), new_element)
end
def append_all(lists, element)
lists.map { |l| l << element }
end
def array_to_binary(array)
array.inject(0) {|sum, n| sum + 2**n}
end
# the following method is horrifically inelegant
# but avoids monkey-patching.
# TODO: do this right, incl. error checking
def pearsons(x, y)
if !x.is_a?(Array) || !y.is_a?(Array) then raise StandardError, "x and y must be arrays", caller end
if x.size != y.size then raise StandardError, "x and y must be same size", caller end
sum_x = x.inject(0) {|sum, n| sum + n}
sum_y = y.inject(0) {|sum, n| sum + n}
sum_square_x = x.inject(0) {|sum, n| sum + n * n}
sum_square_y = y.inject(0) {|sum, n| sum + n * n}
xy = []
x.zip(y) {|a, b| xy.push(a * b)}
sum_xy = xy.inject(0) {|sum, n| sum + n}
num = sum_xy - ((sum_x * sum_y)/x.size)
den = Math.sqrt((sum_square_x - ((sum_x*sum_x)/x.size)) * (sum_square_y - ((sum_y*sum_y)/x.size)))
(num/den)
end
class PCSet
include Comparable
attr_reader :pitches, :base, :input
def initialize(pcarray, base = 12)
if pcarray.instance_of?(Array) && pcarray.all?{|pc| pc.instance_of?(Fixnum)}
#base, #input = base, pcarray
#pitches = pcarray.map{ |x| x % #base }.uniq
else
raise ArgumentError, "Improperly formatted PC array", caller
end
end
def PCSet.new_from_string(pcstring, base = 12)
if base > 36 then raise StandardError, "Use PCSet.new to create pcsets with a base larger than 36", caller end
pcarray = []
pcstring.downcase.split(//).each do |c|
if c <= 'z' and c >= '0' then pcarray.push(c.to_i(36)) end
end
PCSet.new pcarray, base
end
def <=>(pcs)
#pitches <=> pcs.pitches
end
def [](index)
#pitches[index]
end
# Intersection
def &(other)
PCSet.new #pitches & other.pitches
end
# Union
def |(other)
PCSet.new #pitches | other.pitches
end
def inspect
#pitches.inspect
end
def length
#pitches.length
end
def invert(axis = 0)
PCSet.new #pitches.map {|x| (axis-x) % #base}
end
def invert!(axis = 0)
#pitches.map! {|x| (axis-x) % #base}
end
def transpose(interval)
PCSet.new #pitches.map {|x| (x + interval) % #base}
end
def transpose!(interval)
#pitches.map! {|x| (x + interval) % #base}
end
def multiply(m = 5)
PCSet.new #pitches.map {|x| (x * m) % #base}
end
def multiply!(m = 5)
#pitches.map! {|x| (x * m) % #base}
end
def zero
transpose(-1 * #pitches[0])
end
def zero!
transpose!(-1 * #pitches[0])
end
def transpositions
(0..(#base-1)).to_a.map{|x| #pitches.map {|y| (y + x) % #base}}.sort.map {|x| PCSet.new x}
end
def transpositions_and_inversions(axis = 0)
transpositions + invert(axis).transpositions
end
#
# Normal form after Straus. Morris and AthenaCL do this differently.
#
def normal_form
tempar = #pitches.sort
arar = [] # [[1,4,7,8,10],[4,7,8,10,1], etc.] get each cyclic variation
tempar.each {arar.push PCSet.new(tempar.unshift(tempar.pop))}
most_left_compact(arar)
end
def normal_form!
#pitches = normal_form.pitches
end
def is_normal_form?
self.pitches == self.normal_form.pitches
end
def set_class
transpositions_and_inversions.map{|pcs| pcs.normal_form}.sort
end
def prime
most_left_compact([normal_form.zero, invert.normal_form.zero])
end
def prime!
self.pitches = self.prime.pitches
end
def is_prime?
self.pitches == self.prime.pitches
end
def complement
new_pitches = []
#base.times do |p|
if !#pitches.include? p then
new_pitches.push p
end
end
PCSet.new new_pitches
end
def full_interval_vector
pairs = choose(#pitches, 2) # choose every pc pair
intervals = pairs.map {|x| (x[1] - x[0]) % #base} # calculate every interval
i_vector = Array.new(#base-1).fill(0)
intervals.each {|x| i_vector[x-1] += 1} # count the intervals
i_vector
end
def interval_vector
i_vector = full_interval_vector
(0..((#base-1)/2)-1).each {|x| i_vector[x] += i_vector.pop}
i_vector
end
#
# Morris's invariance vector
#
def invariance_vector(m = 5)
t = transpositions.map!{|pcs| self & pcs}
ti = invert.transpositions.map!{|pcs| self & pcs}
tm = multiply(m).transpositions.map!{|pcs| self & pcs}
tmi = invert.multiply(m).transpositions.map!{|pcs| self & pcs}
tc = complement.transpositions.map!{|pcs| self & pcs}
tic = complement.invert.transpositions.map!{|pcs| self & pcs}
tmc = complement.multiply(m).transpositions.map!{|pcs| self & pcs}
tmic = complement.invert.multiply(m).transpositions.map!{|pcs| self & pcs}
[t, ti, tm, tmi, tc, tic, tmc, tmic].map{|x| x.reject{|pcs| pcs.pitches != #pitches}.length}
end
# Huron's aggregate dyadic consonance measure.
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
def huron
if #base != 12 then raise StandardError, "PCSet.huron only makes sense for mod 12 pcsets", caller end
# m2/M7 M2/m7 m3/M6 M3/m6 P4/P5 A4/d5
huron_table = [-1.428, -0.582, 0.594, 0.386, 1.240, -0.453]
interval_consonance = []
interval_vector.zip(huron_table) {|x, y| interval_consonance.push(x * y) }
aggregate_dyadic_consonance = interval_consonance.inject {|sum, n| sum + n}
[aggregate_dyadic_consonance, pearsons(interval_vector, huron_table)]
end
#
# Balzano's vector of relations. Citation for all Balzano methods:
#
# Balzano. "The Pitch Set as a Level of Description for Studying Musical
# Pitch Perception" in Music, Mind, and Brain ed. Clynes. Plenum Press. 1982.
#
def vector_of_relations
(0..length-1).to_a.map do |i|
(0..length-1).to_a.map do |j|
(#pitches[(i + j) % length] - #pitches[i]) % #base
end
end
end
#
# Checks if the set satisfies Balzano's uniqueness.
#
def is_unique?
vector_of_relations.uniq.size == vector_of_relations.size
end
#
# Checks if the set satisfies Balzano's scalestep-semitone coherence.
# For all s[i] and s[i1]:
# j < k => v[i][j] < v[i1][k]
# Where j and k are scalestep-counting indices.
# And unless v[i][j] == 6 (a tritone), in which case the strict inequality is relaxed.
#
def is_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
if v[i][j] == 6
truth_array.push(v[i][j] <= v[i1][k])
else
truth_array.push(v[i][j] < v[i1][k])
end
if v[i1][j] == 6
truth_array.push(v[i1][j] <= v[i][k])
else
truth_array.push(v[i1][j] < v[i][k])
end
end
end
!truth_array.include?(false)
end
#
# Strict Balzano coherence, no inequality relaxation for tritones.
#
def is_strictly_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
truth_array.push(v[i][j] < v[i1][k])
truth_array.push(v[i1][j] < v[i][k])
end
end
!truth_array.include?(false)
end
def notes(middle_c = 0)
noteArray = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
if #base != 12 then raise StandardError, "PCSet.notes only makes sense for mod 12 pcsets", caller end
out_string = String.new
transpose(-middle_c).pitches.each do |p|
out_string += noteArray[p] + ", "
end
out_string.chop.chop
end
def info
print "modulo: #{#base}\n"
print "raw input: #{#input.inspect}\n"
print "pitch set: #{#pitches.inspect}\n"
print "notes: #{notes}\n"
print "normal: #{normal_form.inspect}\n"
print "prime: #{prime.inspect}\n"
print "interval vector: #{interval_vector.inspect}\n"
print "invariance vector: #{invariance_vector.inspect}\n"
print "huron ADC: #{huron[0]} pearsons: #{huron[1]}\n"
print "balzano coherence: "
if is_strictly_coherent?
print "strictly coherent\n"
elsif is_coherent?
print "coherent\n"
else
print "false\n"
end
end
# def lilypond
#
# end
#
# def musicXML
#
# end
###############################################################################
private
#
# Convert every pitch array to a binary representation, e.g.:
# [0,2,4,8,10] -> 010100010101
# 2^n: BA9876543210
# The smallest binary number is the most left-compact.
#
def most_left_compact(pcset_array)
if !pcset_array.all? {|pcs| pcs.length == pcset_array[0].length}
raise ArgumentError, "PCSet.most_left_compact: All PCSets must be of same cardinality", caller
end
zeroed_pitch_arrays = pcset_array.map {|pcs| pcs.zero.pitches}
binaries = zeroed_pitch_arrays.map {|array| array_to_binary(array)}
winners = []
binaries.each_with_index do |num, i|
if num == binaries.min then winners.push(pcset_array[i]) end
end
winners.sort[0]
end
end
I'm calling them as follows:
> my_pcset = PCSet.new([0,2,4,6,8,10])
> my_pcset2 = PCSet.new([1,5,9])
It shoud return:
> my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
> my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
But is returning nothing.
The code is available on github
Thanks
Try this in terminal: irb -r ./path_to_directory/pcset.rb and then initialize the objects.
I think the documentation for the repo is bad as it does not explain how you should be running this.
The result of
my_pcset = PCSet.new([0,2,4,6,8,10])
should set my_pcset to an instance of a PCSet not an array, so these lines from the README file are confusing at best.
3. How to use it
Make new PCSets:
my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
Looking at the code, I see inspect has been delegated to #pitches
def inspect
#pitches.inspect
end
I think if you inspect my_pcset you will get the expected result.
my_pcset = PCSet.new([0,2,4,6,8,10])
p my_pcset # will print [0, 2, 4, 6, 8, 10]
or `my_pcset.inspect` will return what you are expecting.

Creating sort visualiser in Ruby

I'm trying to make a sort visualiser using Ruby and Gosu. When the user left clicks it will sort the numbers out using a insertion sort. When the user right clicks it will sort the numbers out using a bubble sort. So far the only the insertion sort works but the bubble sort crashes after sorting out the first number. Does anyone know why? Thanks.
require 'gosu'
module ZOrder
BACKGROUND, MIDDLE, TOP = *0..2
end
SCREEN_WIDTH = 600 # needs to be COUNT * COLUMN_WIDTH
SCREEN_HEIGHT = 200 # Should be multiple of 100
MAX_VALUE = 100 # the range of the data 1 to 100
COUNT = 30 # Number of items
COL_WIDTH = 20 # suggested at least 10
class Column
# have a pointer to the neighbouring cells
attr_accessor :height, :value
def initialize(value)
#value = value
#height = (SCREEN_HEIGHT/MAX_VALUE) * value
end
end
class GameWindow < Gosu::Window
def initialize
super SCREEN_WIDTH, SCREEN_HEIGHT, false
self.caption = "Sort Visualiser"
#path = nil
#do_insert = false
#do_bubble = false
#columns = Array.new(COUNT)
column_index = 0
while (column_index < COUNT)
col = Column.new(rand(MAX_VALUE + 1))
#columns[column_index] = col
column_index += 1
end
#text_font = Gosu::Font.new(10)
end
def needs_cursor?
true
end
def insertion_sort(loop)
j = loop
done = false
while ((j > 0) and (!done))
if (#columns[j].value < #columns[j - 1].value)
temp = #columns[j - 1]
#columns[j - 1] = #columns[j]
#columns[j] = temp
else
done = true
end
j = j - 1
end
end
def bubble_sort(loop2)
j = loop2
i = loop2 + 1
done = false
while ((j > 0) and (!done))
if (#columns[j].value > #columns[i].value)
temp = #columns[j]
#columns[j] = #columns[i]
#columns[i] = temp
else
done = true
end
j = j - 1
end
end
def button_down(id)
case id
when Gosu::MsLeft
#do_insert = true
#loop = 0
#do_bubble = false
when Gosu::MsRight
#do_bubble = true
#loop2 = 0
#do_insert = false
end
end
def update
if (#do_insert)
puts "Doing insert #{#loop}"
if #loop < (COUNT)
insertion_sort(#loop)
#loop += 1
sleep(0.5)
else
#do_insert = false
end
end
if (#do_bubble)
puts "Doing bubble #{#loop2}"
if #loop2 < (COUNT)
bubble_sort(#loop2)
#loop2 += 1
sleep(0.5)
else
#do_bubble = false
end
end
end
def draw
column_index = 0
while (column_index < #columns.length)
color = Gosu::Color::GREEN
Gosu.draw_rect((column_index * COL_WIDTH), SCREEN_HEIGHT - #columns[column_index].height, COL_WIDTH, #columns[column_index].height, color, ZOrder::MIDDLE, mode=:default)
#text_font.draw("#{#columns[column_index].value}", (column_index * COL_WIDTH) + 5, SCREEN_HEIGHT - 10, ZOrder::TOP, 1.0, 1.0, Gosu::Color::RED)
column_index += 1
end
end
end
window = GameWindow.new
window.show
EDIT: Added in the rest of the code and removed some tags

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~"

very simple ruby programing, getting error and don't understand it

I'm asked to write the ruby program that generate the output based the given command,
The full description
I'm really new in ruby (maybe few hours that I have started ruby)
I'm getting this error, please check my code for other possible errors:
Thank you.
n `block in each2': undefined method `[]' for #<MyVector:0x00000002c4ad90 #array=[2, 3, 4]> (NoMethodError)
What I have done so far:
# MyVector Class
class MyVector
def initialize (a)
if !(a.instance_of? Array)
raise "ARGUMENT OF INITIALIZER MUST BE AN ARRAY"
else
#array = a
end
end
def array
#array
end
def to_s
#array.to_s
end
def length
#array.length
end
def each2(a)
raise Error, "INTEGER IS NOT LIKE VECTOR" if a.kind_of?(Integer)
Vector.Raise Error if length != a.length
return to_enum(:each2, a) unless block_given?
length.times do |i|
yield #array[i], a[i]
end
self
end
def * (a)
Vector.Raise Error if length != a.length
p = 0
each2(a) {|a1, a2|p += a1 * a2}
p
end
end
# MyMatrix Class
class MyMatrix
def initialize a
#array=Array.new(a.length)
i=0
while(i<a.length)
#array[i]=MyVector.new(a[i])
end
end
def to_s
#array.to_s
end
def transpose
size=vectors[0].length
arr= Array.new(size)
i=0
while i<size
a=Array.new(vector.length)
j=0
while j<a.length
a[j]=vectors[j].arr[i]
j+=1
end
arr[i]=a
i+=1
end
arr[i]=a
i+=1
end
def *m
if !(m instance_of? MyMatrix)
raise Error
a=Array.new(#array.length)
i=0
while (i<#array.length)
a[i]=#array[i]*m
i=i+1
end
end
end
end
Input:
Test code
v = MyVector.new([1,2,3])
puts "v = " + v.to_s
v1 = MyVector.new([2,3,4])
puts "v1 = " + v1.to_s
puts "v * v1 = " + (v * v1).to_s
m = MyMatrix.new([[1,2], [1, 2], [1, 2]])
puts "m = " + m.to_s + "\n"
puts "v * m = " + (v * m).to_s
m1 = MyMatrix.new([[1, 2, 3], [2, 3, 4]])
puts "m1 = " + m1.to_s + "\n"
puts "m * m1 = " + (m * m1).to_s
puts "m1 * m = " + (m1 * m).to_s
Desired Output:
v = 1 2 3
v1 = 2 3 4
v * v1 = 20
m =
1 2
1 2
1 2
v * m = 6 12
m1 =
1 2 3
2 3 4
m * m1 =
5 8 11
5 8 11
5 8 11
m1 * m =
6 12
9 18
length.times do |i|
yield #array[i], a[i]
end
In the above block, a is an instance of MyVector. You need to define the [] operator on it, probably something like:
def [](i)
#array[i]
end

Ruby - get bit range from variable

I have a variable and want to take a range of bits from that variable. I want the CLEANEST way to do this.
If x = 19767 and I want bit3 - bit8 (starting from the right):
100110100110111 is 19767 in binary.
I want the part in parenthesis 100110(100110)111 so the answer is 38.
What is the simplest/cleanest/most-elegant way to implement the following function with Ruby?
bit_range(orig_num, first_bit, last_bit)
PS. Bonus points for answers that are computationally less intensive.
19767.to_s(2)[-9..-4].to_i(2)
or
19767 >> 3 & 0x3f
Update:
Soup-to-nuts (why do people say that, anyway?) ...
class Fixnum
def bit_range low, high
len = high - low + 1
self >> low & ~(-1 >> len << len)
end
end
p 19767.bit_range(3, 8)
orig_num.to_s(2)[(-last_bit-1)..(-first_bit-1)].to_i(2)
Here is how this could be done using pure number operations:
class Fixnum
def slice(range_or_start, length = nil)
if length
start = range_or_start
else
range = range_or_start
start = range.begin
length = range.count
end
mask = 2 ** length - 1
self >> start & mask
end
end
def p n
puts "0b#{n.to_s(2)}"; n
end
p 0b100110100110111.slice(3..8) # 0b100110
p 0b100110100110111.slice(3, 6) # 0b100110
Just to show the speeds of the suggested answers:
require 'benchmark'
ORIG_NUMBER = 19767
def f(x,i,j)
b = x.to_s(2)
n = b.size
b[(n-j-1)...(n-i)].to_i(2)
end
class Fixnum
def bit_range low, high
len = high - low + 1
self >> low & ~(-1 >> len << len)
end
def slice(range_or_start, length = nil)
if length
start = range_or_start
else
range = range_or_start
start = range.begin
length = range.count
end
mask = 2 ** length - 1
self >> start & mask
end
end
def p n
puts "0b#{n.to_s(2)}"; n
end
n = 1_000_000
puts "Using #{ n } loops in Ruby #{ RUBY_VERSION }."
Benchmark.bm(21) do |b|
b.report('texasbruce') { n.times { ORIG_NUMBER.to_s(2)[(-8 - 1)..(-3 - 1)].to_i(2) } }
b.report('DigitalRoss string') { n.times { ORIG_NUMBER.to_s(2)[-9..-4].to_i(2) } }
b.report('DigitalRoss binary') { n.times { ORIG_NUMBER >> 3 & 0x3f } }
b.report('DigitalRoss bit_range') { n.times { 19767.bit_range(3, 8) } }
b.report('Philip') { n.times { f(ORIG_NUMBER, 3, 8) } }
b.report('Semyon Perepelitsa') { n.times { ORIG_NUMBER.slice(3..8) } }
end
And the output:
Using 1000000 loops in Ruby 1.9.3.
user system total real
texasbruce 1.240000 0.010000 1.250000 ( 1.243709)
DigitalRoss string 1.000000 0.000000 1.000000 ( 1.006843)
DigitalRoss binary 0.260000 0.000000 0.260000 ( 0.262319)
DigitalRoss bit_range 0.840000 0.000000 0.840000 ( 0.858603)
Philip 1.520000 0.000000 1.520000 ( 1.543751)
Semyon Perepelitsa 1.150000 0.010000 1.160000 ( 1.155422)
That's on my old MacBook Pro. Your mileage might vary.
Makes sense to define a function for that:
def f(x,i,j)
b = x.to_s(2)
n = b.size
b[(n-j-1)...(n-i)].to_i(2)
end
puts f(19767, 3, 8) # => 38
Expanding on the idea from DigitalRoss - instead of taking two arguments, you can pass a range:
class Fixnum
def bit_range range
len = range.last - range.first + 1
self >> range.first & ~(-1 >> len << len)
end
end
19767.bit_range 3..8

Resources