Related
I'm very new to coding and have been trying to improve upon a dice roller for D&D that I found here. I have a variable that determines if the roll's modifier is positive or negative, but for some reason Ruby is giving me a NameError (undefined local variable or method `pos_or_neg' for main:Object). Would also appreciate general advice for making my code better.
def roll(amount = 0, sides = 0)
#For every die(amount), randomly generate a result limited to sides, then add all results together.
amount.to_i.times.sum { |t| rand(1..sides.to_i) }
end
puts "Gimme some dice to roll! (e.g. 2d4, 1d12-1, etc.)"
loop do
input = gets.chomp.to_s
abort("May your rolls be ever natural.") if input == "exit"
next puts "Please specify the number of dice!" if input.start_with?("d")
#Check if modifier is positive or negative.
pos_or_neg == true if input.include? "+"; pos_or_neg == false if input.include? "-"
#Replace everything but numbers with spaces in input, then split.
amount, sides, mod = input.tr("^0-9", " ").split
#Calculate final result using pos_or_neg to determine modifier.
pos_or_neg == true ? fin = roll(amount, sides) + mod.to_i : roll(amount, sides) - mod.to_i
puts fin
end
I apologize if this kind of thing is asked often.
What Yurii said in the comment to your question, you are comparing (==) instead of assigning the value (=).
But also, if there's neither '+' nor '-' in input then pos_or_neg won't be defined. You could change the line:
pos_or_neg == true if input.include? "+"; pos_or_neg == false if input.include? "-"
with:
pos_or_neg = !input.include?('-')
(assuming that the case when there's neither '+' nor '-' is taken as positive)
And (I think) it would be more readable if you change this line:
pos_or_neg == true ? fin = roll(amount, sides) + mod.to_i : roll(amount, sides) - mod.to_i
with
fin = roll(amount, sides) + mod.to_i * (pos_or_neg ? 1 : -1)
I've created a binary search, while looking at the online wiki. I have a class of Athletes that each have a name and number. I'm inputting a file text or csv, doesn't matter - with each of the athletes name's and numbers. My program sorts them first, and then I am trying to add the functionality of searching for a number based off of user input, and displaying who wears that numbered jersey. So my initial post was trying to binary search for people with the same number. As in, if I had Michael Jordan and Lebron James on my list, they both wear 23 - so when my search goes through it would only output 1 (whichever it comes to first). I was looking for how to make my search (below) accept/find multiple occurrences of a number. However, upon further testing, I found that if I input a number not actually in my list it would give me the error: search: stack level too deep which I don't know what that means. I think my search doesn't handle properly if there's no instance of the number, or if the array is 0.
So I was looking for some help to see how I can fix this to work if the number input by a user isn't in the list. So if someone inputs "1000" -- no one has worn that jersey number and should return false. Or something of that sort, break, whatever.
def search(array, num, start = 0, last = nil)
if last == nil
last = array.count - 1
end
mid = (start + last) / 2
if num < array[mid].number
return search(array, num, start, mid - 1)
elsif num > array[mid].number
return search(array, num, mid + 1, last)
else
return mid
end
end
I've now also gotten ==: stack level too deep on the line where if last == nil
This is not the ruby way of doing things. When you have a collection, and you would like to only select some of them based on a certain condition, The ruby approach would be to use Enumerable#select
You would ideally have some array of athletes like so
athletes = [Athlete.new, Athlete.new]
athletes_with_number_23 = athletes.select { |athlete| athlete.number == 23 } #if you want all
first_athlete_wearing_23 = athletes.detect { |athlete| athlete.number == 23 } #if you want only the first one
Disclaimer: this is pseudo code.
I changed my search function:
def search(array, key)
lo = 0
hi = array.length-1
while(lo <= hi)
mid = lo + ((hi-lo)/2)
if array[mid].number == key
return mid
elsif array[mid].number < key
lo = mid + 1
else
hi = mid - 1
end
end
puts "Value not found in array"
end
If I got u right you wanna get ALL athletes with a specific number in a sorted list.
First. Your code is a way too procedural, "This is not the ruby way of doing things.". But I guess it doesn't matter for you.
So, I suggest you find an index of one of the athletes and just walk array left and right from it to collect same-number mans.
Here my procedural version. It uses your function.
def athletes_with_number(athletes, number)
result = []
found_index = search(athletes, number)
return result unless found_index
# walk left
i = found_index
while i >= 0 && athletes[i].number == number
result << athletes[i]
i -= 1
end
# walk right
i = found_index + 1 # athletes[found_index] already added
while i < athletes.size && athletes[i].number == number
result << athletes[i]
i += 1
end
result
end
def search(array, key)
lo = 0
hi = array.length-1
while(lo <= hi)
mid = lo + ((hi-lo)/2)
if array[mid].number == key
return mid
elsif array[mid].number < key
lo = mid + 1
else
hi = mid - 1
end
end
nil
end
This is another Codewars Ruby problem that's got me stumped:
Description:
In this kata you have to implement a base converter, which converts between arbitrary bases / alphabets. Here are some pre-defined alphabets:
bin='01'
oct='01234567'
dec='0123456789'
hex='0123456789abcdef'
allow='abcdefghijklmnopqrstuvwxyz'
allup='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
alpha='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
alphanum='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
The function convert() should take an input (string), the source alphabet (string) and the target alphabet (string). You can assume that the input value always consists of characters from the source alphabet. You don't need to validate it.
Examples:
convert("15", dec, bin) #should return "1111"
convert("15", dec, oct) #should return "17"
convert("1010", bin, dec) #should return "10"
convert("1010", bin, hex) #should return "a"
convert("0", dec, alpha) #should return "a"
convert("27", dec, allow) #should return "bb"
convert("hello", allow, hex) #should return "320048"
Additional Notes:
The maximum input value can always be encoded in a number without loss of precision in JavaScript. In Haskell, intermediate results will probably be to large for Int.
The function must work for any arbitrary alphabets, not only the pre-defined ones.
You don't have to consider negative numbers.
I've been playing with this for a couple of days and managed to get the numeric-base-conversion portion working. It's the alphabetical part of it that I can't figure out how to approach, and my brain is tired from trying. Here's my code:
def convert(input, source, target)
bases = {
:bin => '01',
:oct => '01234567',
:dec => '0123456789',
:hex => '0123456789abcdef',
:allow => 'abcdefghijklmnopqrstuvwxyz',
:allup => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
:alpha => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
:alphanum => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
base_from , base_to = 0
src_num_switch = 1 if source == bases[:bin] || [:oct] || [:dec] || [:hex]
tgt_num_switch = 1 if target == bases[:bin] || [:oct] || [:dec] || [:hex]
src_num_switch = 0 if source == bases[:allow] || [:allup] || [:alpha] || [:alphanum]
tgt_num_switch = 0 if target == bases[:allow] || [:allup] || [:alpha] || [:alphanum]
if source == bases[:bin] then base_from = 2
elsif source == bases[:oct] then base_from = 8
elsif source == bases[:dec] then base_from = 10
elsif source == bases[:hex] then base_from = 16
elsif source == bases[:allow] then base_from = 13
elsif source == bases[:allup] then base_from = 13
elsif source == bases[:alpha] then base_from = 13
elsif source == bases[:alphanum] then base_from = 13
else puts ":( no source match found :("
end
if target == bases[:bin] then puts base_to = 2
elsif target == bases[:oct] then base_to = 8
elsif target == bases[:dec] then base_to = 10
elsif target == bases[:hex] then base_to = 16
elsif target == bases[:allow] then base_to = 13
elsif target == bases[:allup] then base_to = 13
elsif target == bases[:alpha] then base_to = 13
elsif target == bases[:alphanum] then base_to = 13
else puts ":( no target match found :("
end
if base_from == base_to then
return input
elsif src_num_switch == 1 && tgt_num_switch == 1 then
return Integer(input, base_from).to_s(base_to)
elsif src_num_switch == 0 && tgt_num_switch == 0 then
return Integer(input, base_from).to_s(base_to)
# ### # :::::::::::::::::::::::::::::::::::::::::::::
else
puts "ouch, something broke"
end
end
I've got everything down to the "# ### #" portion working for me. Can anyone give me an idea of how to do the alpha-base portion? I've tried the following but had no luck:
if base_from == base_to then return input
elsif src_num_switch == 1 && tgt_num_switch == 1 then
return Integer(input, base_from).to_s(base_to)
elsif src_num_switch == 1 && tgt_num_switch == 0 then
if target == bases[:allup] then return bases[input.index].to_s.upcase
elsif target == bases[:allow] then return bases[input.index].to_s.downcase
end
end
elsif src_num_switch == 0 && tgt_num_switch == 1 then
return input.index.to_s(base_to)
elsif src_num_switch == 0 && tgt_num_switch == 0 then
return Integer(input, base_from).to_s(base_to)
else
puts "ouch, something broke"
end
This one too:
elsif src_num_switch == 1 && tgt_num_switch == 0 then # number-base to alphanumeric-base
if target == bases[:allup] then
return bases[input.index].to_s.upcase
elsif target == bases[:allow] then
return bases[input.index].to_s.downcase
end
elsif src_num_switch == 0 && tgt_num_switch == 1 then # alpha-base to number-base
return input.index.to_s(base_to)
There may be a very clever built-in Ruby solution, but I would guess based on the custom alphabets describing the number systems that there is not. So, I don't have a direct answer to how to complete your code, but I would suggest a slightly different strategy.
Converting from a decimal
Any number system can be converted from the decimal system like so:
vals_in_system = system.length
output_in_system = []
while (decimal_num != 0)
index_of_next_val = decimal_num % system.length
output_in_system.unshift(system[index_of_next_val])
decimal_num = decimal_num / vals_in_system # truncating is desired here
end
It's a bit tricky. This algorithm first tries to determine what value it has to put in the last position (which has the most granularity in whatever number system you're using). E.g. if you were to represent 12 in decimal (yes, it already is, but using this algorithm), a 2 has to go in the last position - no number you put in the tens place or higher will otherwise help you represent 12. If you were to represent 3 in binary, a 1 has to go in the last position of the binary - nothing you put in the next position will get you to a 3. Once it determines this, it can divide by the base, which will leave you with the number you would use to calculate the remaining positions. For example, if you were to represent 123 in decimal, dividing by 10 (the decimal base) and truncating would give you 12. 12 is the representation of the original number except for the final position (which was chopped off by dividing by the base). (I realize this isn't the clearest explanation so let me know if you have questions.) Some examples:
E.g. the decimal number 15 can be converted to binary:
15 % 2 = 1 # last position
15 / 2 = 7
7 % 2 = 1 # next to last position
7 / 2 = 3
3 % 2 = 1 # 3rd to last position
3 / 2 = 1
1 % 2 = 1 # 4th to last position
1 / 2 = 0 # stop
That's kinda boring, you just get 1111. Try something a little more interesting, like 10:
10 % 2 = 0 # last position
10 / 2 = 5
5 % 2 = 1 # next to last position
5 / 2 = 2
2 % 2 = 0 # 3rd to last position
2 / 2 = 1
1 % 2 = 1 # 4th to last position
1 / 2 = 0 # stop
And you get 1010, which is indeed 10 in binary. You can do this with any of those alphabets.
Converting to a decimal
Similarly, any number system can be converted to a decimal by doing the opposite:
vals_in_system = from.length
output_in_decimal = 0
val.each_char do |next_val|
output_in_decimal *= vals_in_system
output_in_decimal += from.index(next_val)
end
This is easier to understand than the "from decimal" algorithm. Consider if you were to apply this to the decimal number 123. This algorithm is essentially doing this equation
((1 * 10) + 2) * 10) + 3
or, much easier to read:
1 * (10 * 10) + 2 * (10) + 3
Just iteratively. It works for other number systems, by replacing the 10 with the base of the number system (i.e. the number of values the number system contains). The only other magic it does it converts a value in the number system into a decimal number using .index.
E.g. converting "bcdl" to decimal from their "allow" system. Using a 0-index, b = the 1st position, c = 2nd, d = 3rd, l = 11th
Start with 0
Multiply by the number system base, which is 26 (26 letters in the lowercase alphabet) = 0
Add the decimal value of b (1) => 1
1 * 26 = 26
Add decimal value of c (2) => 28
28 * 26 => 728
Add decimal value of d (3) => 731
731 * 26 => 19006
Add decimal value of l (11) => 19017 That's the decimal notation for "bcdl".
Putting it together
Once you have converters to and from decimal, you can write a pretty straightforward wrapper to handle every situation (I put DEC in a constant to make it visible in this method, it's the same as dec):
def convert(val, from, to)
case
when from == to then val
when from == DEC then convert_from_dec(val, to)
when to == DEC then convert_to_dec(val, from)
else
convert_from_dec(convert_to_dec(val, from), to)
end
end
After that, you mostly have to deal with edge cases.
As I said, not a direct answer to your question, but it seems like you'll have to use this general approach for the alpha number systems, at which point you may as well use it for everything :)
I honestly tried not to look at alexcavalli's solution, but in the end came to exact same algorithm with a different code. So for explanation why it works look at his much more explained answer. Here it's only code, written in a way if you save it under base_converter.rb name you can run it as:
$ ruby ../base_converer.rb 123 hex dec #=> 291
bases = {
bin: '01',
oct: '01234567',
dec: '0123456789',
hex: '0123456789abcdef',
allow: 'abcdefghijklmnopqrstuvwxyz',
allup: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
alpha: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
alphanum: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
}
def to_int(num, src)
src_map = src.split('').map.with_index.to_h
num.reverse.each_char.with_index.sum{ |c, i| src_map[c] * (src.size ** i) }
end
def from_int(num, dst)
res = []
while num > 0
res << dst[num % dst.size]
num /= dst.size
end
res.join.reverse
end
def convert(num, src, dst)
from_int(to_int(num, src), dst)
end
if ARGV.size > 2
puts convert(ARGV[0], bases[ARGV[1].to_sym], bases[ARGV[2].to_sym])
end
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.
I have a numeric value like 30.6355 that represents money, how to round to 2 decimal places?
You should not use double or float types when dealing with currency: they have both too many decimal places and occasional rounding errors. Money can fall through those holes and it'll be tough to track down the errors after it happens.
When dealing with money, use a fixed decimal type. In Ruby (and Java), use BigDecimal.
Ruby 1.8:
class Numeric
def round_to( places )
power = 10.0**places
(self * power).round / power
end
end
(30.6355).round_to(2)
Ruby 1.9:
(30.6355).round(2)
In 1.9, round can round to a specified number of digits.
This will round for some useful cases - not well written but it works! Feel free to edit.
def round(numberString)
numberString = numberString.to_s
decimalLocation = numberString.index(".")
numbersAfterDecimal = numberString.slice(decimalLocation+1,numberString.length-1)
numbersBeforeAndIncludingDeciaml = numberString.slice(0,decimalLocation+1)
if numbersAfterDecimal.length <= 2
return numberString.to_f
end
thingArray = numberString.split("")
thingArray.pop
prior = numbersAfterDecimal[-1].to_i
idx = numbersAfterDecimal.length-2
thingArray.reverse_each do |numStr|
if prior >= 5
numbersAfterDecimal[idx] = (numStr.to_i + 1).to_s unless (idx == 1 && numStr.to_i == 9)
prior = (numStr.to_i + 1)
else
prior = numStr.to_i
end
break if (idx == 1)
idx -= 1
end
resp = numbersBeforeAndIncludingDeciaml + numbersAfterDecimal[0..1]
resp.to_f
end
round(18.00) == 18.0
round(18.99) == 18.99
round(17.9555555555) == 17.96
round(17.944444444445) == 17.95
round(15.545) == 15.55
round(15.55) == 15.55
round(15.555) == 15.56
round(1.18) == 1.18
round(1.189) == 1.19