Comparisons on numbers and strings in ruby - ruby

In console:
1.9.3p547 :010 > my_s = 00013.to_s
=> "11"
1.9.3p547 :011 > my_s = 00013.to_i
=> 11
1.9.3p547 :012 > 13.to_s
=> "13"
1.9.3p547 :013 > 13.to_i
=> 13
Why this difference in treatment? How can comparisons be made between string values that are fundamentally numbers but that have been padded with zeros?

You wrote 00013, with leading zeros. In Ruby (and a lot of other languages), integer literals starting with zeros are treated as octal (base 8), so the number you typed (when written in decimal) is really 8 + 3 = 11.
To answer the second part of your question: If you have a string like '00013', you can just call #to_i on it and Ruby will actually convert it to 13 (in decimal). So that should allow you to make comparisons.

In Ruby's Integer class, leading zeroes are not stored.
Leading zeroes are number literals and are interpreted like so
Octal: 0, 0o
Hexadecimal: 0, 0x
Binary: 0b
Decimal: 0d
'00013' has three leading zeroes, and matches the format 0o. IRB treats these numbers as octal(base 8)

You don't have any "string values that are fundamentally numbers but that have been padded with zeros" in your example. If you did, it would actually work how you expect:
"00013".to_i
# => 13
You have a number literal in your example that has been padded with zeroes. As the other answers say, this is treated as an octal representation by ruby.
But if you actually had a string representation of a number, padded with zeroes, it works fine, just call #to_i on it.

Related

Returning a non negative integer in descending order

I'm working on a Ruby challenge to take any non-negative integer as an argument and return it with its digits in descending order. Essentially, rearrange the digits to create the highest possible number.
for example:
Input: 145263 Output: 654321
Input: 123456789 Output: 987654321
Currently this is what my solution looks like:
def descending_order(n)
# take any non-negative integer as an argument
# return it with digits in descending order
n.sqrt(1){ |digits| digits.sort_by.reverse }
end
However, it keeps throwing an error message saying:
`descending_order': undefined method `sqrt' for 0:Integer (NoMethodError)
def descending_order(n)
n.to_s.split(//).sort.reverse.join.to_i
end
This creates an array of strings, each string being a single digit as text. We can use the normal sort here, because we can reasonably assume that the collating sequence of the digits in an encoding obeys the same order than their numeric counterpart. In particular, we know that i.e. '4' < '8'.
I think the easiest to understand answer is:
n = 145263
n.digits.sort.reverse.join.to_i
n.digits => [3, 6, 2, 5, 4, 1]
n.digits.sort => [1, 2, 3, 4, 5, 6]
n.digits.sort.reverse => [6, 5, 4, 3, 2, 1]
n.digits.sort.reverse.join => "654321"
n.digits.sort.reverse.join.to_i => 654321
Summary of methods
digits => Returns the digits of ints place-value representation with radix base (default: 10). The digits are returned as an array with the least significant digit as the first array element.
sort => Returns a new array created by sorting self.
reverse => Returns a new array containing selfs elements in reverse order.
join => Returns a string created by converting each element of the array to a string, separated by the given separator. If the separator is nil, it uses current $,. If both the separator and $, are nil, it uses an empty string.
to_i => Returns the result of interpreting leading characters in str as an integer base base (between 2 and 36).
I like Tom Lord's answer in his comment. It's a bit cryptic at first, but Stefan's explanation is excellent.

Generating random number of length 6 with SecureRandom in Ruby

I tried SecureRandom.random_number(9**6) but it sometimes returns 5 and sometimes 6 numbers. I'd want it to be a length of 6 consistently. I would also prefer it in the format like SecureRandom.random_number(9**6) without using syntax like 6.times.map so that it's easier to be stubbed in my controller test.
You can do it with math:
(SecureRandom.random_number(9e5) + 1e5).to_i
Then verify:
100000.times.map do
(SecureRandom.random_number(9e5) + 1e5).to_i
end.map { |v| v.to_s.length }.uniq
# => [6]
This produces values in the range 100000..999999:
10000000.times.map do
(SecureRandom.random_number(9e5) + 1e5).to_i
end.minmax
# => [100000, 999999]
If you need this in a more concise format, just roll it into a method:
def six_digit_rand
(SecureRandom.random_number(9e5) + 1e5).to_i
end
To generate a random, 6-digit string:
# This generates a 6-digit string, where the
# minimum possible value is "000000", and the
# maximum possible value is "999999"
SecureRandom.random_number(10**6).to_s.rjust(6, '0')
Here's more detail of what's happening, shown by breaking the single line into multiple lines with explaining variables:
# Calculate the upper bound for the random number generator
# upper_bound = 1,000,000
upper_bound = 10**6
# n will be an integer with a minimum possible value of 0,
# and a maximum possible value of 999,999
n = SecureRandom.random_number(upper_bound)
# Convert the integer n to a string
# unpadded_str will be "0" if n == 0
# unpadded_str will be "999999" if n == 999999
unpadded_str = n.to_s
# Pad the string with leading zeroes if it is less than
# 6 digits long.
# "0" would be padded to "000000"
# "123" would be padded to "000123"
# "999999" would not be padded, and remains unchanged as "999999"
padded_str = unpadded_str.rjust(6, '0')
Docs to Ruby SecureRand, lot of cool tricks here.
Specific to this question I would say: (SecureRandom.random_number * 1000000).to_i
Docs: random_number(n=0)
If 0 is given or an argument is not given, ::random_number returns a float: 0.0 <= ::random_number < 1.0.
Then multiply by 6 decimal places (* 1000000) and truncate the decimals (.to_i)
If letters are okay, I prefer .hex:
SecureRandom.hex(3) #=> "e15b05"
Docs:
hex(n=nil)
::hex generates a random hexadecimal string.
The argument n specifies the length, in bytes, of the random number to
be generated. The length of the resulting hexadecimal string is twice
n.
If n is not specified or is nil, 16 is assumed. It may be larger in
future.
The result may contain 0-9 and a-f.
Other options:
SecureRandom.uuid #=> "3f780c86-6897-457e-9d0b-ef3963fbc0a8"
SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
For Rails apps creating a barcode or uid with an object you can do something like this in the object model file:
before_create :generate_barcode
def generate_barcode
begin
return if self.barcode.present?
self.barcode = SecureRandom.hex.upcase
end while self.class.exists?(barcode: barcode)
end
SecureRandom.random_number(n) gives a random value between 0 to n. You can achieve it using rand function.
2.3.1 :025 > rand(10**5..10**6-1)
=> 742840
rand(a..b) gives a random number between a and b. Here, you always get a 6 digit random number between 10^5 and 10^6-1.

How to avoid octal, binary, etc. numbers changing output during string conversion

I'm working on a codewars kata that uses the Luhn Algorithm
to validate credit card numbers. Already have my answer, which uses string conversion to split the digits and then reconvert to an integer and then use the rest of the algorithm. All well and good until I test it on an octal number.
The code starts as:
def validate(n)
n.to_s.split("") #n = 7867 gives me ["7","8","6","7"], which is fine
n.to_s.split("") #n = 0776 gives me ["5","1","0], when I need ["0","7","7","6"]
n.to_s.split("") #n = 0100 gives me ["6", "4"] when I need ["0","1","0","0"]
#other code here
end
where the method should be called on whatever n is plugged in.
How do I prevent an octal, binary, or hexidecimal, etc number from converting like that? Is there a way to keep the digits as is so I can use them?
Your problem is that you are obviously storing the number as a number, in n; or else you would not need to say n.to_s.
There must be a place where those numbers enter your program; a user prompt, or by reading a file or whatever. You likely have something like n = user_input.to_i somewhere, forcing ruby to convert the string to a number. Make sure that you treat n as string everywhere, and leading zeroes will stick around.
If you are using numeric literals in your program, you can explicitly say that they are decimal:
n = 0d0100 # or "0100".to_i
n.to_s.rjust(4,"0").split("")
#=> ["0", "1", "0", "0"]
Also, use rjust to make them 4 digits long in string form as credit/debit cards have four 4-digit numbers
To split credit card into an array of integer, you could do something like below:
cc = "4111 1111 1111 1111".gsub(/ /,"").split("").map(&:to_i)
#=> [4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Why do numeric string comparisons give unexpected results?

'10:' < '1:'
# => true
Can someone explain me why the result in the above example is true? If I just compare '1:' and '2:' I get the result expected:
'1:' < '2:'
# => true
Strings are compared character by character.
When you compare 1: vs 2:, the comparison begins with 2 vs 1, and the comparison stops there with the expected result.
When you compare 1: vs 10:, the comparison begins with 1 vs 1, and since it is a tie, the comparison moves on to the next comparison, which is : vs 0, and the comparison stops there with the result that you have found surprising (given your expectation that the integers within the strings would be compared).
To do the comparison you expect, use to_i to convert both operands to integers.
It is character by character comparison in ASCII.
'10:' < '1:' is (49 < 49) || (48 < 58) || (58 < ?)
#=> true
'1:' < '2:' is (49 < 50) || (58 < 58)
#=> true
Left to Right boolean check is used and check breaks where true is found.
Note: It is just my observation over various example patterns.
The first character of each of your two strings are the same. And as Dave said in the comments, the second character of the first, '0', is less than ':', so the first string is less than the second.
Because the ASCII code for 0 is 48, which is smaller than the ASCII code for :, which is 58.

Comparing numerical strings and formatting integers with leading zeros in Ruby

how i use the number 05798300 exact in ruby?
When i enter:
2.0.0p247 :031 > 05798300
SyntaxError: (irb):31: Invalid octal digit
or
2.0.0p247 :001 > 04704110
=> 1280072
I need check if the number: 04704110 is between 0100000000 and 09000000.
If you need to keep leading zeros, store your postal codes as strings and you can compare them as such:
test = '04704110'
lower = '01000000' #assuming eight digits
upper = '09000000'
p lower < test && test < upper
#=> true
Otherwise, compare them as integers but format them when you print them, adding leading zeros:
test = 4704110
p "%08d" % test
#=> "04704110"

Resources