Using each_slice to split input starting from end of string? - ruby

I would like to split a string into array groups of three as shown in desired output. Using Array#each_slice like this 1_223_213_213.to_s.split('').each_slice(3){|arr| p arr }
Current output: Desired output
# ["1", "2", "2"] # ["0", "0", "1"]
# ["3", "2", "1"] # ["2", "2", "3"]
# ["3", "2", "1"] # ["2", "1", "3"]
# ["3"] # ["2", "1", "3"]
Must work with numbers from (0..trillion). I posted my solution as an answer below. Hoping you all can give me some suggestion(s) to optimize or alternative implements?

Try left-padding with zeros until the string length is an even multiple of your "slice" target:
def slice_with_padding(s, n=3, &block)
s = "0#{s}" while s.to_s.size % n != 0
s.to_s.chars.each_slice(n, &block)
end
slice_with_padding(1_223_213_213) { |x| puts x.inspect }
# ["0", "0", "1"]
# ["2", "2", "3"]
# ["2", "1", "3"]
# ["2", "1", "3"]
slice_with_padding(12_345, 4) { |x| puts x.inspect }
# ["0", "0", "0", "1"]
# ["2", "3", "4", "5"]

You might find this a little more pleasing to your eye:
def slice_by_3(n)
n = n.to_s
l = n.length
[*n.rjust(l % 3 == 0 ? l : l + 3 - l % 3, '0').chars.each_slice(3)]
end
slice_by_3 2_123_456_544_545_355
=> [["0", "0", "2"],
["1", "2", "3"],
["4", "5", "6"],
["5", "4", "4"],
["5", "4", "5"],
["3", "5", "5"]]
Alternatively, if you want a more general solution:
def slice_by_n(num, n=3)
num = num.to_s
l = num.length
[*num.rjust(l % n == 0 ? l : l + n - l % n, '0').chars.each_slice(n)]
end

Here is a possible solution for the problem:
def slice_by_3 number
initial_number = number.to_s.split('').size
number = "00#{number}" if initial_number == 1
modulus = number.to_s.split(/.{3}/).size
padleft = '0' * ( (modulus*3) % number.to_s.split('').size )
("#{padleft}#{number}").split('').each_slice(3){|arr| p arr }
end
slice_by_3 2_123_456_544_545_355
# ["0", "0", "2"]
# ["1", "2", "3"]
# ["4", "5", "6"]
# ["5", "4", "4"]
# ["5", "4", "5"]
# ["3", "5", "5"]
Just seems somewhat complex and I want to believe there is a better way. I appreciate your feedback.

def slice_by_3 number
"000#{number}".split('').reverse
.each_slice(3).to_a[0..-2].reverse
.each { |arr| p arr.reverse }
end
slice_by_3 13_456_544_545_355
# ["0", "1", "3"]
# ["4", "5", "6"]
# ["5", "4", "4"]
# ["5", "4", "5"]
# ["3", "5", "5"]
This code reverses the whole array after adding 3 zeroes to the number start. each_slice(3) then slices to the proper groups (although reversed) plus one which consists of either ["0","0","0"], ["0","0"] or ["0"] depending on the original length of the number.
[0..-2] cuts the last group of zeroes. Then the groups are reversed back, and each group is printed (reversed back).

Here are a couple methods
n = 1_223_213_213.to_s
n.rjust(n.size + n.size % 3,"0").chars.each_slice(3).to_a
OR
n.rjust(15,"0").chars.each_slice(3).drop_while{|a| a.join == "000"}
15 is because you stated the max was a trillion obviously this number means very little as it rejects all results that contain all zeros so any number greater than 15 that is divisible by 3 will work for your example

Another way:
def split_nbr(n)
str = n.to_s
len = str.size
str.rjust(len + (3-len%3)%3, '0').scan(/.../).map(&:chars)
end
split_nbr( 1_223_213_213)
#=> [["0", "0", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]
split_nbr( 11_223_213_213)
#=> [["0", "1", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]
split_nbr(111_223_213_213)
#=> [["1", "1", "1"], ["2", "2", "3"], ["2", "1", "3"], ["2", "1", "3"]]

Related

How to split a long number into pairs of digits

I have a string of a long number 12345678 and want to convert it to an array like this :
["12", "34", "56", "78"].
I have tried array.split(//).map { |e| e.to_i } but it does
["1", "2", "3", "4", "5", "6", "7", "8"]
The simplest way would be to use String#scan with the Regexp /../, which matches any pair of characters:
n = 12345678
arr = n.to_s.scan(/../)
# => ["12", "34", "56", "78"]
If you need to handle odd numbers of digits and keep the last digit, use /..?/ instead:
n = 123456789
arr = n.to_s.scan(/..?/)
# => ["12", "34", "56", "78", "9"]
An alternative approach would be to map over the range 0...n.to_s.size using the Range#step method:
n = 123456789
str = n.to_s
arr = (0...str.size).step(2).map {|i| str[i, 2] }
# => ["12", "34", "56", "78", "9"]
You can see all three approaches in action on repl.it: https://repl.it/#jrunning/BlissfulAcclaimedStrategy
I think there is no need to use a regexp, I would do something like this:
12345678.to_s.chars.each_slice(2).map(&:join)
#=> ["12","34","56","78"]
You can use
p = 123456789.to_s
(0..p.length).each_cons(2).map {|i, j| "#{p[i]}#{p[j]}" if i%2 == 0}.compact

Sum of duplicate values with multiple values in hash

Given a hash that looks like the following:
h = {
"0" => ["1", "true", "21"],
"1" => ["2", "true", "21"],
"2" => ["3", "false", "21"],
"3" => ["4", "true", "22"],
"4" => ["5", "true", "22"],
"5" => ["6", "true", "22"],
"6" => ["7", "false", "21"]
}
I want to find the sum of elements at position 0 across arrays that have the same elements at indices 1 and 2, and return a hash like the following:
{
0 => ["3", "true", "21"],
1 => ["10", "false", "21"],
2 => ["15", "true", "22"]
}
Since there are two arrays with indices 1 and 2 having values "true" and "21", I want to sum the integer values of index 0 for those two arrays, for example.
How can I convert the example hash at the top of this question to the resultant hash below it?
Code
def group_em(h)
h.group_by { |_,v| v.drop(1) }.
transform_values do |a|
a.transpose.
last.
map(&:first).
sum(&:to_i).
to_s
end.
each_with_index.
with_object({}) { |((a,v),i),g| g[i] = [v,*a] }
end
Example
h = {
"0" => ["1", "true", "21"],
"1" => ["2", "true", "21"],
"2" => ["3", "false", "21"],
"3" => ["4", "true", "22"],
"4" => ["5", "true", "22"],
"5" => ["6", "true", "22"],
"6" => ["7", "false", "21"]
}
group_em(h)
#=> {0=>["3", "true", "21"],
# 1=>["10", "false", "21"],
# 2=>["15", "true", "22"]}
Explanation
The major steps
For the hash h above the major steps are as follows.
p = h.group_by { |_,v| v.drop(1) }
#=> {["true", "21"]=>[["0", ["1", "true", "21"]],
# ["1", ["2", "true", "21"]]],
# ["false", "21"]=>[["2", ["3", "false", "21"]],
# ["6", ["7", "false", "21"]]],
# ["true", "22"]=>[["3", ["4", "true", "22"]],
# ["4", ["5", "true", "22"]],
# ["5", ["6", "true", "22"]]]}
q = p.transform_values do |a|
a.transpose.
last.
map(&:first).
sum(&:to_i).
to_s
end
#=> {["true", "21"]=>"3", ["false", "21"]=>"10", ["true", "22"]=>"15"}
enum0 = q.each_with_index
#=> #<Enumerator: {["true", "21"]=>"3", ["false", "21"]=>"10",
# ["true", "22"]=>"15"}:each_with_index>
enum1 = enum0.with_object({})
#=> #<Enumerator: #<Enumerator: {["true", "21"]=>"3", ["false", "21"]=>"10",
# ["true", "22"]=>"15"}:each_with_index>:with_object({})>
enum1.each { |((a,v),i),g| g[i] = [v,*a] }
#=> {0=>["3", "true", "21"],
# 1=>["10", "false", "21"],
# 2=>["15", "true", "22"]}
We can see the values that will be generated and passed to the block by by the enumerator enum1 by converting it to an array:
enum1.to_a
#=> [[[[["true", "21"], "3"], 0], []],
# [[[["false", "21"], "10"], 1], []],
# [[[["true", "22"], "15"], 2], []]]
If you compare the return value for enum0 with that of enum1 you can think of the latter as a compound enumerator, though Ruby does not employ that term.
Details of Hash#transform_values
Now let's look more closely at the calculation of q. The first value of p is passed to the block by Hash#transform_values (which made its debut in MRI 2.4) and becomes the value of the block variable a:
a = p.first.last
#=> [["0", ["1", "true", "21"]], ["1", ["2", "true", "21"]]]
The block calculations are as follows.
b = a.transpose
#=> [["0", "1"], [["1", "true", "21"], ["2", "true", "21"]]]
c = b.last
#=> [["1", "true", "21"], ["2", "true", "21"]]
d = c.map(&:first) # ~same as c.map { |a| a.first }
#=> ["1", "2"]
e = e.sum(&:to_i) # ~same as e.sum { |s| s.to_i }
#=> 3
e.to_s
#=> "3"
We see that the value a has been transformed to "3". The remaining calculations to compute q are similar.
Documentation links
You can find documentation for the methods I've used at the following links for classes Array (drop, transpose, last, first and sum), Integer (to_s), String (to_i) and Enumerator (with_object and next), and the module Enumerable (group_by, map and each_with_index).
Decomposition of nested objects
There is one more tricky-bit I would like to mention. That is the line
enum1.each { |((a,v),i),g| g[i] = [v,*a] }
I've written the block variables in such a way to decompose the values that are generated by the enumerator enum1 and passed to the block. I'm sure that it must look quite imposing for a newbie, but it's not so bad if you take step-by-step, as I will explain.
Firstly, suppose I had a single block variable r (enum1.each { |r|...}). The first value is generated and passed to the block, assigning a value to r:
r = enum1.next
#=> [[[["true", "21"], "3"], 0], []]
We could then execute the following statement in the block to decompose (of disambiguate) r as follows:
((a,v),i),g = r
#=> [[[["true", "21"], "3"], 0], []]
producing the following assignments:
a #=> ["true", "21"]
v #=> "3"
i #=> 0
g #=> []
It is equivalent, and simpler, to replace |r| in the block with |((a,v),i),g|.
If you study the locations of the brackets in the nested array produced by enum1.next you will see how I determined where I needed parentheses when writing the block variables. This decomposition of nested arrays and other objects is a very convenient and powerful feature or Ruby, one that is much underused.
I am not a ruby developer so I can't suggest any best practices but simple algorithm that comes in my mind after reading this, is to create a new hash and check if array values are in it or not, if not then append new value like this.
h = {
"0" => ["1", "true", "21"],
"1" => ["2", "true", "21"],
"2" => ["3", "false", "21"],
"3" => ["4", "true", "22"],
"4" => ["5", "true", "22"],
"5" => ["6", "true", "22"],
"6" => ["7", "false", "21"]
}
new_h = {}
h.each do |key, val|
x1 = val.at(1)
x2 = val.at(2)
found = false
new_h.each do |key1, val2|
y1 = val2.at(1)
y2 = val2.at(2)
if x1 === y1 && x2 === y2
found = true
arr = [val2.at(0).to_i + val.at(0).to_i, x1, x2]
new_h[key1] = arr
end
end
if !found
new_h[new_h.length] = val
end
if new_h.empty?
new_h[key] = val
end
end
puts "#{new_h}"
Just out of curiosity.
input.
values.
map { |i, *rest| [rest, i.to_i] }.
group_by(&:shift).
map do |*key, values|
[values.flatten.sum.to_s, *key.flatten]
end
references : Enumerable#group_by, Enumerator#with_index, Array#to_h
key_sum = ->(group) { group.sum { |key, _| key.to_i }.to_s }
given_hash.values.group_by { |_, *rest| rest }.
map.with_index { |(key, group), idx| [idx, [key_sum.call(group), *key]] }.to_h
#=> {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}
group by
given_hash.values.group_by { |_, *rest| rest }
#=> { ["true", "21"] => [["1", "true", "21"], ["2", "true", "21"]]...
key_sum function
key_sum = ->(group) { group.sum { |key, _| key.to_i }.to_s }
key_sum.call([["1", "true", "21"], ["2", "true", "21"]]) #=> '3'
to_h
[[0, ["3", "true", "21"]], [1, ["10", "false", "21"]], [2, ["15", "true", "22"]]].to_h
#=> {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}
This should answer your question, despite being a very long way of solving it. I'm sure there are shortcuts to solve it more easily but you requested a clear explanation of what's happening and I hope that this guides you through how to solve it.
# Start with a hash
hash = {
"0" => ["1", "true", "21"],
"1" => ["2", "true", "21"],
"2" => ["3", "false", "21"],
"3" => ["4", "true", "22"],
"4" => ["5", "true", "22"],
"5" => ["6", "true", "22"],
"6" => ["7", "false", "21"]
}
# Extract just the values from the hash into an array
values = hash.values
added_values = values.map do |array|
# Find all arrays that match this array's values at indices [1] and [2]
matching = values.select { |a| a[1] == array[1] && a[2] == array[2] }
sum = 0
# Add the values at index 0 from each matching array
matching.each { |match| sum += match[0].to_i }
# Return a new array with these values
[sum.to_s, array[1], array[2]]
end
# Reject any duplicative arrays
added_values.uniq!
# Convert the array back to a hash
added_values.each_with_index.each_with_object({}) { |(array, index), hash| hash[index] = array }
Hash and Array have most powerful in-built functions in ruby.
z = h.group_by { |k,v| v[1..2] }.keep_if { |k,v| v.length > 1 }
val = z.map { |k,v| [v.map { |x| x[1] }.map(&:first).map(&:to_i).inject(:+).to_s, k[0], k[1]] }
val.each_with_index.inject({}) { |m,(x,i)| m[i] = x; m }
=> {0 =>["3", "true", "21"], 1 =>["10", "false", "21"], 2 =>["15", "true", "22"]}
If you know these functions then you do not need complex implementation ever. Happy learning :)
h
.values
.group_by{|_, *a| a}
.map
.with_index{|(k, a), i| [i, [a.inject(0){|acc, (n, *)| acc + n.to_i}.to_s, *k]]}
.to_h
# => {0=>["3", "true", "21"], 1=>["10", "false", "21"], 2=>["15", "true", "22"]}

Iterating hash of arrays to create an array of array

Suppose I have a hash
#attribute_type = {
typeA: ['a', 'b', 'c'],
typeB: ['1', '2', '3'],
typeC: ['9', '8', '7']
}
I want to iterate over the values so I can create an array having all distinct possible combinations of the three arrays, for example:
['a', '1', '9'], ['a', '1', '8'], ['a', '1', '7'], ['a', '2', '9'], ...
Is this possible?
h = { :typeA=>['a','b','c'], :typeB=>['1','2','3'], :typeC=>['9','8','7'] }
first, *rest = h.values
#=> [["a", "b", "c"], ["1", "2", "3"], ["9", "8", "7"]]
first.product(*rest)
#=> [["a", "1", "9"], ["a", "1", "8"], ["a", "1", "7"],
# ["a", "2", "9"], ["a", "2", "8"], ["a", "2", "7"],
# ["a", "3", "9"], ["a", "3", "8"], ["a", "3", "7"],
# ["b", "1", "9"], ["b", "1", "8"], ["b", "1", "7"],
# ["b", "2", "9"], ["b", "2", "8"], ["b", "2", "7"],
# ["b", "3", "9"], ["b", "3", "8"], ["b", "3", "7"],
# ["c", "1", "9"], ["c", "1", "8"], ["c", "1", "7"],
# ["c", "2", "9"], ["c", "2", "8"], ["c", "2", "7"],
# ["c", "3", "9"], ["c", "3", "8"], ["c", "3", "7"]]
See Array#product.
My 2 cents.
Pretty the same as Cary Swoveland, but just one line:
h.values.first.product(*h.values.drop(1))

Changing a value in an array of random numbers in a loop

I'm trying to make a loop that when the random number matches the same index of the input the value is changed and stays that way for each following loop.
input = gets.chomp
tries_left = 12
while(tries_left > 0)
tries_left -= 1
computer = 4.times.map do rand(0..6) end.join
if computer[0] == input[0]
computer[0] = input[0]
end
end
in the code above after the first loop the value stored to input[0] resets.
computer = 4.times.map do rand(0..6) end.join
input = gets.chomp
tries_left = 12
while(tries_left > 0)
tries_left -= 1
if computer[0] == input[0]
computer[0] = input[0]
end
if I take computer out of the loop like this, it will have the same random number generated each time. Once again I need it to generate new numbers each time besides what was already a match.
If you make the computer an array of strings, you can freeze it to prevent further modifications to it, and then replace the contents in computer when it doesn't match the index:
input = gets.chomp
tries_left = 12
computer = Array.new(4) { '' }
# setting the srand to 1234, the next 48 calls to 'rand(0..6)' will always
# result in the following sequence:
# 3, 6, 5, 4, 4, 0, 1, 1, 1, 2, 6, 3, 6, 4, 4, 2, 6, 2, 0, 0, 4, 5, 0, 1,
# 6, 6, 2, 0, 3, 4, 5, 2, 6, 2, 3, 3, 0, 1, 3, 0, 3, 2, 3, 4, 1, 3, 3, 3
# this is useful for testing things are working correctly,
# but take it out for 'live' code
srand 1234
while tries_left > 0
# no need to keep iterating if we've generated all the correct values
if computer.all?(&:frozen?)
puts "won #{computer.inspect} in #{12 - tries_left} tries"
break
end
tries_left -= 1
computer.each.with_index do |random, index|
# generate a new random number here unless they guessed correctly previously
random.replace(rand(0..6).to_s) unless random.frozen?
# if they've guessed the new random number, mark the string so they we
# don't update it
random.freeze if random == input[index]
end
puts "#{computer.inspect} has #{computer.count(&:frozen?)} correct numbers"
end
and then when you run the script:
$ echo 3654 | ruby example.rb
# ["3", "6", "5", "4"] has 4 correct numbers
# won ["3", "6", "5", "4"] in 1 tries
$ echo 3644 | ruby example.rb
# ["3", "6", "5", "4"] has 3 correct numbers
# ["3", "6", "4", "4"] has 4 correct numbers
# won ["3", "6", "4", "4"] in 2 tries
$ echo 3555 | ruby example.rb
# ["3", "6", "5", "4"] has 2 correct numbers
# ["3", "4", "5", "0"] has 2 correct numbers
# ["3", "1", "5", "1"] has 2 correct numbers
# ["3", "1", "5", "2"] has 2 correct numbers
# ["3", "6", "5", "3"] has 2 correct numbers
# ["3", "6", "5", "4"] has 2 correct numbers
# ["3", "4", "5", "2"] has 2 correct numbers
# ["3", "6", "5", "2"] has 2 correct numbers
# ["3", "0", "5", "0"] has 2 correct numbers
# ["3", "4", "5", "5"] has 3 correct numbers
# ["3", "0", "5", "5"] has 3 correct numbers
# ["3", "1", "5", "5"] has 3 correct numbers
It’s not quite clear what you are trying to accomplish, but this:
if computer[0] == input[0]
computer[0] = input[0]
end
is obviously a noop. Nothing gets updated, since the computer[0], whatever it is, is being set to the same value it was. I believe you wanted to somehow use an index in the array:
4.times.map do |index|
value = rand(0..6)
# somehow check the similarity, e.g.:
if input[index] == value
# do something
end
end
I beg your pardon for the very vague answer, but it is really hard to understand the goal you are trying to achieve.

Converting an array into a hash with format { value => position_in_array }

I have an array of numbers in string format, and I want to convert them into a hash where the keys are the numbers and the values are the positions of those numbers in the array. So for example:
["1", "5", "3"]
should result in:
{ 1 => 0, 5 => 1, 3 => 2 }
I have the following code, which works:
my_hash = {}
my_array.each do |number_string|
my_hash[number_string.to_i] = my_array.index(number_string)
end
which iterates through the array and pushes each value and its position into the hash.
Is there a shorter and more elegant way to do it? Maybe something similar to Ruby's to_a function, but more like to_h(options).
Hash[["1", "5", "3"]
.map.with_index{|e, i| [e.to_i, i]}]
# => {1=>0, 5=>1, 3=>2}
or
["1", "5", "3"]
.each_with_object({}).with_index{|(e, h), i| h[e.to_i] = i}
# => {1=>0, 5=>1, 3=>2}
arr = ["1", "5", "3"]
ha = Hash[arr.map.with_index {|a, i| [a.to_i, i]}]
puts "ha: #{ha.inspect}"
irb(main):038:0> arr=["1", "5", "3"]
=> ["1", "5", "3"]
irb(main):039:0> Hash[arr.map.with_index {|a, i| [a, i]}]
=> {"1"=>0, "5"=>1, "3"=>2}
irb(main):040:0> Hash[arr.map.with_index {|a, i| [a.to_i, i]}]
=> {1=>0, 5=>1, 3=>2}

Resources