Ruby: Is using `srand` in initializer thread safe? - ruby

Is the follow code thread safe? I am worried srand may be a variable that is not thread safe. I have a rails app and going to be using Puma with 5-10 threads. I do not want to cause issues.
class Test
def initialize(seed)
srand seed # => 1, 222, 222, 111
end # => :initialize
def letter
%w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample # => "g", "u"
end # => :letter
def letter1
%w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample # => "g", "u"
end # => :letter1
end # => :letter1
Test.new(222).letter # => "g"
Test.new(222).letter1 # => "g"
Test.new(111).letter # => "u"
Test.new(111).letter1 # => "u"

Depends what you mean by "thread-safe". The program will still work, Ruby won't enter in an inconsistent state and no data will be lost. However, the default random number generator is global, shared between threads; it will be up to thread timing to see which thread receives which random number. If your intention is that all threads just get random numbers from a single RNG, your code is fine - though the results might not be repeatable, which probably defeats the purpose of srand.
If you want to make sure that each Test only generates numbers independently (and repeatably), you want to have each Test have its own random number generator:
class Test
def initialize(seed)
#random = Random.new(seed)
end
def letter
%w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample(random: #random)
end
end
t1 = Test.new(111)
t2 = Test.new(222)
3.times.map { t1.letter }
# => ["u", "m", "u"]
3.times.map { t2.letter }
# => ["u", "m", "u"]

Related

Ruby map! does not change contained variable

Suppose I have an object x, and an array y=[x] which contains x. If I manipulate x, then y does not change:
x = 1 # => 1
y = [x] # => [1]
x = x+1 # => 2
x # => 2
y # => [1]
and if I change y, then x does not change.
x = 1 # => 1
y = [x] # => [1]
y.map!{|a| a+1} # => [2]
y # => [2]
x # => 1
Is there a way to have them change in parallel? It feels like when I map! over an array the underlying values should change.
first of all
x = x + 1
will create a new variable with an old name x
x = 1
y = [x]
x.object_id
# 3
y[0].object_id
# 3
x = x + 1
x.object_id
# 5
y[0].object_id
# 3
Second, numbers are immutable objects in Ruby (and Strings, for example, are mutable). So you just can't do what you want using number objects in Ruby. What you can do is slightly more obscure. You can create your own mutable object (container for a number) and use a reference to this object in your array.
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x.n += 1
y[0].n
#=> 2
You can go little bit further and to add more magic here to mimic numbers
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
def method_missing(m, *args, &blk)
#n = #n.send(m, *args, &blk)
self
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x += 1
y[0].n
#=> 2
But I would not encourage you to do any of this.
Just stick to the idea that numbers are immutable. And overall you should be careful with mutability.
map! is mutable for the array, not for its elements. It means that the array gets new values in place (i.e. it's assigned to original array). Elements inside the array are not mutated, but replaced by new elements.
If you want to change old values, you can iterate using each and call mutating methods on each element. You can see this with array of strings:
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.map!{|e| e.capitalize }
#=> ["A", "B"]
[a, b]
#=> ["a", "b"]
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.each{|e| e.capitalize! }
#=> ["A", "B"]
[a, b]
#=> ["A", "B"]
Note that it won't work for immutable objects and numbers are immutable, as #fl00r explained in his answer.

Ruby count adjacent duplicate elements in array

Given an array, say %w[ a b c a a b b c c c]. The method should return 3, as it is the maximum quantity of adjacent duplicates ( 3 'c' )
This is what I came up with so far:
def check_quantity_of_same_adjacent_elements(array)
max = 0
array.each_index do |i|
max += 1 if array[i] == array[i+1]
end
max
end
but it obviously don't work as it returns the number of all duplicates
%w[a b c a a b b c c c].chunk{|e| e}.map{|_, v| v.length}.max #=> 3

Combine multiple strings in array 1 to form array 2

I have an array of strings "A" and a target string "B". The B string
contains only characters from a to z and does not contain spaces. I need a ruby function
that returns the array of strings which represents all possible ways to form B from elements of Array A. The order in which combinations are returned is irrelevant. Words from A can be used multiple times.
Example:
A = [‘x’, ‘y’, ‘z’, 'yz',‘xy’, ‘xyz’]
B = ‘xyz’
method(A, B) => [x y z, x yz, xyz, xy z]
I looked into permutation method but can't get it to work.
Here's a recursive solution that doesn't iterate through the permutations:
def find_possible_combinations( list, str )
list.select { |a| str.start_with? a }.map do |a|
if a == str
[a]
else
find_possible_combinations( list-[a], str.slice(a.length..-1) )
.map { |c| "#{a} #{c}" }
end
end.flatten
end
puts "#{find_possible_combinations( %w( x y z yz xy xyz ), 'xyz' )}"
Output:
["x y z", "x yz", "xy z", "xyz"]
A = %w[x y z yz xy xyz]
B = "xyz"
SIZE = B.size
def combos
(1..SIZE).inject([]) {|c, n| c.concat(combos_by_size(n))}.reject(&:empty?)
end
# Select all combinations of size n having SIZE characters
def combos_by_size(n)
A.combination(n).to_a.inject([]) {|c, a| c.concat(matching_combos(a)) if a.join.size == SIZE; c}.reject(&:empty?)
end
# Select all matching combinations using all elements of array a
def matching_combos(a)
a.permutation.to_a.select {|p| p.join == B}
end
combos # => [["xyz"], ["x", "yz"], ["xy", "z"], ["x", "y", "z"]]

Why this weird value is assigned?

class MyClass
def fun
a = 2
b = 5
yield(a,b)
end
def self.fun2
puts self
end
end
m = 1
n = 2
mine = MyClass.new
mine.fun {|m| puts "m = #{m} n = #{n}"}
Here I deliberately do not match the number of parameters for yield, and the output is
test.rb:16: warning: multiple values for a block parameter (2 for 1)
from test.rb:5
m = 25 n = 2
Where does this 25 come from?
I suspect you to be in 1.8.x.
In which case, m will be [a, b]. Which is equivalent to [2, 5].
[2, 5].to_s
# => "25"
"m = #{[2, 5]} n = #{2}"
# => "m = 25 n = 2"
The 25 comes from a and b passed to m as an array.
When you puts it, that's the output you get in Ruby 1.8.
You could also try doing puts "m = #{m.inspect}...." or puts "m = #{m.class.to_s}"

make an object behave like an Array for parallel assignment in ruby

Suppose you do this in Ruby:
ar = [1, 2]
x, y = ar
Then, x == 1 and y == 2. Is there a method I can define in my own classes that will produce the same effect? e.g.
rb = AllYourCode.new
x, y = rb
So far, all I've been able to do with an assignment like this is to make x == rb and y = nil. Python has a feature like this:
>>> class Foo:
... def __iter__(self):
... return iter([1,2])
...
>>> x, y = Foo()
>>> x
1
>>> y
2
Yep. Define #to_ary. This will let your object be treated as an array for assignment.
irb> o = Object.new
=> #<Object:0x3556ec>
irb> def o.to_ary
[1, 2]
end
=> nil
irb> x, y = o
=> [1,2]
irb> x
#=> 1
irb> y
#=> 2
The difference between #to_a and #to_ary is that #to_a is used to try to convert
a given object to an array, while #to_ary is available if we can treat the given object as an array. It's a subtle difference.
Almost:
class AllYourCode
def to_a
[1,2]
end
end
rb = AllYourCode.new
x, y = *rb
p x
p y
Splat will try to invoke to_ary, and then try to invoke to_a. I'm not sure why you want to do this though, this is really a syntactical feature that happens to use Array in its implementation, rather than a feature of Array.
In other words the use cases for multiple assignment are things like:
# swap
x, y = y, x
# multiple return values
quot, rem = a.divmod(b)
# etc.
name, age = "Person", 100
In other words, most of the time the object being assigned from (the Array) isn't even apparent.
You can't redefine assignment, because it's an operator instead of a method. But if your AllYourCode class were to inherit from Array, your example would work.
When Ruby encounters an assignment, it looks at the right hand side and if there is more than one rvalue, it collects them into an array. Then it looks at the left hand side. If there is one lvalue there, it is assigned the array.
def foo
return "a", "b", "c" # three rvalues
end
x = foo # => x == ["a", "b", "c"]
If there is more than one lvalue (more specifically, if it sees a comma), it assigns rvalues successively and discards the extra ones.
x, y, z = foo # => x == "a", y == "b", z == "c"
x, y = foo # => x == "a", y == "b"
x, = foo # => x == "a"
You can do parallel assignment if an array is returned, too, as you have discovered.
def bar
["a", "b", "c"]
end
x = bar # => x == ["a", "b", "c"]
x, y, z = bar # => x == "a", y == "b", z == "c"
x, y = bar # => x == "a", y == "b"
x, = bar # => x == "a"
So in your example, if rb is an Array or inherits from Array, x and y will be assigned its first 2 values.

Resources