Ruby How to convert string to integer without .to_i - ruby

Is there a way to output an integer given a string containing numbers between 0 and 9. For example, input is "219", output would be 219, and you can't use .to_i

You can use Kernel::Integer:
Integer("219")
#=> 219
Integer("21cat9")
# ArgumentError: invalid value for Integer(): "21cat9"
Sometimes this method is used as follows:
def convert_to_i(str)
begin
Integer(str)
rescue ArgumentError
nil
end
end
convert_to_i("219")
#=> 219
convert_to_i("21cat9")
#=> nil
convert_to_i("1_234")
#=> 1234
convert_to_i(" 12 ")
#=> 12
convert_to_i("0b11011") # binary representation
#=> 27
convert_to_i("054") # octal representation
#=> 44
convert_to_i("0xC") # hexidecimal representation
#=> 12
Some use an "inline rescue" (though it is less selective, as it rescues all exceptions):
def convert_to_i(str)
Integer(str) rescue nil
end
There are similar Kernel methods to convert a string to a float or rational.

def str_to_int(string)
digit_hash = {"0" => 0, "1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9}
total = 0
num_array = string.split("").reverse
num_array.length.times do |i|
num_value = digit_hash[num_array[i]]
num_value_base_ten = num_value * (10**i)
total += num_value_base_ten
end
return total
end
puts str_to_int("119") # => 119

Related

Decode base45 string

We are trying to implement the verification of the new EU corona virus test/vaccination certificates, but can't get the base45 decoding working.
Specification is here: https://datatracker.ietf.org/doc/draft-faltstrom-base45/
We nearly finished our class, but we sometimes get wrong values back..
Target is this:
Encoding example 1: The string "AB" is the byte sequence [65 66].
The 16 bit value is 65 * 256 + 66 = 16706. 16706 equals 11 + 45 * 11
+ 45 * 45 * 8 so the sequence in base 45 is [11 11 8]. By looking up
these values in the Table 1 we get the encoded string "BB8".
Encoding example 2: The string "Hello!!" as ASCII is the byte
sequence [72 101 108 108 111 33 33]. If we look at each 16 bit
value, it is [18533 27756 28449 33]. Note the 33 for the last byte.
When looking at the values modulo 45, we get [[38 6 9] [36 31 13] [9
2 14] [33 0]] where the last byte is represented by two. By looking
up these values in the Table 1 we get the encoded string "%69
VD92EX0".
Encoding example 3: The string "base-45" as ASCII is the byte
sequence [98 97 115 101 45 52 53]. If we look at each 16 bit value,
it is [25185 29541 11572 53]. Note the 53 for the last byte. When
looking at the values modulo 45, we get [[30 19 12] [21 26 14] [7 32
5] [8 1]] where the last byte is represented by two. By looking up
these values in the Table 1 we get the encoded string "UJCLQE7W581".
Here is my current code, which produces wrong values:
class Base45
ALPHABET = {
"00" => "0",
"01" => "1",
"02" => "2",
"03" => "3",
"04" => "4",
"05" => "5",
"06" => "6",
"07" => "7",
"08" => "8",
"09" => "9",
"10" => "A",
"11" => "B",
"12" => "C",
"13" => "D",
"14" => "E",
"15" => "F",
"16" => "G",
"17" => "H",
"18" => "I",
"19" => "J",
"20" => "K",
"21" => "L",
"22" => "M",
"23" => "N",
"24" => "O",
"25" => "P",
"26" => "Q",
"27" => "R",
"28" => "S",
"29" => "T",
"30" => "U",
"31" => "V",
"32" => "W",
"33" => "X",
"34" => "Y",
"35" => "Z",
"36" => " ",
"37" => "$",
"38" => "%",
"39" => "*",
"40" => "+",
"41" => "-",
"42" => ".",
"43" => "/",
"44" => ":"
}.freeze
def self.encode_base45(text)
restsumme = text.unpack('S>*')
# not sure what this is doing, but without it, it works worse :D
restsumme << text.bytes[-1] if text.bytes.size > 2 && text.bytes[-1] < 256
bytearr = restsumme.map do |bytes|
arr = []
multiplier, rest = bytes.divmod(45**2)
arr << multiplier if multiplier > 0
multiplier, rest = rest.divmod(45)
arr << multiplier if multiplier > 0
arr << rest if rest > 0
arr.reverse
end
return bytearr.flatten.map{|a| ALPHABET[a.to_s.rjust(2, "0")]}.join
end
def self.decode_base45(text)
arr = text.split("").map do |char|
ALPHABET.invert[char]
end
textarr = arr.each_slice(3).to_a.map do |group|
subarr = group.map.with_index do |val, index|
val.to_i * (45**index)
end
ap subarr
subarr.sum
end
return textarr.pack("S>*") # returns wrong values
end
end
Results:
Base45.encode_base45("AB")
=> "BB8" # works
Base45.decode_base45("BB8")
=> "AB" # works
Base45.encode_base45("Hello!!")
=> "%69 VD92EX" # works
Base45.decode_base45("BB8")
=> "Hello!\x00!" # wrong \x00
Base45.encode_base45("base-45")
=> "UJCLQE7W581" # works
Base45.decode_base45("UJCLQE7W581")
=> "base-4\x005" # wrong \x00
Any hints appreciated :(
After struggling to get the other answers to work, I made my own method based on your question and this snippet. Abovementioned answers worked in most cases but not in all of them, especially when the string length mod 3 = 2.
class Base45
ALPHABET = {
0 => "0",
1 => "1",
2 => "2",
3 => "3",
4 => "4",
5 => "5",
6 => "6",
7 => "7",
8 => "8",
9 => "9",
10 => "A",
11 => "B",
12 => "C",
13 => "D",
14 => "E",
15 => "F",
16 => "G",
17 => "H",
18 => "I",
19 => "J",
20 => "K",
21 => "L",
22 => "M",
23 => "N",
24 => "O",
25 => "P",
26 => "Q",
27 => "R",
28 => "S",
29 => "T",
30 => "U",
31 => "V",
32 => "W",
33 => "X",
34 => "Y",
35 => "Z",
36 => " ",
37 => "$",
38 => "%",
39 => "*",
40 => "+",
41 => "-",
42 => ".",
43 => "/",
44 => ":"
}.freeze
def self.decode_base45(text)
raise ArgumentError, "invalid base45 string" if text.size % 3 == 1
arr = text.split("").map do |char|
ALPHABET.invert[char]
end
arr.each_slice(3).to_a.map do |group|
if group.size == 3
x = group[0] + group[1] * 45 + group[2] * 45 * 45
raise ArgumentError, "invalid base45 string" if x > 0xFFFF
x.divmod(256)
else
x = group[0] + group[1] * 45
raise ArgumentError, "invalid base45 string" if x > 0xFF
x
end
end.flatten.pack("C*")
end
end
if you'd like a bodgy way of doing this:
return textarr.map{|x| x<256 ? [x].pack("C*") : [x].pack("n*") }.join
looking at this scheme, it feels like a weird way to encode, as we're working with numbers ... if it were me, I'd have started at the tail of the string and worked towards the head, but that's because we're using numbers.
anyway, the reason that my bodge works is that it treats small elements/numbers as 8-bit unsigned instead of 16-bit unsigned.
...
slightly more pleasing to the eye, but probably no better:
def self.decode_base45(text)
arr = text.split("").map do |char|
ALPHABET.invert[char]
end
textarr = arr.each_slice(3).to_a.map do |group|
subarr = group.map.with_index do |val, index|
val.to_i * (45**index)
end
ap subarr
subarr.sum.divmod(256)
end.flatten.reject(&:zero?)
return textarr.pack("C*") # returns wrong values
end
It might not be a proper solution to the problem here.
But adding textarr.pack("S>*").gsub(/\x00/, "") solved the problem for the given decoding examples.
Also it's really weird that your encode version didn't work well for me (had wrong results in two first examples).
Anyway, this thread led me to contribute a bit by making this as a gem.
Let's take QED8WEX0 and QED8WEX00 for example, you got [[26, 14, 13], [8, 32, 14], [33, 0]] and [[26, 14, 13], [8, 32, 14], [33, 0, 0]] respectively.
The problem here is their final form are the same: [26981, 29798, 33] and you can't determine whether 33 represents for 1 bytes or 2 bytes.
We can solve it by passing dynamic argument based on input length to Array#pack, for example:
[26981, 29798, 33].pack((input.length % 3).zero? ? 'n*' : "n#{input.length / 3}C")
Here is my implementation:
# frozen_string_literal: true
class Base45Lite
MAX_UINT18 = 2**16 - 1
SQUARED_45 = 45**2
MAPPING = [
*'0'..'9',
*'A'..'Z',
' ', '$', '%', '*', '+', '-', '.', '/', ':'
].map!.with_index { |x, i| [i, x] }.to_h.freeze
REVERSE_MAPPING = MAPPING.invert.freeze
def self.encode(input)
sequence = []
input.unpack('n*').map! do |uint16|
i, c = uint16.divmod(45)
i, d = i.divmod(45)
_, e = i.divmod(45)
sequence.push(c, d, e)
end
if input.bytesize.odd?
i, c = input.getbyte(-1).divmod(45)
_, d = i.divmod(45)
sequence.push(c, d)
end
sequence.map!{ MAPPING[_1] }.join
end
def self.decode(input)
input
.chars.map! { REVERSE_MAPPING[_1] }
.each_slice(3).map do |slice|
c, d, e = slice
sum = c + d * 45
sum += e * SQUARED_45 if e
sum
end
.pack((input.length % 3).zero? ? 'n*' : "n#{input.length / 3}C")
end
end
if __FILE__ == $PROGRAM_NAME
require 'minitest/autorun'
class Base45Test < Minitest::Test
parallelize_me!
def test_encode
assert_equal 'BB8', Base45Lite.encode('AB')
assert_equal '%69 VD92EX0', Base45Lite.encode('Hello!!')
assert_equal 'UJCLQE7W581', Base45Lite.encode('base-45')
end
def test_decode
assert_equal 'ietf!', Base45Lite.decode('QED8WEX0')
assert_equal 'AB', Base45Lite.decode('BB8')
assert_equal 'Hello!!', Base45Lite.decode('%69 VD92EX0')
assert_equal 'base-45', Base45Lite.decode('UJCLQE7W581')
end
end
end
You can find a more comprehensive implementation here https://gist.github.com/tonytonyjan/5eefdfbe7a79cd676e75c138466e921d
You can also install the Ruby gem https://rubygems.org/gems/base45_lite
Benchmark
Compare with https://github.com/wattswing/base45
require 'benchmark/ips'
require 'base45'
require_relative 'base45_lite'
message = 'base-45'
encoded = 'UJCLQE7W581'
Benchmark.ips do |x|
x.report('Base45Lite.encode'){ |n| n.times { Base45Lite.encode(message) } }
x.report('Base45.encode'){ |n| n.times { Base45.encode(message) } }
x.compare!
end
Benchmark.ips do |x|
x.report('Base45Lite.decode'){ |n| n.times { Base45Lite.decode(encoded) } }
x.report('Base45.decode'){ |n| n.times { Base45.decode(encoded) } }
x.compare!
end
Warming up --------------------------------------
Base45Lite.encode 37.721k i/100ms
Base45.encode 16.121k i/100ms
Calculating -------------------------------------
Base45Lite.encode 348.105k (± 6.7%) i/s - 1.735M in 5.008303s
Base45.encode 148.871k (± 7.1%) i/s - 741.566k in 5.008783s
Comparison:
Base45Lite.encode: 348105.0 i/s
Base45.encode: 148870.7 i/s - 2.34x (± 0.00) slower
Warming up --------------------------------------
Base45Lite.decode 25.909k i/100ms
Base45.decode 16.972k i/100ms
Calculating -------------------------------------
Base45Lite.decode 231.134k (± 6.9%) i/s - 1.166M in 5.068220s
Base45.decode 148.288k (± 6.5%) i/s - 746.768k in 5.057694s
Comparison:
Base45Lite.decode: 231134.5 i/s
Base45.decode: 148287.8 i/s - 1.56x (± 0.00) slower

Roman Numerals Decoder: Ruby

Alright I've been working on this coding challenge for quite some time and I guess it's officially time for me to raise the flag. Help!
My task is to create a function that takes a Roman numeral as its argument and returns its value as a numeric decimal integer.
So far I've successfully created a hash mapping the numbers to its numeric values. I've also created an empty array roman_no to pass the key/value pair through.
What I am struggling with is writing the expression. Below is the full code:
def solution(roman)
# take a value of a roman numeral
roman_numeral =
{
1000 => "M",
900 => "CM",
500 => "D",
400 => "CD",
100 => "C",
90 => "XC",
50 => "L",
40 => "XL",
10 => "X",
9 => "IX",
5 => "V",
4 => "IV",
1 => "I"
}
roman_no = Array.new
roman_numeral.each do | key, value |
while
"#{roman}" >= "#{key}"
+= roman_no
"#{roman}" -= "#{key}"
end
return roman_no
solution('XXI')
How can I write an argument that will take the value from roman_numeral and return its number counter part?
for example:
solution('XXI') # should return 21
def solution(roman)
mapping = {
"M"=>1000,
"D"=>500,
"C"=>100,
"L"=>50,
"X"=>10,
"V"=>5,
"I"=>1
}
# split string into characters
roman.chars.map do |l|
mapping[l] # replace character with integer value
end
.compact # removes potential nils caused by invalid chars
# Splits array into chunks so that we can handle numerals such as IIX
.chunk_while do |i,j|
i <= j #
end
# each chunk will be an array like [10, 10, 100] or [1, 1, 1, 1]
.map do |chunk|
if chunk.first < chunk.last
chunk.reverse.inject(:-) # handles numerals such as IIX with subtraction
else
chunk.sum # chunk is just a list of numerals such as III
end
end
.sum # sums everything up
end

How to convert a string of values with a range to an array in Ruby

I'm trying to parse a string of numbers and ranges joined by ",", and convert it to a numerical array. I have this as input: "1,3,6-8,5", and would like to have an array like this: [1,3,5,6,7,8].
I can only do it without the range, like this:
"12,2,6".split(",").map { |s| s.to_i }.sort #=> [2, 6, 12]
With a range, I cannot do it:
a = "12,3-5,2,6"
b = a.gsub(/-/, "..") #=> "12,3..5,2,6"
c = b.split(",") #=> ["12", "3..5", "2", "6"]
d = c.sort_by(&:to_i) #=> ["2", "3..5", "6", "12"]
e = d.split(",").map { |s| s.to_i } #>> Error
How can I do this?
I was also thinking to use the splat operator in map, but splat doesn't accept strings like [*(3..5)].
"12,3-5,2,6".
gsub(/(\d+)-(\d+)/) { ($1..$2).to_a.join(',') }.
split(',').
map(&:to_i)
#⇒ [12, 3, 4, 5, 2, 6]
"1,3,6-8,5".split(',').map do |str|
if matched = str.match(/(\d+)\-(\d+)/)
(matched[1].to_i..matched[2].to_i).to_a
else
str.to_i
end
end.flatten
or
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(/(\d+)\-(\d+)/)
output.concat (matched[1].to_i..matched[2].to_i).to_a
else
output << str.to_i
end
end
or strict
RANGE_PATTERN = /\A(\d+)\-(\d+)\z/
INT_PATTERN = /\A\d+\z/
"1,3,6-8,5".split(',').each_with_object([]) do |str, output|
if matched = str.match(RANGE_PATTERN)
output.concat (matched[1].to_i..matched[2].to_i).to_a
elsif str.match(INT_PATTERN)
output << str.to_i
else
raise 'Wrong format given'
end
end
"1,3,6-8,5".split(',').flat_map do |s|
if s.include?('-')
f,l = s.split('-').map(&:to_i)
(f..l).to_a
else
s.to_i
end
end.sort
#=> [1, 3, 5, 6, 7, 8]
"1,3,6-8,5"
.scan(/(\d+)\-(\d+)|(\d+)/)
.flat_map{|low, high, num| num&.to_i || (low.to_i..high.to_i).to_a}
#=> [1, 3, 6, 7, 8, 5]

Trouble with classes in ruby and codewars

so i am trying a kata on Codewars: Flexible Card Game
http://www.codewars.com/kata/5436fdf34e3d6cb156000350/train/ruby
the code I have written has passed most tests but trips up at the end giving this:
#draw
chooses cards from the end
Test Passed: Value == [[:clubs, 13]]
removes cards from the deck
Test Passed: Value == 51
returns the cards that were drawn
Test Passed: Value == 1
Expected [:clubs, 13] to be a Card
chooses cards from the end
Test Passed: Value == [[:clubs, 12], [:clubs, 13]]
removes cards from the deck
Test Passed: Value == 50
returns the cards that were drawn
Test Passed: Value == 2
Expected [:clubs, 12] to be a Card
Expected [:clubs, 13] to be a Card
What I don't understand is that when the test calls the method draw it seems to expect to different returns from the same method. I'm sure it's something I've done wrong but I cant see it. Any help would be great. Here is my code:
class Card
include Comparable
attr_accessor :suit, :rank
def initialize(suit, rank)
#suit = suit
#rank = rank
end
def <=> (another_card)
if self.rank < another_card.rank
-1
elsif self.rank > another_card.rank
1
else
0
end
end
def face_card?
#rank > 10 ? true : false
end
def to_s
#rank_hash = {13 => "King", 12 => "Queen", 11 => "Jack", 10 => "10", 9 => "9", 8 => "8", 7 => "7", 6 => "6", 5 => "5", 4 => "4", 3 => "3", 2 => "2", 1 => "Ace"}
#suit_hash = {:clubs => "Clubs", :spades => "Spades", :hearts => "Hearts", :diamonds => "Diamonds"}
"#{#rank_hash[#rank]} of #{#suit_hash[#suit]}"
end
end
class Deck < Card
attr_accessor :cards
def initialize
#rank_array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
#suit_array = [:hearts, :diamonds, :spades, :clubs]
#cards = #suit_array.product(#rank_array)
end
def count
#cards.size
end
def shuffle
#cards.shuffle!
end
def draw(n=1)
#cards.pop(n)
end
end
Issue was with the initialize method. Below fixed the issue.
def initialize
#cards = []
#rank_array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
#suit_array = [:hearts, :diamonds, :spades, :clubs]
#suit_array.each do |suit|
#rank_array.each do |rank|
each_card = Card.new(suit, rank)
#cards << each_card
end
end
end

Ruby: reuse value in a block without assigning it to variable (write object method on the fly)

There are several situations where I'd like to apply a block to a certain value and use the value inside this block, to use the enumerator coding style to every element.
If such method would be called decompose, it would look like:
result = [3, 4, 7, 8].decompose{ |array| array[2] + array[3] } # result = 15
# OR
result = {:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] } # result = 'value'
# OR
[min, max] = [3, 4, 7, 8].decompose{ |array| [array.min, array.max] } # [min, max] = [3, 8]
# OR
result = 100.decompose{ |int| (int - 1) * (int + 1) / (int * int) } # result = 1
# OR
result = 'Paris'.decompose{ |str| str.replace('a', '') + str[0] } # result = 'PrisP'
The method simply yields self to the block, returning the block's result. I don't think it exists, but you can implement it yourself:
class Object
def decompose
yield self
end
end
[3, 4, 7, 8].decompose{ |array| array[2] + array[3] }
#=> 15
{:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] }
#=> "value"
[3, 4, 7, 8].decompose{ |array| [array.min, array.max] }
#=> [3, 8]
It actually exists (I could not believe it didn't).
It is called BasicObject#instance_eval. Here's the doc: http://apidock.com/ruby/BasicObject/instance_eval
Available since Ruby 1.9 as this post explains: What's the difference between Object and BasicObject in Ruby?

Resources