call function until (with call limit) - ruby-on-rails

I have a function that generates random output (string).
I need to call that function until I get 3 different outputs (strings).
What is the most elegant way to generate array with 3 unique strings by calling the function, with the limit how many times the function can be called if the output is not generated in specified number of attempts?
Here's what I currently have:
output = []
limit_calls = 5
limit_calls.times do |i|
str = generate_output_function
output.push str
break if output.uniq.size > 2
end
Can this be beautified / shortened to 1 line? I'm pretty sure in ruby.. :)
Thanks

Using a set makes it (a bit) easier:
require 'set'
output = Set.new
limit_calls = 5
call_count = 0
while output.size < 3 and call_count < limit_calls
output << generate_output_function
call_count += 1
end
output
or with an array
output = []
limit_calls = 5
while output.size < limit_calls and output.uniq.size < 3
output << generate_output_function
end
output.uniq
UPDATE with the call limit. Seems like the Array version wins! Thanks Iain!
Will also ponder a version using inject.
UPDATE 2 - with inject:
5.times.inject([]) { |a, el| a.uniq.size < 3 ? a << generate_output_function : a }
there is your oneliner. I am not sure I prefer it cause it is a bit hard to follow.....

Froderik's answer missed out the call_limit requirement. What about a function like...
def unique_string_array(call_limit)
output = []
calls = 0
until (output.size == 3 || calls == call_limit) do
(output << generate_output_function).uniq! && calls+=1
end
output
end
It isn't a one-liner but it is readable... with this implementation, you may end up with arrays less than size 3. The most important thing is that you have a test that asserts the behaviour you want! (in order to test this thoroughly you'll have to stub out the call to generate_output_function)

Related

Optimizing finding and counting proper divisors in ruby

This code needs to run under 7000ms or it times out and I am trying to learn ruby so I am here to see if anyone has any ideas that could optimize this code. Or if you can just let me know which functions in this code take the most time so I can concentrate on the parts that will do the most good.
The questions to solve is that you have to tell if the number of divisors for any umber is odd or even.
For n=12 the divisors are [1,2,3,4,6,12] – 'even'
For n=4 the divisors are [1,2,4] – 'odd'
Any help is greatly appreciated,
Thanks.
def oddity(n)
div(n) % 2 == 0 ? (return 'even'): (return 'odd')
end
def div(num)
divs = []
(1..num).each{|x| if (num % x == 0) then divs << x end}
return divs.length
end
The key observation here is that you need only the number of divisors, rather than the divisors themselves. Thus, a fairly simple solution is to decompose the number to primes, and check how many combinations can we form.
require 'mathn'
def div(num)
num.prime_division.inject(1){ |prod, n| prod *= n[1] + 1 }
end
prime_division returns a list of pairs, where the first is the prime and the second is its exponent. E.g.:
12.prime_division
=> [[2, 2], [3, 1]]
We simply multiply the exponents, adding 1 to each, to account for the case where this prime wasn't taken.
Since performance is an issue, let's compare the OP's solution with #standelaune's and #dimid's.
require 'prime'
require 'fruity'
n = 100_000
m = 20
tst = m.times.map { rand(n) }
#=> [30505, 26103, 53968, 24108, 78302, 99141, 22816, 67504, 10149, 28406,
# 18294, 92203, 73157, 5444, 24928, 65154, 24850, 64219, 68310, 64951]
def op(num) # Alex
divs = []
(1..num).each { |x| if (num % x == 0) then divs << x end }
divs.length
end
def test_op(tst) # Alex
tst.each { |n| op(n) }
end
def pd(num) # divid
num.prime_division.inject(1){ |prod, n| prod *= n[1] + 1 }
end
def test_pd(tst) #divid
tst.each { |n| nfacs_even?(n) }
end
def div(num) # standelaune
oddity = false
(1..num).each{|x| if (num % x == 0) then oddity = !oddity end}
oddity ? "odd" : "even"
end
def test_div(tst) # standelaune
tst.each { |n| div(n) }
end
compare do
_test_op { test_op tst }
_test_div { test_div tst }
_test_pd { test_pd tst }
end
Running each test 16 times. Test will take about 56 seconds.
_test_pd is faster than _test_div by 480x ± 100.0
_test_div is similar to _test_op
I'm not suprised that divid's method smokes the others, as prime_division uses (an instance of) the default prime generator, Prime::Generator23, That generator is coded in C and is fast relative to other generators in Prime subclasses.
You could solve this by optimising your algorithm.
You don't have to check all numbers below the number you are examining. It is enough to split your number in to it´s prime components. Then it is a simple matter of combinatorics to determine how many possible divisors there are.
One way to get all prime components could be:
PRIME_SET = [2,3,5,7,11,13,17,19]
def factorize(n)
cut_off = Math.sqrt(n)
parts = []
PRIME_SET.each do |p|
return parts if p > cut_off
if n % p == 0
n = n/p
parts << p
redo
end
end
raise 'To large number for current PRIME_SET'
end
Then computing the number of possible can be done in a number of different ways and there are probably ways of doing it without even computing them. But here is a naive implementation.
def count_possible_divisors(factors)
divisors = Set.new
(1..factors.length-1).each do |i|
factors.combination(i).each do |comb|
divisors.add(comb.reduce(1, :*))
end
end
divisors.length + 2 # plus 2 for 1 and n
end
This should result in less work than what you are doing. But for large numbers this is a hard task to achieve.
If you want to stick with your algorithm, here is an optimization.
def div(num)
oddity = false
(1..num).each{|x| if (num % x == 0) then oddity = !oddity end}
oddity ? "odd" : "even"
end

Is it possible to insert one variable's value into the name of another variable?

Let's say I have a loop repeating 1000 times.
Previous to the loop, I set 1000 variables, like so:
#n1 = "blabla"
#n2 = "blabla"
#n3 = "blabla"
#n4 = "blabla"
...
I also have a variable #count that counts which iteration the loop is on, ie. it starts at 1 and increases by 1 with each loop. What I want to do is print #n1 if #count = 1, print #n2 if #count = 2, and so forth. In other words, I want ruby to use the value of #count to decide which #n_ variable to use. I don't want to use conditional statements, because I'd need 1000 of them.
Something like this:
#count = 1
if #count < 1001
puts #("n + #count")
#count = #count + 1
end
Is there a way to do this?
Let's say you have an instance called foo, with a thousand instance variables, to loop through them all you could do:
foo.instance_variables.each do |v|
p foo.instance_variable_get(v)
end
That said, you can also use the string name to fetch them:
1000.times do |count|
p foo.instance_variable_get("#n#{count}")
end
Yes, you can do do this:
if #count < 1001
instance_variable_set("##{#count}", #count + 1)
end
It would be more idiomatic to store in a hash, e.g.
h = {}
if #count < 1001
h[#count] = #count + 1
end
While you can use something like instance_variable_get, you would usually use an array or an hash to store your strings:
n = ["blabla", "blabla", "blabla", ... ]
count = 0
if count < 1000
puts n[count]
count += 1
end

Getting the number of same elements in both arrays in Ruby

I need to count the number of values that both arrays have.
def process_2arrays(arr1, arr2)
length1 = arr1.count
length2 = arr2.count
arr3 = []
i = 0
while length1 >= i do
ci = arr1[i]
if arr2.include?(ci)
arr3 << ci
damn = arr3.count
i = i + 1
end
return [(damn), (2), (3), (4)]
end
end
When I pass the values to the function it returns [nil, 2, 3, 4]
Whats the problem here?
To find elements that exist in both arrays, use the set intersection method &.
http://ruby-doc.org/core-2.2.0/Array.html#method-i-26
def count_same_elements(a1,a2)
(array1 & array2).length
end
Example
count_same_elements([1,2,3,4],[2,3,4,5])
=> 3
damn is initialized within a do .. end block, specifically the while block. Therefore, its value will live within that scope, and when you call the variable outside the block its value is nil.
If you want to preserve the value, you must initialize the variable to nil outside the block.
i = 0
damn = nil
...
As a side note, your code is lacking the most basic Ruby standards. In Ruby you generally use an iterator, not the while. Moreover, you don't use the return at the end of a method.
This is how you would write your method in Ruby using the iterators and taking advantage of some methods from the core library.
def process_2arrays(arr1, arr2)
arr3 = arr1.select { |e| arr2.include?(e) }
[arr3.size, 2, 3, 4]
end
Changing completely approach, you can use
def process_2arrays(arr1, arr2)
(arr1 & arr2).size
end

ruby looping question

I want to make a loop on a variable that can be altered inside of the loop.
first_var.sort.each do |first_id, first_value|
second_var.sort.each do |second_id, second_value_value|
difference = first_value - second_value
if difference >= 0
second_var.delete(second_id)
else
second_var[second_id] += first_value
if second_var[second_id] == 0
second_var.delete(second_id)
end
first_var.delete(first_id)
end
end
end
The idea behind this code is that I want to use it for calculating how much money a certain user is going to give some other user. Both of the variables contain hashes. The first_var is containing the users that will get money, and the second_var is containing the users that are going to pay. The loop is supposed to "fill up" a user that should get money, and when a user gets full, or a user is out of money, to just take it out of the loop, and continue filling up the rest of the users.
How do I do this, because this doesn't work?
Okay. What it looks like you have is two hashes, hence the "id, value" split.
If you are looping through arrays and you want to use the index of the array, you would want to use Array.each_index.
If you are looping through an Array of objects, and 'id' and 'value' are attributes, you only need to call some arbitrary block variable, not two.
Lets assume these are two hashes, H1 and H2, of equal length, with common keys. You want to do the following: if H1[key]value is > than H2[key]:value, remove key from H2, else, sum H1:value to H2:value and put the result in H2[key].
H1.each_key do |k|
if H1[k] > H2[k] then
H2.delete(k)
else
H2[k] = H2[k]+H1[k]
end
end
Assume you are looping through two arrays, and you want to sort them by value, and then if the value in A1[x] is greater than the value in A2[x], remove A2[x]. Else, sum A1[x] with A2[x].
b = a2.sort
a1.sort.each_index do |k|
if a1[k] > b[k]
b[k] = nil
else
b[k] = a1[k] + b[k]
end
end
a2 = b.compact
Based on the new info: you have a hash for payees and a hash for payers. Lets call them ees and ers just for convenience. The difficult part of this is that as you modify the ers hash, you might confuse the loop. One way to do this--poorly--is as follows.
e_keys = ees.keys
r_keys = ers.keys
e = 0
r = 0
until e == e_keys.length or r == r_keys.length
ees[e_keys[e]] = ees[e_keys[e]] + ers[r_keys[r]]
x = max_value - ees[e_keys[e]]
ers[r_keys[r]] = x >= 0 ? 0 : x.abs
ees[e_keys[e]] = [ees[e_keys[e]], max_value].min
if ers[r_keys[r]] == 0 then r+= 1 end
if ees[e_keys[e]] == max_value then e+=1 end
end
The reason I say that this is not a great solution is that I think there is a more "ruby" way to do this, but I'm not sure what it is. This does avoid any problems that modifying the hash you are iterating through might cause, however.
Do you mean?
some_value = 5
arrarr = [[],[1,2,5],[5,3],[2,5,7],[5,6,2,5]]
arrarr.each do |a|
a.delete(some_value)
end
arrarr now has the value [[], [1, 2], [3], [2, 7], [6, 2]]
I think you can sort of alter a variable inside such a loop but I would highly recommend against it. I'm guessing it's undefined behaviour.
here is what happened when I tried it
a.each do |x|
p x
a = []
end
prints
1
2
3
4
5
and a is [] at the end
while
a.each do |x|
p x
a = []
end
prints nothing
and a is [] at the end
If you can I'd try using
each/map/filter/select.ect. otherwise make a new array and looping through list a normally.
Or loop over numbers from x to y
1.upto(5).each do |n|
do_stuff_with(arr[n])
end
Assuming:
some_var = [1,2,3,4]
delete_if sounds like a viable candidate for this:
some_var.delete_if { |a| a == 1 }
p some_var
=> [2,3,4]

Best way to analyse data using ruby

I would like to analyse data in my database to find out how many times certain words appear.
Ideally I would like a list of the top 20 words used in a particular column.
What would be the easiest way of going about this.
Create an autovivified hash and then loop through the rows populating the hash and incrementing the value each time you get the same key (word). Then sort the hash by value.
A word counter...
I wasn't sure if you were asking how to get rails to work on this or how to count words, but I went ahead and did a column-oriented ruby wordcounter anyway.
(BTW, at first I did try the autovivified hash, what a cool trick.)
# col: a column name or number
# strings: a String, Array of Strings, Array of Array of Strings, etc.
def count(col, *strings)
(#h ||= {})[col = col.to_s] ||= {}
[*strings].flatten.each { |s|
s.split.each { |s|
#h[col][s] ||= 0
#h[col][s] += 1
}
}
end
def formatOneCol a
limit = 2
a.sort { |e1,e2| e2[1]<=>e1[1] }.each { |results|
printf("%9d %s\n", results[1], results[0])
return unless (limit -= 1) > 0
}
end
def formatAllCols
#h.sort.each { |a|
printf("\n%9s\n", "Col " + a[0])
formatOneCol a[1]
}
end
count(1,"how now")
count(1,["how", "now", "brown"])
count(1,[["how", "now"], ["brown", "cow"]])
count(2,["you see", "see you",["how", "now"], ["brown", "cow"]])
count(2,["see", ["see", ["see"]]])
count("A_Name Instead","how now alpha alpha alpha")
formatAllCols
$ ruby count.rb
Col 1
3 how
3 now
Col 2
5 see
2 you
Col A_Name Instead
3 alpha
1 how
$
digitalross answer looks too verbose to me, also, as you tag ruby-on-rails and said you use DB.. i'm assuming you need an activerecord model so i'm giving you a full solution
in your model:
def self.top_strs(column_symbol, top_num)
h = Hash.new(0)
find(:all, :select => column_symbol).each do |obj|
obj.send(column_symbol).split.each do |word|
h[word] += 1
end
end
h.map.sort_by(&:second).reverse[0..top_num]
end
for example, model Comment, column body:
Comment.top_strs(:body, 20)

Resources