Why does the total object count by `ObjectSpace.count_objects` not change? - ruby

I get this result (Cf. https://ruby-doc.org/core-2.5.1/ObjectSpace.html#method-c-count_objects):
total = ObjectSpace.count_objects[:TOTAL]
new_object = "tonytonyjan"
ObjectSpace.count_objects[:TOTAL] - total # => 0
total = ObjectSpace.count_objects[:T_STRING]
new_object = "tonytonyjan"
ObjectSpace.count_objects[:T_STRING] - total # => 0
Please explain why the result is zero. Did new_object die just after the initialization?

Rather rely on each_object to give the status about live objects:
def foo
total = ObjectSpace.each_object(String).count
str = "kiddorails"
puts ObjectSpace.each_object(String).count - total
end
foo
#=> 1
Another thing to note: the above code snippet is not fullproof to give the detail about incremented String objects, since GC is enable and can kick in anytime. I would prefer this:
def foo
GC.enable # enables GC if not enabled
GC.start(full_mark: true, immediate_sweep: true, immediate_mark: false) # perform GC if required on current object space
GC.disable # disable GC to get the right facts below
total = ObjectSpace.each_object(String).count
100.times { "kiddorails" }
puts ObjectSpace.each_object(String).count - total
end
foo #=> 100

Related

Ruby oneliner for defining, iterating over array, adding and returning value?

I have this piece of code:
def total_balance
total = 0
users.each { |user| total += user.balance }
total
end
and i wonder if there is a shorter version/one liner for this?
it basically iterates over users and adds their balance together.
ty!
def total_balance
users.sum { |user| user.balance }
# or users.sum(&:balance)
# or users.map(&:balance).sum
# or users.reduce(0) { |total, user| total += user.balance }
# or users.inject(0) { |total, user| total += user.balance }
end
With the inject or reduce, I always get the accumulator and the object mixed up in the pipe variables. But that's easy to fix with debugging :D

How to make loops multithread in ruby?

How can I make these loops parallel with multithreading capability of ruby?
1.
from = 'a' * 1
to = 'z' * 3
("#{from}".."#{to}").each do |combination|
# ...
end
2.
##alphabetSet_tr.length.times do |i|
##alphabetSet_tr.length.times do |j|
##alphabetSet_tr.length.times do |k|
combination = ##alphabetSet_tr[i] + ##alphabetSet_tr[j] + ##alphabetSet_tr[k]
end
end
end
Note: ##alphabetSet_tr is an array which has 29 items
If you want to utilize your cores, you can use a Queue to divide the workload between a constant number of threads:
require 'thread'
queue = Queue.new
number_of_cores = 32
threads = (0..number_of_cores).map do
Thread.new do
combination = queue.pop
while combination
# do stuff with combination...
# take the next combination from the queue
combination = queue.pop
end
end
end
# fill the queue:
("#{from}".."#{to}").each do |combination|
queue << combination
end
# drain the threads
number_of_cores.times { queue << nil }
threads.each { |t| t.join }
If you fear that the size of the queue itself would be an issue - you can use SizedQueue which will block push operations if it gets larger than a certain size - capping its memory usage -
queue = SizedQueue.new(10000)
from = 'a' * 1
to = 'z' * 3
threads = ("#{from}".."#{to}").map do |combination|
Thread.new do
# ...
end
end
# In case you want the main thread waits all the child threads.
threads.each(&:join)

Merging Ranges using Sets - Error - Stack level too deep (SystemStackError)

I have a number of ranges that I want merge together if they overlap. The way I’m currently doing this is by using Sets.
This is working. However, when I attempt the same code with a larger ranges as follows, I get a `stack level too deep (SystemStackError).
require 'set'
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten!
sets_subsets = set.divide { |i, j| (i - j).abs == 1 } # this line causes the error
puts sets_subsets
The line that is failing is taken directly from the Ruby Set Documentation.
I would appreciate it if anyone could suggest a fix or an alternative that works for the above example
EDIT
I have put the full code I’m using here:
Basically it is used to add html tags to an amino acid sequence according to some features.
require 'set'
def calculate_formatting_classes(hsps, signalp)
merged_hsps = merge_ranges(hsps)
sp = format_signalp(merged_hsps, signalp)
hsp_class = (merged_hsps - sp[1]) - sp[0]
rank_format_positions(sp, hsp_class)
end
def merge_ranges(ranges)
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten
end
def format_signalp(merged_hsps, sp)
sp_class = sp - merged_hsps
sp_hsp_class = sp & merged_hsps # overlap regions between sp & merged_hsp
[sp_class, sp_hsp_class]
end
def rank_format_positions(sp, hsp_class)
results = []
results += sets_to_hash(sp[0], 'sp')
results += sets_to_hash(sp[1], 'sphsp')
results += sets_to_hash(hsp_class, 'hsp')
results.sort_by { |s| s[:pos] }
end
def sets_to_hash(set = nil, cl)
return nil if set.nil?
hashes = []
merged_set = set.divide { |i, j| (i - j).abs == 1 }
merged_set.each do |s|
hashes << { pos: s.min.to_i - 1, insert: "<span class=#{cl}>" }
hashes << { pos: s.max.to_i - 0.1, insert: '</span>' } # for ordering
end
hashes
end
working_hsp = [Range.new(7, 136), Range.new(143, 178)]
not_working_hsp = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
sp = Range.new(1, 20).to_set
# working
results = calculate_formatting_classes(working_hsp, sp)
# Not Working
# results = calculate_formatting_classes(not_working_hsp, sp)
puts results
Here is one way to do this:
ranges = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
ranges.size.times do
ranges = ranges.sort_by(&:begin)
t = ranges.each_cons(2).to_a
t.each do |r1, r2|
if (r2.cover? r1.begin) || (r2.cover? r1.end) ||
(r1.cover? r2.begin) || (r1.cover? r2.end)
ranges << Range.new([r1.begin, r2.begin].min, [r1.end, r2.end].max)
ranges.delete(r1)
ranges.delete(r2)
t.delete [r1,r2]
end
end
end
p ranges
#=> [73..2914, 3203..3241]
The other answers aren't bad, but I prefer a simple recursive approach:
def merge_ranges(*ranges)
range, *rest = ranges
return if range.nil?
# Find the index of the first range in `rest` that overlaps this one
other_idx = rest.find_index do |other|
range.cover?(other.begin) || other.cover?(range.begin)
end
if other_idx
# An overlapping range was found; remove it from `rest` and merge
# it with this one
other = rest.slice!(other_idx)
merged = ([range.begin, other.begin].min)..([range.end, other.end].max)
# Try again with the merged range and the remaining `rest`
merge_ranges(merged, *rest)
else
# No overlapping range was found; move on
[ range, *merge_ranges(*rest) ]
end
end
Note: This code assumes each range is ascending (e.g. 10..5 will break it).
Usage:
ranges = [ 73..856, 82..1145, 116..2914, 3203..3241 ]
p merge_ranges(*ranges)
# => [73..2914, 3203..3241]
ranges = [ 0..10, 5..20, 30..50, 45..80, 50..90, 100..101, 101..200 ]
p merge_ranges(*ranges)
# => [0..20, 30..90, 100..200]
I believe your resulting set has too many items (2881) to be used with divide, which if I understood correctly, would require 2881^2881 iterations, which is such a big number (8,7927981983090337174360463368808e+9966) that running it would take nearly forever even if you didn't get stack level too deep error.
Without using sets, you can use this code to merge the ranges:
module RangeMerger
def merge(range_b)
if cover?(range_b.first) && cover?(range_b.last)
self
elsif cover?(range_b.first)
self.class.new(first, range_b.last)
elsif cover?(range_b.last)
self.class.new(range_b.first, last)
else
nil # Unmergable
end
end
end
module ArrayRangePusher
def <<(item)
if item.kind_of?(Range)
item.extend RangeMerger
each_with_index do |own_item, idx|
own_item.extend RangeMerger
if new_range = own_item.merge(item)
self[idx] = new_range
return self
end
end
end
super
end
end
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
new_ranges = Array.new
new_ranges.extend ArrayRangePusher
ranges.each do |range|
new_ranges << range
end
puts ranges.inspect
puts new_ranges.inspect
This will output:
[73..856, 82..1145, 116..2914, 3203..3241]
[73..2914, 3203..3241]
which I believe is the intended output for your original problem. It's a bit ugly, but I'm a bit rusty at the moment.
Edit: I don't think this has anything to do with your original problem before the edits which was about merging ranges.

How to count how many times an iterator will call yield?

I have a method, foo, that yields objects. I want to count the number of objects it yields.
I have
def total_foo
count = 0
foo { |f| count += 1}
count
end
but there's probably a better way. Any ideas for this new Rubyist?
Here's the definition for foo (it's a helper method in Rails):
def foo(resource=#resource)
resource.thingies.each do |thingy|
bar(thingy) { |b| yield b } # bar also yields objects
end
end
Any method that calls yield can be used to build an Enumerator object, on which you can call count, by means of the Object#to_enum method. Remember that when you call count the iterator is actually executed so it should be free of side effects! Following a runnable example that mimics your scenario:
#resources = [[1,2], [3,4]]
def foo(resources = #resources)
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo { |i| puts i }
# Output:
# 1
# 2
# 3
# 4
to_enum(:foo).count
# => 4
You can pass an argument to foo:
to_enum(:foo, [[5,6]]).count
# => 2
Alternatively you can define foo to return an Enumerator when it's called without a block, this is the way stdlib's iterators work:
def foo(resources = #resources)
return to_enum(__method__, resources) unless block_given?
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo.count
# => 4
foo([[1,2]]).count
# => 2
foo([[1,2]]) { |i| puts i }
# Output:
# 1
# 2
You can pass a block to to_enum that is called when you call size on the Enumerator to return a value:
def foo(resources = #resources)
unless block_given?
return to_enum(__method__, resources) do
resources.map(&:size).reduce(:+) # thanks to #Ajedi32
end
end
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo.size
# => 4
foo([]).size
# => 0
In this case using size is sligthly faster than count, your mileage may vary.
Assuming you otherwise only care about the side-effect of foo, you could have foo itself count the iterations:
def foo(resource=#resource)
count = 0
resource.thingies.each do |thingy|
bar(thingy) do |b|
count += 1
yield b
end # bar also yields objects
end
count
end
And then:
count = foo { |f| whatever... }
You can also ignore the return value if you choose, so just:
foo { |f| whatever... }
In cases you don't care what the count is.
There may be better ways to handle all of this depending upon the bigger context.

Problems with Ruby Arrays

I am currently writing a small ruby class that is intended to store the amount of times a randomly generated number is seen within an array, along with the value that is seen itself.
I am trying to do the following to add the value along with a default times seen as 1 to the array. The script should check to see if the value has been included within the array and if so, increment the times this value has been seen by 1
However I am recieving duplicate values, which shouldnt happen - as the code should only allow a value to be stored once and if the value is already in the memory array, increment it by one.
The code is attatched below, if anyone can suggest what I am doing wrong it would be awesome.
Cheers
Martin
#memory = Array.new
def store(value)
foundflag = false
#memory.each do |array|
if value == array[0]
#Incrementing value timesseen
array[1] = array[1]+1
#Value found, changing found flag
foundflag = true
#Loop break
break
end
end
if foundflag != true then
#memory.push([value,1])
end
end
store(5)
Full script (untidy!)
class STAT
def initialize()
#STAT memory settings
#memory = Array.new
#Prediction settings
#predictions = 0
#sucessfulpredictions = 0
end
#STAT main functions
def store(value)
foundflag = false
#memory.each do |array|
if value == array[0]
#Incrementing value timesseen
array[1] = array[1]+1
#Value found, changing found flag
foundflag = true
#Loop break
break
end
end
if foundflag != true then
#memory.push([value,1])
end
end
def predict(nosimulations)
#Generate random value less than the total memory size
randomvalue = rand(total)+1
count = 0
#memory.each do |array|
value = array[0]
timesseen = array[1]
if randomvalue <= count + timesseen
puts "Predicted value #{value}"
end
count = count + array[1]
end
end
def simulate(nosimulations)
count = 1
while count <= nosimulations
#Generating a random number
randomnumber = rand(100)+1
#Storing the random number
store(randomnumber)
#Print simulation details#
puts "Running simulation #{count} of #{nosimulations}"
puts "Generated random number: #{randomnumber}"
#Incrementing count
count = count + 1
end
end
#STAT technical functions
def inspect()
#Print memory information message
puts "Memory information:"
#memory.each do |array|
value = array[0]
timesseen = array[1]
puts "Value #{value} - times seen: #{timesseen}/#{total}"
end
end
def total()
total = 0
#memory.each do |array|
total = total + array[1]
end
return total
end
#STAT load/save functions
def load(filename)
#Default engine to be loaded
enginename = "#{filename}.stat"
#Print checking for saved engine message
puts "Checking for saved memory file..."
#Checking for saved engine
if File.exists?(enginename)
#Print loading engine message
puts "Loading memory..."
#memory = Marshal.load File.read(enginename)
#Print engine loaded message
puts "Engine loaded"
else
#Print memory not found message
puts "Cannot load memory, no memory file found"
end
end
def save(filename)
#Default name for engine to be saved
enginename = "#{filename}.stat"
#Print saving engine message
puts "Saving memory..."
#Saving engine to specified file
serialized_array = Marshal.dump(#memory)
savefile = File.new(enginename,"w")
savefile.write(serialized_array)
savefile.close
#Print engine saved message
puts "Memory saved"
end
end
#STAT class test software
stat = STAT.new
filename = "test"
#Load
stat.load(filename)
#Simulate
stat.simulate(1000000)
#Testing
#stat.store(5)
#stat.store(5)
#Inspect
stat.inspect
#Predict
#stat.predict(1000000)
#Save
stat.save(filename)
Use:
occurrences = Hash[ my_array.group_by{ |o| o }.map{ |o,a| [o,a.length] } ]
to get a hash mapping each value to the number of times it occurs. To calculate it yourself:
occurrences = Hash.new(0)
my_array.each{ |o| occurrences[o]+=1 }
See the docs if you don't know what these methods do.
You are working too hard. You want to store the amount of times a randomly generated number is seen within an array, along with the value that is seen itself.
How can Ruby help you with that? Since you are working with an array, glance through the available methods and yes, there it is: count.
ar = [1,2,1,3,2,4,3,2,6,7,8,7]
rn = rand(10)
ar.count(rn)

Resources