I am writing a ruby method as follows:
def build_array(word)
word_copy = word
array = []
length = word_copy.length
for i in 1..length do
array[i] = word_copy % 10
word_copy = word_copy/10;
end
puts array
end
I would like to create an iterator which counts from 1 to the number of digits in a Bignum.
Bignum.length is not valid ruby. Alternatively, is there a way to bust up a Bignum into an array of its constituent digits (Which is what I am trying to implement).
Thanks!
x = 424723894070784320891478329472334238899
Math.log10(x).floor + 1 # => 39
or
Math.log10(x + 1).ceil # => 39
You could convert it to String and count its size
b = 2_999_999_000_000_000
p b.class
#=> Bignum
p b.to_s.size
#=> 16
p b.to_s.split("").map(&:to_i)
#=> [2, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Related
I was just curious as to how to change a number from number base m to another base n with a Ruby program, not a gem. Has anyone done this and would like to share their thoughts or ideas? Just thought it would be fun to try out a program like this :)
I've done it for bin to dec, dec to bin, dex to hex, hex to dec, but would want to know how to do it from m to n.
def bin2dec(num)
sum = 0
i = 0
while i < num.length
sum += 2 ** i * num[num.length - i - 1].to_i
i += 1
end
return sum
end
bin = gets.chomp
out = bin2dec(bin)
print out
def dec2bin(dec)
out = ""
num = dec
while num != 0
out = "#{num % 2}" + out
num = num / 2
end
return out
end
dec = gets.to_i
print dec2bin(dec)
These functions are built in.
To convert "EFFE" from base 16 (hex) to base 8 (octal)...
"EFFE".to_i(16).to_s(8)
# => "167776"
To put this in a method...
def convert_base(string, from_base, to_base)
string.to_i(from_base).to_s(to_base)
end
If you want a method which converts any positive base to any other positive base, start looking at Integer#digits. It takes an argument (10 by default), but nothing stops you from getting a number in base 543.
For 11 <= n <= 36, Ruby has a convention that allows integers to be expressed in base n with the 10 digits 0-9 and the first n-10 characters of the alphabet. It is for that reason that we obtain the following results:
1270.to_s(36) #=> "za"
"za".to_i(36) #=> 1270
1270.to_s(37) #=> ArgumentError (invalid radix 37)
"za".to_i(37) #=> ArgumentError (invalid radix 37)
Ruby's representation of integers, however, is just a convention.
I will only deal with non-negative integers and will refer to them as "numbers". Negative integers can be negated, converted to a number of a different base and then that number negated.
We could express numbers of any base as arrays of digits, where each digit is expressed as a base 10 integer. For example, we could write:
46 in base 10 as [4, 6]
za in base 36 as [36, 10]
a two-digit base N number as [n, m], where n and m are both between 0 and N-1.
We can write a method to convert a base 10 number to this array representation:
def base10_to_base_n(n10, radix)
arr = []
while n10 > 0
n10, rem = n10.divmod(radix)
arr << rem
end
arr.reverse
end
base10_to_base_n(123, 10)
#=> [1, 2, 3]
base10_to_base_n(131, 2)
#=> [1, 0, 0, 0, 0, 0, 1, 1]
abase10_to_base_n(1234, 16)
#=> [4, 13, 2]
base10_to_base_n(9234, 99)
#=> [93, 27]
Note that, in the third example:
4*(16**2) + 13*(16**1) + 2*(16**0) #=> 9234
Next we create a method that does the reverse: converts a number in a given base, described as an array of digits (the argument base_n) to a base 10 number.
def base_n_to_base_10(base_n, radix)
pow = 1
arr = base_n.reverse
base_n.reverse.reduce do |n10, digit|
pow *= radix
n10 + digit*pow
end
end
base_n_to_base_10([1, 2, 3], 10)
#=> 123
base_n_to_base_10([1, 0, 0, 0, 0, 0, 1, 1], 2)
#=> 131
base_n_to_base_10([4, 13, 2], 16)
#=> 1234
base_n_to_base_10([93, 27], 99)
#=> 9234
As expected, if
radix = 87
n10 = 6257
base87 = base10_to_base_n(n10, radix)
#=> [71, 80]
then:
base_n_to_base_10(base10_to_base_n(n10, radix), radix)
#=> 6257
base10_to_base_n(base_n_to_base_10(base87, radix), radix)
#=> [71, 80]
If you wanted to do it without the built-in methods...
def convert_base(string, from_base, to_base)
characters = (0..9).to_a + ('A'..'Z').to_a
string = string.to_s.upcase.gsub(/[^0-9A-Z]/i, '') # remove non alphanumeric
digits = string.split('')
decimal_value = digits.inject(0){|sum, digit| (sum * from_base) + characters.find_index(digit) }
result = []
while decimal_value > 0
result << characters[decimal_value % to_base]
decimal_value = decimal_value / to_base
end
result = result.join.reverse
return result if result.length > 0
'0'
end
convert_base('effe', 16, 8)
# => "167776"
Lets say I have an integer 98.
The binary representation of this string would be:
(98).to_s(2) # 1100010
Now I want to convert this binary string to an integer array of all the bits that are set. This would give me:
[64,32,2]
How would I go about this?
Update: The conversion of int to int array does not necessarily need to involve String, it is just what I knew. I assume non String operations would also be faster.
Ruby is amazing seeing all these different ways to handle this!
This would work:
i = 98
(0...i.bit_length).map { |n| i[n] << n }.reject(&:zero?)
#=> [2, 32, 64]
Fixnum#bit_length returns the position of the highest "1" bit
Fixnum#[n] returns the integer's nth bit, i.e. 0 or 1
Fixnum#<< shifts the bit to the left. 1 << n is equivalent to 2n
Step by step:
(0...i.bit_length).map { |n| i[n] }
#=> [0, 1, 0, 0, 0, 1, 1]
(0...i.bit_length).map { |n| i[n] << n }
#=> [0, 2, 0, 0, 0, 32, 64]
(0...i.bit_length).map { |n| i[n] << n }.reject(&:zero?)
#=> [2, 32, 64]
You might want to reverse the result.
In newer versions of Ruby (2.7+) you could also utilize filter_map and nonzero? to remove all 0 values:
(0...i.bit_length).filter_map { |n| (i[n] << n).nonzero? }
#=> [2, 32, 64]
Reverse the string, map it to binary code values of each digit, reject zeros. Optionally reverse it again.
s.reverse.chars.map.with_index{ |c, i| c.to_i * 2**i }.reject{ |b| b == 0 }.reverse
Or you could push the values to array with each_with_index
a = []
s.reverse.each_with_index do |c, i|
a.unshift c.to_i * 2**i
end
what is probably faster and more readable, but less idiomatic.
(98).to_s(2).reverse.chars.each_with_index.
map {|x,i| x=="1" ? 2**i : nil }.compact.reverse
Phew! Let's break that down:
First get the binary String as your example (98).to_s(2)
We will need to start 0-index from right hand side, hence .reverse
.chars.each_with_index gives us pairs such as [ '1', 4 ] for character at bit position
The .map converts "1" characters to their value 2 ** i (i.e. 2 to the power of current bit position) and "0" to nil so it can be removed
.compact to discard the nil values that you don't want
.reverse to have descending powers of 2 as your example
Here are a couple of ways:
#1
s = (98).to_s(2)
sz = s.size-1
s.each_char.with_index.with_object([]) { |(c,i),a| a << 2**(sz-i) if c == '1' }
# => [64, 32, 2]
#2
n = 2**(98.to_s(2).size-1)
arr = []
while n > 0
arr << n if 90[n]==1
n /= 2
end
arr
#=> [64, 32, 2]
Is there a method in ruby that allows breaking integers up into 1s, 10s, 100s, 1000s, ...?
I know that you can convert an integer to a string and parse the string to get the values mentioned above, but I would imagine there is a really simple way to do this with ruby like most other things. So far I have this:
1234.to_s.chars.reverse.each_with_index
.map{|character, index| character.to_i * 10**index }
# => [4, 30, 200, 1000]
But is there something specific to do this in ruby?
You could do that as follows:
n = 1020304
Math.log10(n).ceil.times.with_object([]) do |i,a|
n, d = n.divmod(10)
a << d * 10**i
end
#=> [4, 0, 300, 0, 20000, 0, 1000000]
Hmmm. That looks a bit odd. Maybe it would be better to return a hash:
Math.log10(n).ceil.times.with_object({}) do |i,h|
n, d = n.divmod(10)
h[10**i] = d
end
#=> {1=>4, 10=>0, 100=>3, 1000=>0, 10000=>2, 100000=>0, 1000000=>1}
def value_of_powers(number, base: 10)
number.to_s.reverse.each_char.with_index.map do |character, index|
base**index * character.to_i
end.reverse
end
value_of_powers(10212, base: 3) # => [81, 0, 18, 3, 2]
value_of_powers(1234) # => [1000, 200, 30, 4]
I reversed the order so that the values are read in the same order as we read numbers.
As shown, it also works for other base numbers. Given no base, it will default to base 10.
This isn't particularly clever, but I suppose it's relatively clear about what it does.
def tens_places(n, place = 1)
if n >= 1
[(n % 10).floor * place] + tens_places(n/10, place * 10)
else
[]
end
end
Given an array of ints I want to quantize each value so that the sum of quantized values is 100. Each quantized value should also be an integer. This works when the whole array is quantized, but when a subset of quantized values is added up it doesn't remain quantized with respect to the rest of the values.
For example, the values 44, 40, 7, 2, 0, 0 are quantized to 47, 43, 8, 2, 0, 0 (the sum of which is 100). If you take the last 4 quantized values the sum is 53 which is consistent with the first value (i.e. 47 + 53 = 100).
But with the values 78, 7, 7, 1, 0, 0, the sum of the last 4 quantized values (8, 8, 1, 0, 0) is 17. The first quantized value is 84 which when added to 17 does not equal 100. Clearly the reason for this is due to the rounding. Is there a way to adjust the rounding so that subsets are still consistent?
Here is the Ruby code:
class Quantize
def initialize(array)
#array = array.map { |a| a.to_i }
end
def values
#array.map { |a| quantize(a) }
end
def sub_total(i, j)
#array[i..j].map { |a| quantize(a) }.reduce(:+)
end
private
def quantize(val)
(val * 100.0 / total).round(0)
end
def total
#array.reduce(:+)
end
end
And the (failing) tests:
require 'quantize'
describe Quantize do
context 'first example' do
let(:subject) { described_class.new([44, 40, 7, 2, 0, 0]) }
context '#values' do
it 'quantizes array to add up to 100' do
expect(subject.values).to eq([47, 43, 8, 2, 0, 0])
end
end
context '#sub_total' do
it 'adds a subset of array' do
expect(subject.sub_total(1, 5)).to eq(53)
end
end
end
context 'second example' do
let(:subject) { described_class.new([78, 7, 7, 1, 0, 0]) }
context '#values' do
it 'quantizes array to add up to 100' do
expect(subject.values).to eq([84, 8, 8, 1, 0, 0])
end
end
context '#sub_total' do
it 'adds a subset of array' do
expect(subject.sub_total(1, 5)).to eq(16)
end
end
end
end
As noted in the comments on the question, the quantization routine does not perform correctly: the second example [78, 7, 7, 1, 0, 0] is quantized as [84, 8, 8, 1, 0, 0] — which adds to 101 and not to 100.
Here is an approach that will yield correct results:
def quantize(array, value)
quantized = array.map(&:to_i)
total = array.reduce(:+)
remainder = value - total
index = 0
if remainder > 0
while remainder > 0
quantized[index] += 1
remainder -= 1
index = (index + 1) % quantized.length
end
else
while remainder < 0
quantized[index] -= 1
remainder += 1
index = (index + 1) % quantized.length
end
end
quantized
end
This solves your problem, as stated in the question. The troublesome result becomes [80, 8, 8, 2, 1, 1], which adds to 100 and maintains the subset relationship that you described. The solution can, of course, be made more performant — but it has the advantage of working and being dead simple to understand.
Is there a Ruby idiom for popping items from an array while a condition is true, and returning the collection?
I.e,
# Would pop all negative numbers from the end of 'array' and place them into 'result'.
result = array.pop {|i| i < 0}
From what I can tell, something like the above doesn't exist.
I'm currently using
result = []
while array.last < 0 do
result << array.pop
end
Maybe you are looking for take_while?
array = [-1, -2, 0, 34, 42, -8, -4]
result = array.reverse.take_while { |x| x < 0 }
result would be [-8, -4].
To get the original result back you could use drop_while instead.
result = array.reverse.drop_while { |x| x < 0 }.reverse
result would be [-1, -2, 0, 34, 42] in this case.
You could write it yourself:
class Array
def pop_while(&block)
result = []
while not self.empty? and yield(self.last)
result << self.pop
end
return result
end
end
result = array.pop_while { |i| i < 0 }
In case your looking for a solution to pop all items that satisfy a condition, consider a select followed by a delete_if, e.g.
x = [*-10..10].sample(10)
# [-9, -2, -8, 0, 7, 9, -1, 10, -10, 3]
neg = x.select {|i| i < 0}
# [-9, -2, -8, -1, -10]
pos = x.delete_if {|i| i < 0}
# [0, 7, 9, 10, 3]
# note that `delete_if` modifies x
# so at this point `pos == x`