I have array of objects form AR
I want to rarefy them, with limit.
Current method looks like:
def rarefied_values(limit = 200)
all_values = self.values.all
rarefied_values = []
chunk_size = (all_values.size / limit.to_f).ceil
if all_values.size > limit
all_values.each_slice(chunk_size) do |chunk|
rarefied_values.push(chunk.first)
end
return rarefied_values
else
return all_values
end
end
Any hints for refactoring?
def rarefied_values(limit = 200)
all_values = values.all
return all_values unless all_values.size > limit
chunk_size = all_values.size / limit
(0...limit).map{|i| all_values[i*chunk_size]}
end
Some general points in refactoring in ruby
self can be omitted usually. In a few cases, you cannot, for example self.class. In this case, self.values.all => values.all
If one of the conditioned procedures is much simpler compared to the others, then place that simple case first, and get rid of it from the rest of the code using return. In this case, return all_values unless all_values.size > limit
In general, when you need nested conditions, design it so that cases with simpler procedures split off eariler, and the complicated cases are placed toward the end.
Let the code be lazy as possible. In this case, rarefied_values = [] is not necessary if all_values.size > limit. So put that in the conditioned section.
Here's a naïve refactor, keeping your same methods, but removing the explicit return calls and only performing certain transformations if necessary:
def rarefied_values(limit = 200)
all_values = self.values.all
if all_values.size <= limit
all_values
else
chunk_size = (all_values.size / limit.to_f).ceil
[].tap{ |rare| all_values.each_slice(chunk_size){ |c| rare << c.first } }
end
end
Here's a faster, more terse version:
def rarefied_values(limit = 200)
all_values = self.values.all
if (size = all_values.size) <= limit
all_values
else
all_values.values_at(*0.step(size-1,(size.to_f/limit).ceil))
end
end
Related
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
Let's say i have two relation arrays of a user's daily buy and sell.
how do i iterate through both of them using .each and still let the the longer array run independently once the shorter one is exhaused. Below i want to find the ratio of someone's daily buys and sells. But can't get the ratio because it's always 1 as i'm iterating through the longer array once for each item of the shorter array.
users = User.all
ratios = Hash.new
users.each do |user|
if user.buys.count > 0 && user.sells.count > 0
ratios[user.name] = Hash.new
buy_array = []
sell_array = []
date = ""
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at)
daily_buy.each do |buy|
daily_sell.each do |sell|
if buy[0].to_date == sell[0].to_date
date = buy[0].to_date
buy_array << buy[1]
sell_array << sell[1]
end
end
end
ratio_hash[user.name][date] = (buy_array.length.round(2)/sell_array.length)
end
end
Thanks!
You could concat both arrays and get rid of duplicated elements by doing:
(a_array + b_array).uniq.each do |num|
# code goes here
end
Uniq method API
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at
buys_and_sells = daily_buy + daily_sell
totals = buys_and_sells.inject({}) do |hsh, transaction|
hsh['buys'] ||= 0;
hsh['sells'] ||= 0;
hsh['buys'] += 1 if transaction.is_a?(Buy)
hsh['sells'] += 1 if transaction.is_a?(Sell)
hsh
end
hsh['buys']/hsh['sells']
I think the above might do it...rather than collecting each thing in to separate arrays, concat them together, then run through each item in the combined array, increasing the count in the appropriate key of the hash returned by the inject.
In this case you can't loop them with each use for loop
this code will give you a hint
ar = [1,2,3,4,5]
br = [1,2,3]
array_l = (ar.length > br.length) ? ar.length : br.length
for i in 0..array_l
if ar[i] and br[i]
puts ar[i].to_s + " " + br[i].to_s
elsif ar[i]
puts ar[i].to_s
elsif br[i]
puts br[i].to_s
end
end
I'm writing a forum application in Rails and I'm stuck on limiting nested quotes.
I'm try to use regex and recursion, going down to each matching tag, counting the levels and if the current level is > max, deleting everything inside of it. Problem is that my regex is only matching the first [ quote ] with the first seen [ /quote ], and not the last as intended.
The regex is just a slight tweak of what was given in the docs of the custom bbcode library I'm using (I know very little about regex, I've tried to learn as much as I can in the past couple days but I'm still stuck). I changed it so it'd include [quote], [quote=name] and [quote=name;222] . Could someone examine my code and let me know what the problem could be? I'd appreciate it lots.
def remove_nested_quotes(post_string, max_quotes, count)
result = post_string.match(/\[quote(:.*)?(?:)?(.*?)(?:)?\](.*?)\[\/quote\1?\]/mi)
if result.nil?
return false
elsif (count = count+1) > max_quotes
full_str = result[0]
offset_beg = result.begin(3)
offset_end = result.end(3)
excess_quotes = full_str[offset_beg ..offset_end ]
new_string = full_str.slice(excess_quotes )
return new_string
else
offset_beg = result.begin(3)
offset_end = result.end(3)
full_str = result[0]
inner_string = full_str[offset_beg..offset_end]
return remove_nested_quotes(inner_string , max, count)
end
end
I mean something like
counter = 0
max = 5
loop do
matched = false
string.match /endquote|quote/ do |match|
matched = true
if endquote matched
counter -= 1
else # quote matched
counter += 1
end
if counter > max
# Do something, break or return
else
string = match.post_match
end
end
break unless matched
end
in ruby/rails3, I need to do some heavy text parsing to find a certain string. Right now I'm doing something like the following:
extract_type1 = body.scan(/(stuff)/m).size
extract_type2 = body.scan(/(stuff)/m).size
extract_type3 = body.scan(/(stuff)/m).size
extract_type4 = body.scan(/(stuff)/m).size
extract_type5 = body.scan(/(stuff)/m).size
if extract_type1 > 0
elsif extract_type2 > 0
elsif extract_type3 > 0
elsif extract_type4 > 0
elsif extract_type5 > 0
The problem here is that I keep needing to add extract types based on the app. And that results in a lot of processing when the case occurs that extract_type1 >0 and the rest aren't needed.
But at the same time, it's nice and clean to have the extract logic separated from the if block as that would be busy messy and hard to read.
Any thoughts on how to optimize this while not compromising readability?
Thanks
what about storing all your "keywords" you are searching for in an array and iterate over it like:
stuff = ["stuff1", "stuff2"]
stuff.each do |c_stuff|
if body.scan(/(#{Regexp.escape(c_stuff)})/m).size > 0
# do something
# break the loop
break
end
end
Edit: If you need the index of the element, you can use each_with_index do |c_stuff, c_index|
Lazy evaluation might work for you; just convert your extract_X variables to lambdas so that the values are computed on use:
extract_type1 = lambda { body.scan(/(stuff)/m).size }
extract_type2 = lambda { body.scan(/(stuff)/m).size }
extract_type3 = lambda { body.scan(/(stuff)/m).size }
extract_type4 = lambda { body.scan(/(stuff)/m).size }
extract_type5 = lambda { body.scan(/(stuff)/m).size }
if extract_type1.call > 0
elsif extract_type2.call > 0
elsif extract_type3.call > 0
elsif extract_type4.call > 0
elsif extract_type5.call > 0
If you're using the extract_X values more than once then you can add memoization to the lambdas so that the values are computed on first access and then cached so that subsequent accesses would just use the value that was already computed.
Having inherited a project, I find code like this, to search for a set of records in the codebase:
Listing.category_id_equals(category_id).city_id_equals(city.id).end_date_greater_than(Time.now.utc).start_date_less_than(Time.now.utc).validated_equals(true)
This works, but is brittle, because if I want to not include any of the conditions, I need a new method. So, using proc's I now have :
def self.for_cat_and_city cat=nil, city=nil
base = proc { |o| o.end_date_greater_than(Time.now.utc).start_date_less_than(Time.now.utc).validated_equals(true)}
city_query = proc { |o, city| o.city_id_equals(city.id)}
cat_query = proc { |o, cat| o.category_id_equals(cat.id)}
limit = proc { |o, limit| o.limit(limit)}
unless cat.nil?
return city_query.call(cat_query.call(base.call(Listing),cat), city) unless city.nil?
return cat_query.call(base.call(Listing), cat)
end
return city_query.call(base.call(Listing), city) unless city.nil?
return base.call(Listing)
end
Which works really well. But now I need to add a limit to the number of records. How can I do this easily within the bounds of what I'm doing above?
Try just adding limit to the end of the chain, like this:
listings = Listing.category_id_equals(category_id).city_id_equals(city.id).end_date_greater_than(Time.now.utc).start_date_less_than(Time.now.utc).validated_equals(true).limit(30)
Does that work?