why ruby upto method is so slow? - ruby

n = 5000000
Benchmark.bm(7) do |x|
x.report("upto :") { for i in 1..n; 0.upto(( 10 )) ; end }
x.report("range :") { for i in 1..n; 0..10 ; end }
end
Results:
user system total real
upto : 1.116440 0.068953 1.185393 ( 1.187705)
range : 0.156921 0.000000 0.156921 ( 0.156759)
Why does upto() takes so much time compared to the manual range ?

This benchmark actually compares oranges to apples now: upto performs the actual iterations under the hood, while 0..10 is a range literal that causes only a creation of a tiny object per iteration (with no memory allocations).

Related

Why is array.min so slow?

I noticed that array.min seems slow, so I did this test against my own naive implementation:
require 'benchmark'
array = (1..100000).to_a.shuffle
Benchmark.bmbm(5) do |x|
x.report("lib:") { 99.times { min = array.min } }
x.report("own:") { 99.times { min = array[0]; array.each { |n| min = n if n < min } } }
end
The results:
Rehearsal -----------------------------------------
lib: 1.531000 0.000000 1.531000 ( 1.538159)
own: 1.094000 0.016000 1.110000 ( 1.102130)
-------------------------------- total: 2.641000sec
user system total real
lib: 1.500000 0.000000 1.500000 ( 1.515249)
own: 1.125000 0.000000 1.125000 ( 1.145894)
I'm shocked. How can my own implementation running a block via each beat the built-in? And beat it by so much?
Am I somehow mistaken? Or is this somehow normal? I'm confused.
My Ruby version, running on Windows 8.1 Pro:
C:\>ruby --version
ruby 2.2.3p173 (2015-08-18 revision 51636) [i386-mingw32]
Have a look at the implementation of Enumerable#min. It might use each eventually to loop through the elements and get the min element, but before that it does some extra checking to see if it needs to return more than one element, or if it needs to compare the elements via a passed block. In your case the elements will get to be compared via min_i function, and I suspect that's where the speed difference comes from - that function will be slower than simply comparing two numbers.
There's no extra optimization for arrays, all enumerables are traversed the same way.
It's even faster if you use:
def my_min(ary)
the_min = ary[0]
i = 1
len = ary.length
while i < len
the_min = ary[i] if ary[i] < the_min
i += 1
end
the_min
end
NOTE
I know this is not an answer, but I thought it was worth sharing and putting this code into a comment would have been exceedingly ugly.
For those who likes to upgrade to newer versions of software
require 'benchmark'
array = (1..100000).to_a.shuffle
Benchmark.bmbm(5) do |x|
x.report("lib:") { 99.times { min = array.min } }
x.report("own:") { 99.times { min = array[0]; array.each { |n| min = n if n < min } } }
end
Rehearsal -----------------------------------------
lib: 0.021326 0.000017 0.021343 ( 0.021343)
own: 0.498233 0.001024 0.499257 ( 0.499746)
-------------------------------- total: 0.520600sec
user system total real
lib: 0.018126 0.000000 0.018126 ( 0.018139)
own: 0.492046 0.000000 0.492046 ( 0.492367)
RUBY_VERSION # => "2.7.1"
If you are looking into solving this in really performant manner: O(log(n)) or O(n), look at https://en.wikipedia.org/wiki/Selection_algorithm#Incremental_sorting_by_selection and https://en.wikipedia.org/wiki/Heap_(data_structure)

Infinite loop in algorithm to match clocks running at different speeds

I'm trying to solve this problem:
Two clocks, which show the time in hours and minutes using the 24 hour clock, are running at different
speeds. Each clock is an exact number of minutes per hour fast. Both clocks start showing the same time
(00:00) and are checked regularly every hour (starting after one hour) according to an accurate timekeeper.
What time will the two clocks show on the first occasion when they are checked and show the same time?
NB: For this question we only care about the clocks matching when they are checked.
For example, suppose the first clock runs 1 minute fast (per hour) and the second clock runs 31 minutes
fast (per hour).
• When the clocks are first checked after one hour, the first clock will show 01:01 and the second clock
will show 01:31;
• When the clocks are checked after two hours, they will show 02:02 and 03:02;
• After 48 hours the clocks will both show 00:48.
Here is my code:
def add_delay(min,hash)
hash[:minutes] = (hash[:minutes] + min)
if hash[:minutes] > 59
hash[:minutes] %= 60
if min < 60
add_hour(hash)
end
end
hash[:hour] += (min / 60)
hash
end
def add_hour(hash)
hash[:hour] += 1
if hash[:hour] > 23
hash[:hour] %= 24
end
hash
end
def compare(hash1,hash2)
(hash1[:hour] == hash2[:hour]) && (hash1[:minutes] == hash2[:minutes])
end
#-------------------------------------------------------------------
first_clock = Integer(gets) rescue nil
second_clock = Integer(gets) rescue nil
#hash1 = if first_clock < 60 then {:hour => 1,:minutes => first_clock} else {:hour => 1 + (first_clock/60),:minutes => (first_clock%60)} end
#hash2 = if second_clock < 60 then {:hour => 1,:minutes => second_clock} else {:hour => 1 + (second_clock/60),:minutes => (second_clock%60)} end
hash1 = {:hour => 0, :minutes => 0}
hash2 = {:hour => 0, :minutes => 0}
begin
hash1 = add_hour(hash1)
hash1 = add_delay(first_clock,hash1)
hash2 = add_hour(hash2)
p hash2.to_s
hash2 = add_delay(second_clock,hash2)
p hash2.to_s
end while !compare(hash1,hash2)
#making sure print is good
if hash1[:hour] > 9
if hash1[:minutes] > 9
puts hash1[:hour].to_s + ":" + hash1[:minutes].to_s
else
puts hash1[:hour].to_s + ":0" + hash1[:minutes].to_s
end
else
if hash1[:minutes] > 9
puts "0" + hash1[:hour].to_s + ":" + hash1[:minutes].to_s
else
puts "0" + hash1[:hour].to_s + ":0" + hash1[:minutes].to_s
end
end
#-------------------------------------------------------------------
For 1 and 31 the code runs as expected. For anything bigger, such as 5 and 100, it seems to get into an infinite loop and I don't see where the bug is. What is going wrong?
The logic in your add_delay function is flawed.
def add_delay(min,hash)
hash[:minutes] = (hash[:minutes] + min)
if hash[:minutes] > 59
hash[:minutes] %= 60
if min < 60
add_hour(hash)
end
end
hash[:hour] += (min / 60)
hash
end
If hash[:minutes] is greater than 60, you should increment the hour no matter what. Observe that an increment less than 60 can cause the minutes to overflow.
Also, you may have to increment the hour more than once if the increment exceeds 60 minutes.
Finally, it is wrong to do hash[:hour] += (min / 60) because min is not necessarily over 60 and because you have already done add_hour(hash).
Here is a corrected version of the function:
def add_delay(minutes, time)
time[:minutes] += minutes
while time[:minutes] > 59 # If the minutes overflow,
time[:minutes] -= 60 # subtract 60 minutes and
add_hour(time) # increment the hour.
end # Repeat as necessary.
time
end
You can plug this function into your existing code. I have merely taken the liberty of renaming min to minutes and hash to time inside the function.
Your code
Let's look at your code and at the same time make some small improvements.
add_delay takes a given number of minutes to add to the hash, after converting the number of minutes to hours and minutes and then the number of hours to the number of hours within a day. One problem is that if a clock gains more than 59 minutes per hour, you may have to increment hours by more than one. Try writing it and add_hours like this:
def add_delay(min_to_add, hash)
mins = hash[:minutes] + min_to_add
hrs, mins = mins.divmod 60
hash[:minutes] = mins
add_hours(hash, hrs)
end
def add_hours(hash, hours=1)
hash[:hours] = (hash[:hours] + hours) % 24
end
We do not necessarily care what either of these methods returns, as they modify the argument hash.
This uses the very handy method Fixnum#divmod to convert minutes to hours and minutes.
(Aside: some Rubiests don't use hash as the name of a variable because it is also the name of a Ruby method.)
Next, compare determines if two hashes with keys :hour and :minutes are equal. Rather than checking if both the hours and minutes match, you can just see if the hashes are equal:
def compare(hash1, hash2)
hash1 == hash2
end
Get the minutes per hour by which the clocks are fast:
first_clock = Integer(gets) rescue nil
second_clock = Integer(gets) rescue nil
and now initialize the hashes and step by hour until a match is found, then return either hash:
def find_matching_time(first_clock, second_clock)
hash1 = {:hours => 0, :minutes => 0}
hash2 = {:hours => 0, :minutes => 0}
begin
add_delay(first_clock, hash1)
add_hours(hash1)
add_delay(second_clock, hash2)
add_hours(hash2)
end until compare(hash1, hash2)
hash1
end
Let's try it:
find_matching_time(1, 31)
# => {:hours=>0, :minutes=>48}
find_matching_time(5, 100)
#=> {:hours=>0, :minutes=>0}
find_matching_time(5, 5)
#=> {:hours=>1, :minutes=>5}
find_matching_time(0, 59)
#=> {:hours=>0, :minutes=>0}
These results match those I obtained below with an alternative method. You do not return the number hours from the present until the times are the same, but you may not need that.
I have not identified why you were getting the infinite loop, but perhaps with this analysis you will be able to find it.
There are two other small changes I would suggest: 1) incorporating add_hours in add_delay and renaming the latter, and 2) getting rid of compare because it so simple and only used in one place:
def add_hour_and_delay(min_to_add, hash)
mins = hash[:minutes] + min_to_add
hrs, mins = mins.divmod 60
hash[:minutes] = mins
hash[:hours] = (hash[:hours] + 1 + hrs) % 24
end
def find_matching_time(first_clock, second_clock)
hash1 = {:hours => 0, :minutes => 0}
hash2 = {:hours => 0, :minutes => 0}
begin
add_hour_and_delay(first_clock, hash1)
add_hour_and_delay(second_clock, hash2)
end until hash1 == hash2
hash1
end
Alternative method
Here's anther way to write the method. Let:
f0: minutes per hour the first clock is fast
f1: minutes per hour the second clock is fast
Then we can compute the next time they will show the same time as follows.
Code
MINS_PER_DAY = (24*60)
def find_matching_time(f0, f1)
elapsed_hours = (1..Float::INFINITY).find { |i|
(i*(60+f0)) % MINS_PER_DAY == (i*(60+f1)) % MINS_PER_DAY }
[elapsed_hours, "%d:%02d" % ((elapsed_hours*(60+f0)) % MINS_PER_DAY).divmod(60)]
end
Examples
find_matching_time(1, 31)
#=> [48, "0:48"]
After 48 hours both clocks will show a time of "0:48".
find_matching_time(5, 100)
#=> [288, "0:00"]
find_matching_time(5, 5)
#=> [1, "1:05"]
find_matching_time(0, 59)
#=> [1440, "0:00"]
Explanation
After i hours have elapsed, the two clocks will respectively display a time that is the following number of minutes within a day:
(i*(60+f0)) % MINS_PER_DAY # clock 0
(i*(60+f1)) % MINS_PER_DAY # clock 1
Enumerable#find is then used to determine the first number of elapsed hours i when these two values are equal. We don't know how long that may take, so I've enumerated over all positive integers beginning with 1. (I guess it could be no more than 59 hours, so I could have written (1..n).find.. where n is any integer greater than 58.) The value returned by find is assigned to the variable elapsed_hours.
Both clocks will display the same time after elapsed_hours, so we can compute the time either clock will show. I've chosen to do that for clock 0. For the first example (f0=1, f1=31)
elapsed_hours #=> 48
so
mins_clock0_advances = elapsed_hours*(60+1)
#=> 2928
mins_clock_advances_within_day = mins_clock0_advances % MINS_PER_DAY
#=> 48
We then convert this to hours and minutes:
mins_clock_advances_within_day.divmod(60)
#=> [0, 48]
which we can then the method String#% to format this result appropriately:
"%d:%02d" % mins_clock_advances_within_day.divmod(60)
#=> "0:48"
See Kernel#sprintf for information on formatting when using %. In "%02d", d is for "decimal", 2 is the field width and 0 means pad left with zeroes.

Matlab fprintf - What is faster - writing all output or only a few using if with modulo?

In my special case, I want to decide this for matlab. Is it faster (inside a for loop with ~250.000 runs) with the "if", so fprintf is only used 250 times?
for i=1:250042
if ~(mod(i, 1000))
fprintf(<something to standard output>);
end
end
I know for programming in C, programs were much slower, when printing to the standard output.
It is much slower to print everything. By profiling the following code
clear all
clear classes
for i=1:250042
if ~(mod(i, 1000))
fprintf(['current loop: ', num2str(i), '\n']);
end
end
for i=1:250042
fprintf(['current loop: ', num2str(i), '\n']);
end
I found the following:
time calls line
< 0.01 1 1 clear all
< 0.01 1 2 clear classes
3
1 4 for i=1:250042
0.13 250042 5 if ~(mod(i, 1000))
0.12 250 6 fprintf(['current loop: ', num2str(i), '\n']);
250 7 end
0.24 250042 8 end
9
1 10 for i=1:250042
37.90 250042 11 fprintf(['current loop: ', num2str(i), '\n']);
0.47 250042 12 end
Printing everything is orders of magnitude slower.
Calls to fprintf have considerable overhead, especially for small write operations. For instance, executing the following code:
fid = fopen ( 'a.txt' , 'w+' );
timeStart1000 = tic;
for ( ii = 1 : 10 )
for ( iii = 1 : 100 )
b = num2str ( ii );
fprintf ( fid , b );
end
end
timeStop1000 = toc ( timeStart1000 );
timeStart10 = tic;
for ( ii = 1 : 10 )
c = '';
for ( iii = 1 : 100 )
c = [ c , num2str(ii) ];
end
fprintf ( fid , c );
end
timeStop10 = toc ( timeStart10 );
There is a significant time difference between 1000 and 10 calls to fprintf: timeStop1000 = 0.1816 vs. timeStop10 = 0.0765.
First of all, I prefer to not use i as an index, as it serves to represent the imaginary number in Matlab.
Your question is easy to test:
tic
for jj=1:250042
if ~mod(jj,1000)
disp('Hello')
end
end
a=toc;
tic
for jj=1:250042
if ~mod(jj,100)
disp('Hello')
end
end
b=toc;
clc
disp(a)
disp(b)
gives
0.0295
0.0736
so the answer is: yes, it is faster to print less.

Number crunching in Ruby (optimisation needed)

Ruby may not be the optimal language for this but I'm sort of comfortable working with this in my terminal so that's what I'm going with.
I need to process the numbers from 1 to 666666 so I pin out all the numbers that contain 6 but doesn't contain 7, 8 or 9. The first number will be 6, the next 16, then 26 and so forth.
Then I needed it printed like this (6=6) (16=6) (26=6) and when I have ranges like 60 to 66 I need it printed like (60 THRU 66=6) (SPSS syntax).
I have this code and it works but it's neither beautiful nor very efficient so how could I optimize it?
(silly code may follow)
class Array
def to_ranges
array = self.compact.uniq.sort
ranges = []
if !array.empty?
# Initialize the left and right endpoints of the range
left, right = array.first, nil
array.each do |obj|
# If the right endpoint is set and obj is not equal to right's successor
# then we need to create a range.
if right && obj != right.succ
ranges << Range.new(left,right)
left = obj
end
right = obj
end
ranges << Range.new(left,right) unless left == right
end
ranges
end
end
write = ""
numbers = (1..666666).to_a
# split each number in an array containing it's ciphers
numbers = numbers.map { |i| i.to_s.split(//) }
# delete the arrays that doesn't contain 6 and the ones that contains 6 but also 8, 7 and 9
numbers = numbers.delete_if { |i| !i.include?('6') }
numbers = numbers.delete_if { |i| i.include?('7') }
numbers = numbers.delete_if { |i| i.include?('8') }
numbers = numbers.delete_if { |i| i.include?('9') }
# join the ciphers back into the original numbers
numbers = numbers.map { |i| i.join }
numbers = numbers.map { |i| i = Integer(i) }
# rangify consecutive numbers
numbers = numbers.to_ranges
# edit the ranges that go from 1..1 into just 1
numbers = numbers.map do |i|
if i.first == i.last
i = i.first
else
i = i
end
end
# string stuff
numbers = numbers.map { |i| i.to_s.gsub(".."," thru ") }
numbers = numbers.map { |i| "(" + i.to_s + "=6)"}
numbers.each { |i| write << " " + i }
File.open('numbers.txt','w') { |f| f.write(write) }
As I said it works for numbers even in the millions - but I'd like some advice on how to make prettier and more efficient.
I deleted my earlier attempt to parlez-vous-ruby? and made up for that. I know have an optimized version of x3ro's excellent example.
$,="\n"
puts ["(0=6)", "(6=6)", *(1.."66666".to_i(7)).collect {|i| i.to_s 7}.collect do |s|
s.include?('6')? "(#{s}0 THRU #{s}6=6)" : "(#{s}6=6)"
end ]
Compared to x3ro's version
... It is down to three lines
... 204.2 x faster (to 66666666)
... has byte-identical output
It uses all my ideas for optimization
gen numbers based on modulo 7 digits (so base-7 numbers)
generate the last digit 'smart': this is what compresses the ranges
So... what are the timings? This was testing with 8 digits (to 66666666, or 823544 lines of output):
$ time ./x3ro.rb > /dev/null
real 8m37.749s
user 8m36.700s
sys 0m0.976s
$ time ./my.rb > /dev/null
real 0m2.535s
user 0m2.460s
sys 0m0.072s
Even though the performance is actually good, it isn't even close to the C optimized version I posted before: I couldn't run my.rb to 6666666666 (6x10) because of OutOfMemory. When running to 9 digits, this is the comparative result:
sehe#meerkat:/tmp$ time ./my.rb > /dev/null
real 0m21.764s
user 0m21.289s
sys 0m0.476s
sehe#meerkat:/tmp$ time ./t2 > /dev/null
real 0m1.424s
user 0m1.408s
sys 0m0.012s
The C version is still some 15x faster... which is only fair considering that it runs on the bare metal.
Hope you enjoyed it, and can I please have your votes if only for learning Ruby for the purpose :)
(Can you tell I'm proud? This is my first encounter with ruby; I started the ruby koans 2 hours ago...)
Edit by #johndouthat:
Very nice! The use of base7 is very clever and this a great job for your first ruby trial :)
Here's a slight modification of your snippet that will let you test 10+ digits without getting an OutOfMemory error:
puts ["(0=6)", "(6=6)"]
(1.."66666666".to_i(7)).each do |i|
s = i.to_s(7)
puts s.include?('6') ? "(#{s}0 THRU #{s}6=6)" : "(#{s}6=6)"
end
# before:
real 0m26.714s
user 0m23.368s
sys 0m2.865s
# after
real 0m15.894s
user 0m13.258s
sys 0m1.724s
Exploiting patterns in the numbers, you can short-circuit lots of the loops, like this:
If you define a prefix as the 100s place and everything before it,
and define the suffix as everything in the 10s and 1s place, then, looping
through each possible prefix:
If the prefix is blank (i.e. you're testing 0-99), then there are 13 possible matches
elsif the prefix contains a 7, 8, or 9, there are no possible matches.
elsif the prefix contains a 6, there are 49 possible matches (a 7x7 grid)
else, there are 13 possible matches. (see the image below)
(the code doesn't yet exclude numbers that aren't specifically in the range, but it's pretty close)
number_range = (1..666_666)
prefix_range = ((number_range.first / 100)..(number_range.last / 100))
for p in prefix_range
ps = p.to_s
# TODO: if p == prefix_range.last or p == prefix_range.first,
# TODO: test to see if number_range.include?("#{ps}6".to_i), etc...
if ps == '0'
puts "(6=6) (16=6) (26=6) (36=6) (46=6) (56=6) (60 thru 66) "
elsif ps =~ /7|8|9/
# there are no candidate suffixes if the prefix contains 7, 8, or 9.
elsif ps =~ /6/
# If the prefix contains a 6, then there are 49 candidate suffixes
for i in (0..6)
print "(#{ps}#{i}0 thru #{ps}#{i}6) "
end
puts
else
# If the prefix doesn't contain 6, 7, 8, or 9, then there are only 13 candidate suffixes.
puts "(#{ps}06=6) (#{ps}16=6) (#{ps}26=6) (#{ps}36=6) (#{ps}46=6) (#{ps}56=6) (#{ps}60 thru #{ps}66) "
end
end
Which prints out the following:
(6=6) (16=6) (26=6) (36=6) (46=6) (56=6) (60 thru 66)
(106=6) (116=6) (126=6) (136=6) (146=6) (156=6) (160 thru 166)
(206=6) (216=6) (226=6) (236=6) (246=6) (256=6) (260 thru 266)
(306=6) (316=6) (326=6) (336=6) (346=6) (356=6) (360 thru 366)
(406=6) (416=6) (426=6) (436=6) (446=6) (456=6) (460 thru 466)
(506=6) (516=6) (526=6) (536=6) (546=6) (556=6) (560 thru 566)
(600 thru 606) (610 thru 616) (620 thru 626) (630 thru 636) (640 thru 646) (650 thru 656) (660 thru 666)
(1006=6) (1016=6) (1026=6) (1036=6) (1046=6) (1056=6) (1060 thru 1066)
(1106=6) (1116=6) (1126=6) (1136=6) (1146=6) (1156=6) (1160 thru 1166)
(1206=6) (1216=6) (1226=6) (1236=6) (1246=6) (1256=6) (1260 thru 1266)
(1306=6) (1316=6) (1326=6) (1336=6) (1346=6) (1356=6) (1360 thru 1366)
(1406=6) (1416=6) (1426=6) (1436=6) (1446=6) (1456=6) (1460 thru 1466)
(1506=6) (1516=6) (1526=6) (1536=6) (1546=6) (1556=6) (1560 thru 1566)
(1600 thru 1606) (1610 thru 1616) (1620 thru 1626) (1630 thru 1636) (1640 thru 1646) (1650 thru 1656) (1660 thru 1666)
etc...
Note I don't speak ruby, but I intend to dohave done a ruby version later just for speed comparison :)
If you just iterate all numbers from 0 to 117648 (ruby <<< 'print "666666".to_i(7)') and print them in base-7 notation, you'll at least have discarded any numbers containing 7,8,9. This includes the optimization suggestion by MrE, apart from lifting the problem to simple int arithmetic instead of char-sequence manipulations.
All that remains, is to check for the presence of at least one 6. This would make the algorithm skip at most 6 items in a row, so I deem it less unimportant (the average number of skippable items on the total range is 40%).
Simple benchmark to 6666666666
(Note that this means outputting 222,009,073 (222M) lines of 6-y numbers)
Staying close to this idea, I wrote this quite highly optimized C code (I don't speak ruby) to demonstrate the idea. I ran it to 282475248 (congruent to 6666666666 (mod 7)) so it was more of a benchmark to measure: 0m26.5s
#include <stdio.h>
static char buf[11];
char* const bufend = buf+10;
char* genbase7(int n)
{
char* it = bufend; int has6 = 0;
do
{
has6 |= 6 == (*--it = n%7);
n/=7;
} while(n);
return has6? it : 0;
}
void asciify(char* rawdigits)
{
do { *rawdigits += '0'; }
while (++rawdigits != bufend);
}
int main()
{
*bufend = 0; // init
long i;
for (i=6; i<=282475248; i++)
{
char* b7 = genbase7(i);
if (b7)
{
asciify(b7);
puts(b7);
}
}
}
I also benchmarked another approach, which unsurprisingly ran in less than half the time because
this version directly manipulates the results in ascii string form, ready for display
this version shortcuts the has6 flag for deeper recursion levels
this version also optimizes the 'twiddling' of the last digit when it is required to be '6'
the code is simply shorter...
Running time: 0m12.8s
#include <stdio.h>
#include <string.h>
inline void recursive_permute2(char* const b, char* const m, char* const e, int has6)
{
if (m<e)
for (*m = '0'; *m<'7'; (*m)++)
recursive_permute2(b, m+1, e, has6 || (*m=='6'));
else
if (has6)
for (*e = '0'; *e<'7'; (*e)++)
puts(b);
else /* optimize for last digit must be 6 */
puts((*e='6', b));
}
inline void recursive_permute(char* const b, char* const e)
{
recursive_permute2(b, b, e-1, 0);
}
int main()
{
char buf[] = "0000000000";
recursive_permute(buf, buf+sizeof(buf)/sizeof(*buf)-1);
}
Benchmarks measured with:
gcc -O4 t6.c -o t6
time ./t6 > /dev/null
$range_start = -1
$range_end = -1
$f = File.open('numbers.txt','w')
def output_number(i)
if $range_end == i-1
$range_end = i
elsif $range_start < $range_end
$f.puts "(#{$range_start} thru #{$range_end})"
$range_start = $range_end = i
else
$f.puts "(#{$range_start}=6)" if $range_start > 0 # no range, print out previous number
$range_start = $range_end = i
end
end
'1'.upto('666') do |n|
next unless n =~ /6/ # keep only numbers that contain 6
next if n =~ /[789]/ # remove nubmers that contain 7, 8 or 9
output_number n.to_i
end
if $range_start < $range_end
$f.puts "(#{$range_start} thru #{$range_end})"
end
$f.close
puts "Ruby is beautiful :)"
I came up with this piece of code, which I tried to keep more or less in FP-styling. Probably not much more efficient (as it has been said, with basic number logic you will be able to increase performance, for example by skipping from 19xx to 2000 directly, but that I will leave up to you :)
def check(n)
n = n.to_s
n.include?('6') and
not n.include?('7') and
not n.include?('8') and
not n.include?('9')
end
def spss(ranges)
ranges.each do |range|
if range.first === range.last
puts "(" + range.first.to_s + "=6)"
else
puts "(" + range.first.to_s + " THRU " + range.last.to_s + "=6)"
end
end
end
range = (1..666666)
range = range.select { |n| check(n) }
range = range.inject([0..0]) do |ranges, n|
temp = ranges.last
if temp.last + 1 === n
ranges.pop
ranges.push(temp.first..n)
else
ranges.push(n..n)
end
end
spss(range)
My first answer was trying to be too clever. Here is a much simpler version
class MutablePrintingCandidateRange < Struct.new(:first, :last)
def to_s
if self.first == nil and self.last == nil
''
elsif self.first == self.last
"(#{self.first}=6)"
else
"(#{self.first} thru #{self.last})"
end
end
def <<(x)
if self.first == nil and self.last == nil
self.first = self.last = x
elsif self.last == x - 1
self.last = x
else
puts(self) # print the candidates
self.first = self.last = x # reset the range
end
end
end
and how to use it:
numer_range = (1..666_666)
current_range = MutablePrintingCandidateRange.new
for i in numer_range
candidate = i.to_s
if candidate =~ /6/ and candidate !~ /7|8|9/
# number contains a 6, but not a 7, 8, or 9
current_range << i
end
end
puts current_range
Basic observation: If the current number is (say) 1900 you know that you can safely skip up to at least 2000...
(I didn't bother updating my C solution for formatting. Instead I went with x3ro's excellent ruby version and optimized that)
Undeleted:
I still am not sure whether the changed range-notation behaviour isn't actually what the OP wants: This version changes the behaviour of breaking up ranges that are actually contiguous modulo 6; I wouldn't be surprised the OP actually expected
.
....
(555536=6)
(555546=6)
(555556 THRU 666666=6)
instead of
....
(666640 THRU 666646=6)
(666650 THRU 666656=6)
(666660 THRU 666666=6)
I'll let the OP decide, and here is the modified version, which runs in 18% of the time as x3ro's version (3.2s instead of 17.0s when generating up to 6666666 (7x6)).
def check(n)
n.to_s(7).include?('6')
end
def spss(ranges)
ranges.each do |range|
if range.first === range.last
puts "(" + range.first.to_s(7) + "=6)"
else
puts "(" + range.first.to_s(7) + " THRU " + range.last.to_s(7) + "=6)"
end
end
end
range = (1..117648)
range = range.select { |n| check(n) }
range = range.inject([0..0]) do |ranges, n|
temp = ranges.last
if temp.last + 1 === n
ranges.pop
ranges.push(temp.first..n)
else
ranges.push(n..n)
end
end
spss(range)
My answer below is not complete, but just to show a path (I might come back and continue the answer):
There are only two cases:
1) All the digits besides the lowest one is either absent or not 6
6, 16, ...
2) At least one digit besides the lowest one includes 6
60--66, 160--166, 600--606, ...
Cases in (1) do not include any continuous numbers because they all have 6 in the lowest digit, and are different from one another. Cases in (2) all appear as continuous ranges where the lowest digit continues from 0 to 6. Any single continuation in (2) is not continuous with another one in (2) or with anything from (1) because a number one less than xxxxx0 will be xxxxy9, and a number one more than xxxxxx6 will be xxxxxx7, and hence be excluded.
Therefore, the question reduces to the following:
3)
Get all strings between "" to "66666" that do not include "6"
For each of them ("xxx"), output the string "(xxx6=6)"
4)
Get all strings between "" to "66666" that include at least one "6"
For each of them ("xxx"), output the string "(xxx0 THRU xxx6=6)"
The killer here is
numbers = (1..666666).to_a
Range supports iterations so you would be better off by going over the whole range and accumulating numbers that include your segments in blocks. When one block is finished and supplanted by another you could write it out.

How do I optimize this bit of ruby code to go faster?

It's an implementation of Sieve of Eratosthenes.
class PrimeGenerator
def self.get_primes_between( x, y)
sieve_array = Array.new(y) {|index|
(index == 0 ? 0 : index+1)
}
position_when_we_can_stop_checking = Math.sqrt(y).to_i
(2..position_when_we_can_stop_checking).each{|factor|
sieve_array[(factor).. (y-1)].each{|number|
sieve_array[number-1] = 0 if isMultipleOf(number, factor)
}
}
sieve_array.select{|element|
( (element != 0) && ( (x..y).include? element) )
}
end
def self.isMultipleOf(x, y)
return (x % y) == 0
end
end
Now I did this for a 'submit solutions to problems since you have time to kill' site. I chose ruby as my impl language.. however i was declared timed out.
I did some benchmarking
require 'benchmark'
Benchmark.bmbm do |x|
x.report ("get primes") { PrimeGenerator.get_primes_between(10000, 100000)}
end
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-mswin32]
L:\Gishu\Ruby>ruby prime_generator.rb
Rehearsal ----------------------------------------------
get primes 33.953000 0.047000 34.000000 ( 34.343750)
------------------------------------ total: 34.000000sec
user system total real
get primes 33.735000 0.000000 33.735000 ( 33.843750)
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]
Rehearsal ----------------------------------------------
get primes 65.922000 0.000000 65.922000 ( 66.110000)
------------------------------------ total: 65.922000sec
user system total real
get primes 67.359000 0.016000 67.375000 ( 67.656000)
So I redid the thing in C# 2.0 / VS 2008 -->
722 milliseconds
So now this prods me into thinking is it a problem with my implementation or is the perf diff between languages this wide? (I was amazed with the 1.9 Ruby VM... until I had to go compare it with C# :)
UPDATE:
Turned out to be my "put-eratosthenes-to-shame-adaptation" after all :) Eliminating unnecessary loop iterations was the major optimization. In case anyone is interested in the details.. you can read it here; this question is too long anyways.
I'd start by looking at your inner loop. sieve_array[(factor).. (y-1)] is going to create a new array each time it's executed. Instead, try replacing it with a normal indexing loop.
Obviously each computer is going to benchmark this differently, but I was able to make this run approximately 50x faster on my machine (Ruby 1.8.6) by removing the looping on the array with an each block, and by causing the inner loop to check less numbers.
factor=2
while factor < position_when_we_can_stop_checking
number = factor
while number < y-1
sieve_array[number-1] = 0 if isMultipleOf(number, factor)
number = number + factor; # Was incrementing by 1, causing too many checks
end
factor = factor +1
end
I don't know how it compares for speed, but this is a fairly small and simple SoE implementation that works fine for me:
def sieve_to(n)
s = (0..n).to_a
s[0]=s[1]=nil
s.each do |p|
next unless p
break if p * p > n
(p*p).step(n, p) { |m| s[m] = nil }
end
s.compact
end
There are a few further little speedups possible, but I think it's pretty good.
They're not exactly equivalent, so your 10_000 to 1_000_000 would equate to
sieve_to(1_000_000) - sieve_to(9_999)
or something closely approximate.
Anyhow, on WinXP, with Ruby 1.8.6 (and fairly hefty Xeon CPUs) I get this:
require 'benchmark'
Benchmark.bm(30) do |r|
r.report("Mike") { a = sieve_to(10_000) - sieve_to(1_000) }
r.report("Gishu") { a = PrimeGenerator.get_primes_between( 1_000, 10_000) }
end
which gives
user system total real
Mike 0.016000 0.000000 0.016000 ( 0.016000)
Gishu 1.641000 0.000000 1.641000 ( 1.672000)
(I stopped running the one million case because I got bored waiting).
So I'd say it was your algorithm. ;-)
The C# solution is pretty much guaranteed to be orders of magnitude faster though.
The Sieve of Eratosthenes works fine as illustrative way to find primes, but I would implement it a little bit different. The essence is that you don't have to check numbers which are multiples of already known primes. Now, instead of using an array to store this information, you can also create a list of all sequential primes up to the square root of the number you are checking, and then it suffices to go through the list of primes to check for primes.
If you think of it, this does what you do on the image, but in a more "virtual" way.
Edit: Quickly hacked implementation of what I mean (not copied from the web ;) ):
public class Sieve {
private readonly List<int> primes = new List<int>();
private int maxProcessed;
public Sieve() {
primes.Add(maxProcessed = 2); // one could add more to speed things up a little, but one is required
}
public bool IsPrime(int i) {
// first check if we can compare against known primes
if (i <= primes[primes.Count-1]) {
return primes.BinarySearch(i) >= 0;
}
// if not, make sure that we got all primes up to the square of i
int maxFactor = (int)Math.Sqrt(i);
while (maxProcessed < maxFactor) {
maxProcessed++;
bool isPrime = true;
for (int primeIndex = 0; primeIndex < primes.Count; primeIndex++) {
int prime = primes[primeIndex];
if (maxProcessed % prime == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.Add(maxProcessed);
}
}
// now apply the sieve to the number to check
foreach (int prime in primes) {
if (i % prime == 0) {
return false;
}
if (prime > maxFactor) {
break;
}
}
return true;
}
}
Uses about 67ms on my slow machine.... test app:
class Program {
static void Main(string[] args) {
Stopwatch sw = new Stopwatch();
sw.Start();
Sieve sieve = new Sieve();
for (int i = 10000; i <= 100000; i++) {
sieve.IsPrime(i);
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
}
}
Benchmark it with ruby-prof. it can spit out things tools like kcachegrind can look at to see where your code is slow.
Then once you make the ruby fast, use RubyInline to optimize the method for you.
I would also note that Ruby, in my experience, is a lot slower on Windows systems than on *nix. I'm not sure what speed processor you have, of course, but running this code on my Ubuntu box in Ruby 1.9 took around 10 seconds, and 1.8.6 took 30.

Resources