Consistent weighted mapping in Ruby - ruby

So I currently have the below method which randomly returns a string (of a known set of strings) based on a weighted probability (based on this):
def get_response(request)
responses = ['text1', 'text2', 'text3', 'text4', 'text5', 'text6']
weights = [5, 5, 10, 10, 20, 50]
ps = weights.map { |w| (Float w) / weights.reduce(:+) }
# => [0.05, 0.05, 0.1, 0.1, 0.2, 0.5]
weighted_response_hash = responses.zip(ps).to_h
# => {"text1"=>0.05, "text2"=>0.05, "text3"=>0.1, "text4"=>0.1, "text5"=>0.2, "text6"=>0.5}
response = weighted_response_hash.max_by { |_, weight| rand ** (1.0 / weight) }.first
response
end
Now instead of a random weighted output, I want the output to be consistent based on an input string while keeping the weighted probability of the response. So for example, a call such as:
get_response("This is my request")
Should always produce the same output, while keeping the weighted probability of the output text.
I think Modulo can be used here in some way, hash mapping to the same result but I'm kinda lost.

What #maxpleaner was trying to say with srand is
srand may be used to ensure repeatable sequences of pseudo-random numbers between different runs of the program.
So, if you seed the random generator, you will always get the same results back.
For example if you do
random = Random.new(request.hash)
response = weighted_response_hash.max_by { |_, weight| random.rand ** (1.0 / weight) }.first
you will always end up with the same response whenever you pass in the same request.
old code
3.times.collect { get_response('This is my Request') }
# => ["text6", "text1", "text6"]
3.times.collect { get_response('This is my Request 2') }
# => ["text6", "text4", "text5"]
new code, seeding the random
3.times.collect { get_response('This is my Request') }
# => ["text4", "text4", "text4"]
3.times.collect { get_response('This is my Request 2') }
# => ["text1", "text1", "text1"]
The output is still weighted, just now has some predictability:
randoms = 100.times.collect { |x| get_response("#{x}") }
randoms.group_by { |item| item }.collect { |key, values| [key, values.length / 100.0] }.sort_by(&:first)
# => [["text1", 0.03], ["text2", 0.03], ["text3", 0.08], ["text4", 0.11], ["text5", 0.27], ["text6", 0.48]]

Related

Project Euler 8 in Ruby

I know my code works to get the correct answer for 4 adjacent integers. But it's not working with 13.
The only thing I can think of is that it can be an issue with an unsigned int, but in Ruby I don't think I'd have that problem because it would change automatically into a Bignum class.
So that means that somewhere in my calculation I am wrong?
Please give me a hint.
# Euler 8
# http://projecteuler.net/index.php?section=problems&id=8
# Find the thirteen adjacent digits in the 1000-digit number
# that have the greatest product.
# What is the value of this product?
number = []
#split the integer as a string into an array
long_digit = "73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450"
long_digit.split("").map { |s| number << s.to_i }
#iterate through the array to find the 13 ajacent digits that have the largest product
largest_product = 0
a = 0
#stay within the bounds of the array
while number[a+12]
current_product = number[a] * number[a+1] * number[a+2] * number[a+3] * number[a+4] * number[a+5] * number[a+6] * number[a+7] * number[a+8] * number[a+9] * number[a+10] * number[a+11] * number[a+12]
if current_product > largest_product
largest_product = current_product
end
a = a + 1
end
puts largest_product
I think this solution is pretty clean and simple:
#!/usr/bin/env ruby
input = "
73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450"
.gsub(/\s+/, '')
puts input.chars
.map(&:to_i)
.each_cons(13)
.map { |seq| seq.reduce(:*) }
.max
gsub performs the trimming.
chars gets the characters.
map(&:to_i) maps all the chars to ints.
each_cons(13) gets blocks of consecutive numbers (https://ruby-doc.org/core-2.4.1/Enumerable.html#method-i-each_cons)
map { |seq| seq.reduce(:*) } is going to take each of the consecutive blocks and perform a reduce (multiplying all the numbers of each slice/consecutive block of numbers).
max gets the maximum value.
Issue seems to be due to lot of white space chars in the string long_digit that are become 0 in the array number, thus giving wrong results.
Here is a corrected and simplified version. After removing newlines and spaces using gsub, we now have a 1000 digit number and we get correct answer.
number = long_digit.gsub!(/\s/, '').split("").map{ |s| s.to_i }
n = 13
p number.each_cons(n).map{|a| a.reduce {|a, i| a = a * i }}.max
#=> 23514624000
First, let's fix the string:
long_digit.gsub!(/\s|\n/,'')
long_digit.size #=> 1000
We can speed this up by eliminating 13-character substrings that contain a zero:
shorter_digit_arr = long_digit.split('0').reject { |s| s.size < 13 }
#=> ["7316717653133",
# "6249192251196744265747423553491949349698352",
# "6326239578318",
# "18694788518438586156",
# "7891129494954595",
# "17379583319528532",
# "698747158523863",
# "435576689664895",
# "4452445231617318564",
# "987111217223831136222989342338",
# "81353362766142828",
# "64444866452387493",
# "1724271218839987979",
# "9377665727333",
# "594752243525849",
# "632441572215539753697817977846174",
# "86256932197846862248283972241375657",
# "79729686524145351",
# "6585412275886668811642717147992444292823",
# "863465674813919123162824586178664583591245665294765456828489128831426",
# "96245544436298123",
# "9878799272442849",
# "979191338754992",
# "559357297257163626956188267"]
Now, for each element of shorter_digit_arr, find the 13-character substring whose product of digits is greatest, then find the largest of those (shorter_digit_arr.size #=> 24) products. The main benefit of splitting the string into substrings in this way is that absence of zeroes allows us to perform the product calculations in a more efficient way than simply grinding out 12 multiplications for each substring:
res = shorter_digit_arr.map do |s|
cand = s[0,13].each_char.reduce(1) { |prod,t| prod * t.o_i }
best = { val: cand, offset: 0 }
(13...s.size).each do |i|
cand = cand*(s[i].to_i)/(s[i-13].to_i)
best = { val: cand, offset: i-12 } if cand > best[:val]
end
[best[:val], s[best[:offset],13]]
end.max_by(&:first)
#=> [23514624000, "5576689664895"]
puts "max_product: %d for: '%s'" % res
#=> max_product: 23514624000 for: '5576689664895'
The solution is the last 13 characters of:
s = shorter_digit_arr[7]
#=> "435576689664895"
The key here is the line:
cand = cand*(s[i].to_i)/(s[i-13].to_i)
which computes a 13-digit product by multiplying the "previous" 13-digit product by the digit added and dividing it by the digit dropped off.
In finding the maximum product for this element, the calculations are as follows:
s = "435576689664895"
cand = s[0,13].each_char.reduce(1) { |prod,t| prod * t.to_i }
#=> = "4355766896648".each_char.reduce(1) { |prod,t| prod * t.to_i }
# = 6270566400
best_val = { val: 6270566400, offset: 0 }
enum = (13...s.size).each
#=> #<Enumerator: 13...15:each>
The elements of this enumerator will be passed to the block by Enumerator#each. We can see what they are by converting enum to an array:
enum.to_a
#=> [13, 14]
We can use Enumerator#next to simulate the passing of the elements of enum to the block and their assignment to the block variable i.
Pass the first element of the enumerator (13) to the block:
i = enum.next
#=> 13
cand = cand*(s[i].to_i)/(s[i-13].to_i)
# = 6270566400*(s[13].to_i)/(s[0].to_i)
# = 6270566400*(9)/(4)
# = 14108774400
cand > best[:val]
#=> 14108774400 > 6270566400 => true
best = { val: cand, offset: i-12 }
#=> { val: 14108774400, offset: 1 }
Pass the second element (14) to the block:
i = enum.next
#=> 14
cand = cand*(s[i].to_i)/(s[i-13].to_i)
#=> = 14108774400*(s[14].to_i)/(s[1].to_i)
# = 14108774400*(5)/(3)
# = 23514624000
cand > best[:val]
#=> 23514624000 > 14108774400 => true
best = { val: 23514624000, offset: 2 }
All elements of the enumerator have now been passed to the block. We can confirm that:
i = enum.next
#=> StopIteration: iteration reached an end
The result (for shorter_digit_arr[7]) is:
[best[:val], s[best[:offset],13]]
#=> [23514624000, "435576689664895"[2,13]]
# [23514624000, "5576689664895"]

ruby adding range elements in each block

How can I add range elements in each block together. E.g.:
a = [[1..10, 13..20, 21..24], [34..39, 42..45], [50..55]]
outcome:
a = [[9+7+3],[5+3],[5]]
a = [20, 8, 5]
You can access the first and last elements of a range by the corresponding first and last methods. inject(:+) sums up the partial distances of all the ranges belonging to the same group.
a.map { |ran­ges| range­s.map { |rang­e| range­.last - range­.first }.inj­ect(:+) }
=> [19, 8, 5]
Or, even shorter, as suggested by tokland using Ruby 2.0:
a.map { |ran­ges| range­s.map(&:size).reduce(0, :+) }
Ruby 2.0:
a.map { |ranges| ranges.map { |r| r.size - 1 } .reduce(0, :+) }
Range class has a method called #size. Thus we can do as :
a.map { |ranges| ranges.inject(0) { |sum,rng| sum + rng.size - 1 } }

Calculating Median in Ruby

How do I calculate the median of an array of numbers using Ruby?
I am a beginner and am struggling with handling the cases of the array being of odd and even length.
Here is a solution that works on both even and odd length array and won't alter the array:
def median(array)
return nil if array.empty?
sorted = array.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
Similar to nbarraille's, but I find it a bit easier to keep track of why this one works:
class Array
def median
sorted = self.sort
half_len = (sorted.length / 2.0).ceil
(sorted[half_len-1] + sorted[-half_len]) / 2.0
end
end
half_len = number of elements up to and including (for array with odd number of items) middle of array.
Even simpler:
class Array
def median
sorted = self.sort
mid = (sorted.length - 1) / 2.0
(sorted[mid.floor] + sorted[mid.ceil]) / 2.0
end
end
If by calculating Median you mean this
Then
a = [12,3,4,5,123,4,5,6,66]
a.sort!
elements = a.count
center = elements/2
elements.even? ? (a[center] + a[center+1])/2 : a[center]
def median(array) #Define your method accepting an array as an argument.
array = array.sort #sort the array from least to greatest
if array.length.odd? #is the length of the array odd?
array[(array.length - 1) / 2] #find value at this index
else array.length.even? #is the length of the array even?
(array[array.length/2] + array[array.length/2 - 1])/2.to_f
#average the values found at these two indexes and convert to float
end
end
More correct solution with handling edge cases:
class Array
def median
sorted = self.sort
size = sorted.size
center = size / 2
if size == 0
nil
elsif size.even?
(sorted[center - 1] + sorted[center]) / 2.0
else
sorted[center]
end
end
end
There is a specs to prove:
describe Array do
describe '#median' do
subject { arr.median }
context 'on empty array' do
let(:arr) { [] }
it { is_expected.to eq nil }
end
context 'on 1-element array' do
let(:arr) { [5] }
it { is_expected.to eq 5 }
end
context 'on 2-elements array' do
let(:arr) { [1, 2] }
it { is_expected.to eq 1.5 }
end
context 'on odd-size array' do
let(:arr) { [100, 5, 2, 12, 1] }
it { is_expected.to eq 5 }
end
context 'on even-size array' do
let(:arr) { [7, 100, 5, 2, 12, 1] }
it { is_expected.to eq 6 }
end
end
end
I like to use Refinements, which is a safe way to Monkey Patch the ruby classes without collateral effects over the system.
The usage become much more cleaner than a new method.
With the Refinements you can monkey patch the Array class, implement the Array#median and this method will only be available inside the scope of the class that is using the refinement! :)
Refinements
module ArrayRefinements
refine Array do
def median
return nil if empty?
sorted = sort
(sorted[(length - 1) / 2] + sorted[length / 2]) / 2.0
end
end
end
class MyClass
using ArrayRefinements
# You can use the Array#median as you wish here
def test(array)
array.median
end
end
MyClass.new.test([1, 2, 2, 2, 3])
=> 2.0
def median(array)
half = array.sort!.length / 2
array.length.odd? ? array[half] : (array[half] + array[half - 1]) / 2
end
*If the length is even, you must add the middle point plus the middle point - 1 to account for the index starting at 0
def median(arr)
sorted = arr.sort
if sorted == []
return nil
end
if sorted.length % 2 != 0
result = sorted.length / 2 # 7/2 = 3.5 (rounded to 3)
return sorted[result] # 6
end
if sorted.length % 2 == 0
result = (sorted.length / 2) - 1
return (sorted[result] + sorted[result+1]) / 2.0 # (4 + 5) / 2
end
end
p median([5, 0, 2, 6, 11, 10, 9])
Here's a solution:
app_arry = [2, 3, 4, 2, 5, 6, 16].sort
# check array isn't empty
if app_arry.empty? || app_arry == ""
puts "Sorry, This will not work."
return nil
end
length = app_arry.length
puts "Array length = #{length}"
puts "Array = #{app_arry}"
if length % 2 == 0
# even number of elements
puts "median is #{(app_arry[length/2].to_f + app_arry[(length-1)/2].to_f)/2}"
else
# odd number of elements
puts "median is #{app_arry[(length-1)/2]}"
end
OUTPUT
Array length = 7
Array = [2, 3, 4, 2, 5, 6, 16]
median is 2
def median(array, already_sorted=false)
return nil if array.empty?
array = array.sort unless already_sorted
m_pos = array.size / 2
return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
There are many ways to do this, but for both performance and reliability, I suggest using the enumerable-statistics library created by Ruby committer mrkn.
https://github.com/mrkn/enumerable-statistics
require 'enumerable/statistics'
ary = [1,2,3,3,4]
ary.mean # => 2.6
ary.median # => 3
I think it's good:
#!/usr/bin/env ruby
#in-the-middle value when odd or
#first of second half when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
sorted[middle]
end
or
#in-the-middle value when odd or
#average of 2 middle when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
ary.size.odd? ? sorted[middle] : (sorted[middle]+sorted[middle-1])/2.0
end
I used sort_by rather than sort because it's faster: Sorting an array in descending order in Ruby.

Ruby array with an extra state

I'm trying to go through an array and add a second dimension for true and false values in ruby.
For example. I will be pushing on arrays to another array where it would be:
a = [[1,2,3,4],[5]]
I would like to go through each array inside of "a" and be able to mark a state of true or false for each individual value. Similar to a map from java.
Any ideas? Thanks.
You're better off starting with this:
a = [{ 1 => false, 2 => false, 3 => false, 4 => false }, { 5 => false }]
Then you can just flip the booleans as needed. Otherwise you will have to pollute your code with a bunch of tests to see if you have a Fixnum (1, 2, ...) or a Hash ({1 => true}) before you can test the flag's value.
Hashes in Ruby 1.9 are ordered so you wouldn't lose your ordering by switching to hashes.
You can convert your array to this form with one of these:
a = a.map { |x| Hash[x.zip([false] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = false } }
And if using nil to mean "unvisited" makes more sense than starting with false then:
a = a.map { |x| Hash[x.zip([nil] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = nil } }
Some useful references:
Hash[]
each_with_object
zip
Array *
If what you are trying to do is simply tag specific elements in the member arrays with boolean values, it is just a simple matter of doing the following:
current_value = a[i][j]
a[i][j] = [current_value, true_or_false]
For example if you have
a = [[1,2,3,4],[5]]
Then if you say
a[0][2] = [a[0,2],true]
then a becomes
a = [[1,2,[3,true],4],[5]]
You can roll this into a method
def tag_array_element(a, i, j, boolean_value)
a[i][j] = [a[i][j], boolean_value]
end
You might want to enhance this a little so you don't tag a specific element twice. :) To do so, just check if a[i][j] is already an array.
Change x % 2 == 0 for the actual operation you want for the mapping:
>> xss = [[1,2,3,4],[5]]
>> xss.map { |xs| xs.map { |x| {x => x % 2} } }
#=> [[{1=>false}, {2=>true}, {3=>false}, {4=>true}], [{5=>false}]]

can't convert Array into Integer

I'm trying to iterate through an array, #chem_species = ["H2", "S", "O4"] and multiply a constant times the amount of constants present: H = 1.01 * 2, S = 32.1 * 1 and so on. The constants are of course defined within the class, before the instance method.
The code I've constructed to do this does not function:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan({/\d+/)}
#mm = x[0] * y[0]
end
yields -> TypeError: can't convert Array into Integer
Any suggestions on how to better code this? Thank you for your insight in advance.
How about doing it all in one scan & map? The String#scan method always returns an array of the strings it matched. Look at this:
irb> "H2".scan /[A-Z]+|\d+/i
=> ["H", "2"]
So just apply that to all of your #chem_species using map:
irb> #chem_species.map! { |chem| chem.scan /[A-Z]+|\d+/i }
=> [["H", "2"], ["S"], ["O", "4"]]
OK, now map over #chem_species, converting each element symbol to the value of its constant, and each coefficient to an integer:
irb> H = 1.01
irb> S = 32.01
irb> O = 15.99
irb> #chem_species.map { |(elem, coeff)| self.class.const_get(elem) * (coeff || 1).to_i }
=> [2.02, 32.01, 63.96]
There's your molar masses!
By the way, I suggest you look up the molar masses in a single hash constant instead of multiple constants for each element. Like this:
MASSES = { :H => 1.01, :S => 32.01, :O => 15.99 }
Then that last map would go like:
#chem_species.map { |(elem, coeff)| MASSES[elem.to_sym] * (coeff || 1).to_i }
You have a syntax error in your code: Maybe it should be:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan(/\d+/)}
#mm = x[0] * y[0]
end
Have you looked at the output of #chem_species.map { |chem| chem.scan(/[A-Z]/)} (or the second one for that matter)? It's giving you an array of arrays, so if you really wanted to stick with this approach you'd have to do x[0][0].
Instead of mapping, do each
#chem_species.each { |c| c.scan(/[A-Z]/) }
Edit: just realized that that didn't work at all how I had thought it did, my apologies on a silly answer :P
Here's a way to multiply the values once you have them. The * operator won't work on arrays.
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = []
x.zip(y) { |a,b| res.push(a*b) }
res.inject(0) { |sum, v| sum += v}
# sum => 122
Or, cutting out the middle man:
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = 0
x.zip(y) { |a,b| res += (a*b) }
# res => 122
(one-liners alert, off-topic alert)
you can parse the formula directly:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/)
# -> [["H", "2"], ["S", ""], ["O", "4"]]
calculate partial sums:
aw = { 'H' => 1.01, 'S' => 32.07, 'O' => 16.00 }
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}
# -> [2.02, 32.07, 64.0]
total sum:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}.inject{|s,x| s+x}
# -> 98.09

Resources