Edit hash values as per the array values and conditions - arrays

I have a hash as follows:
random_data = {
0=>{"ABC"=>"201604", "CDE"=>"0002874", "TPP"=>"000004971821467", "APD"=>"00182", "PRODUCTID"=>"ACH", "OPP"=>"00000000000176564", "CTC"=>"00000000000286920"},
1=>{"ABC"=>"301604", "CDE"=>"0001074", "TPP"=>"000004971821467", "APD"=>"00182", "PRODUCTID"=>"ACH", "OPP"=>"00000000000119964", "CTC"=>"00000000000211920"}
}
and the 2 arrays as follows:
arr1 = [CHAR,NUMBER,NUMBER,NUMBER,CHAR,NUMBER,CHAR]
arr2 = [6,(7,0),(15,0),(5,0),3,(15,2),17]
The elements of arr1 and arr2 are mapped with the hash values, the conditions are as follows:
if array arr1 's element is char, do nothing with the hash's value.
if array arr1 's element is NUMBER, the corresponding element in hash random_data should get converted to integer, i.e the leading zeros should be removed.
For example, arr1[1] is 'Number', hence for every second element of hash, leading zeros should get removed.
ie. "CDE"=>"0002874" should become "CDE"=>"2874"
If arr1 is NUMBER and arr2 element's second digit is not 0 then decimal should be added in the hash.
For exemple:
arr1[5] = Number
arr2[5] = (15,2)
then 5th element in hash OPP should become like this "OPP"=>"1199.64" i.e for decimal is added for 2 places (15,2)
The outputs is expected as follows:
random_data = {
0=>{"ABC"=>"201604", "CDE"=>"2874", "TPP"=>"4971821467", "APD"=>"182", "PRODUCTID"=>"ACH", "OPP"=>"1765.64", "CTC"=>"00000000000286920"},
1=>{"ABC"=>"301604", "CDE"=>"1074", "TPP"=>"4971821467", "APD"=>"182", "PRODUCTID"=>"ACH", "OPP"=>"1199.64", "CTC"=>"00000000000211920"}
}

arr1 = %w| CHAR NUMBER NUMBER NUMBER CHAR NUMBER CHAR |
#=> ["CHAR", "NUMBER", "NUMBER", "NUMBER", "CHAR", "NUMBER", "CHAR"]
arr2 = [6, [7,0], [15,0], [5,0], 3, [15,2], 17]
enum = arr1.zip(arr2).map { |type, obj| type=="CHAR" ? type : obj.last }.to_enum
#=> #<Enumerator: ["CHAR", 0, 0, 0, "CHAR", 2, "CHAR"]:each>
random_data.each do |k,h|
h.update(h) do |*,v|
obj = enum.next
case obj
when "CHAR"
v
else
x = v.to_i
x = x.to_f/10**obj if obj > 0
x.to_s
end
end
enum.rewind
end
#=> {0=>{"ABC"=>"201604", "CDE"=>"2874", "TPP"=>"4971821467", "APD"=>"182",
# "PRODUCTID"=>"ACH", "OPP"=>"0.17", "CTC"=>"00000000000286920"},
# 1=>{"ABC"=>"301604", "CDE"=>"1074", "TPP"=>"4971821467", "APD"=>"182",
# "PRODUCTID"=>"ACH", "OPP"=>"1199.64", "CTC"=>"00000000000211920"}}

arr1 = %w|CHAR NUMBER NUMBER NUMBER CHAR NUMBER CHAR|
arr2 = [6, [7,0], [15,0], [5,0], 3, [15,2], 17]
types = arr1.zip(arr2)
random_data.map do |_, hash|
hash.map.with_index do |(k, v), idx|
type, digits = types[idx]
[
k,
type == 'NUMBER' ? v.to_i.to_s.tap do |s|
s[/(?=.{#{[*digits][1]}}\z)/] = '.' unless [*digits][1].zero?
end : v
]
end
end

Related

If there's two maximum elements of an array?

In this code if user type 2, two times and 1, two times. Then there's two maximum elements and both Kinder and Twix should be printed. But how ? I probably can do this with if method but this will make my code even longer. Any cool version? Can I do this with just one if?
a = [0, 0, 0,]
b = ["Kinder", "Twix", "Mars"]
while true
input = gets.chomp.to_i
if input == 1
a[0] += 1
elsif input == 2
a[1] += 1
elsif input == 3
a[2] += 1
elsif input == 0
break
end
end
index = a.index(a.max)
chocolate = b[index] if index
print a.max,chocolate
The question really has nothing to do with how the array a is constructed.
def select_all_max(a, b)
mx = a.max
b.values_at(*a.each_index.select { |i| a[i] == mx })
end
b = ["Kinder", "Twix", "Mars"]
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
See Array#values_at.
This could alternatively be done in a single pass.
def select_all_max(a, b)
b.values_at(
*(1..a.size-1).each_with_object([0]) do |i,arr|
case a[i] <=> arr.last
when 0
arr << i
when 1
arr = [i]
end
end
)
end
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
p select_all_max [1, 1, 1], b
["Kinder", "Twix", "Mars"]
One way would be as follows:
First, just separate the input-gathering from the counting, so we'll just gather input in this step:
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
Now we can tally up the inputs. If you have Ruby 2.7 you can simply do counts_by_input = inputs.tally to get { "Twix" => 2, "Kinder" => 2 }. Otherwise, my preferred approach is to use group_by with transform_values:
counts_by_input = inputs.group_by(&:itself).transform_values(&:count)
# => { "Twix" => 2, "Kinder" => 2 }
Now, since we're going to be extracting values based on their count, we want to have the counts as keys. Normally we might invert the hash, but that won't work in this case because it will only give us one value per key, and we need multiple:
inputs_by_count = counts_by_input.invert
# => { 2 => "Kinder" }
# This doesn't work, it removed one of the values
Instead, we can use another group_by and transform_values (the reason I like these methods is because they're very versatile ...):
inputs_by_count = counts_by_input.
group_by { |input, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
# => { 2 => ["Twix", "Kinder"] }
The transform_values code here is probably a bit confusing, but one important thing to understand is that often times, calling Enumerable methods on hashes converts them to [[key1, val1], [key2, val2]] arrays:
counts_by_input.group_by { |input, count| count }
# => { 2 => [["Twix", 2], ["Kinder", 2]] }
Which is why we call transform_values { |keyvals| keyvals.map(&:first) } afterwards to get our desired format { 2 => ["Twix", "Kinder"] }
Anyway, at this point getting our result is very easy:
inputs_by_count[inputs_by_count.keys.max]
# => ["Twix", "Kinder"]
I know this probably all seems a little insane, but when you get familiar with Enumerable methods you will be able to do this kind of data transformation pretty fluently.
Tl;dr, give me the codez
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
inputs_by_count = inputs.
group_by(&:itself).
transform_values(&:count).
group_by { |keyvals, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
top_count = inputs_by_count.keys.max
inputs_by_count[top_count]
# => ["Twix", "Kinder"]
How about something like this:
maximum = a.max # => 2
top_selling_bars = a.map.with_index { |e, i| b[i] if e == maximum }.compact # => ['Kinder', 'Twix']
p top_selling_bars # => ['Kinder', 'Twix']
If you have
a = [2, 2, 0,]
b = ['Kinder', 'Twix', 'Mars']
You can calculate the maximum value in a via:
max = a.max #=> 2
and find all elements corresponding to that value via:
b.select.with_index { |_, i| a[i] == max }
#=> ["Kinder", "Twix"]

Get the longest prefix in arrays Ruby

I have an array of arrays. Within each subarray, if two or more elements share a prefix whose length equals to or is greater than eight, then I want to replace those elements by their longest prefix. For this array:
m = [
["A", "97455589955", "97455589920", "97455589921"],
["B", "2348045101518", "2348090001559"]
]
I expect an output like this:
n = [
["A", "974555899"],
["B", "2348045101518", "2348090001559"]
]
For first subarray in m, the longest prefix is "974555899" of length nine.
974555899-55
974555899-20
974555899-21
For the second subarray, the longest prefix is "23480" of length five, and that is shorter than eight. In this case, the second subarray is left as is.
23480-45101518
23480-90001559
For this input:
m = [
["A", "2491250873330", "249111222333", "2491250872214", "2491250872213"],
["B", "221709900000"],
["C", "6590247968", "6590247969", "6598540040", "65985400217"]
]
The output should be like this:
[
["A", "2491250873330", "249111222333", "249125087221"],
["B", "221709900000"],
["C", "659024796", "65985400"]
]
For array m[0], there is no prefix long enough between its four numbers, but there is a prefix 249125087221 of length twelve between m[0][2] and m[0][3]. For array m[2], there is prefix "659024796" of length nine between m[2][0] and m[2][1], and there is another prefix "65985400" of length eight between m[2][2] and m[2][3].
I constructed the code below:
m.map{|x, *y|
[x, y.map{|z| z[0..7]}.uniq].flatten
}
With my code with the first input, I get this output.
[
["A", "97455589"],
["B", "23480451", "23480900"]
]
I'm stuck on how to get dynamically the common prefix without setting a fixed length.
Code
def doit(arr, min_common_length)
arr.map do |label, *values|
[label, values.group_by { |s| s[0, min_common_length] }.
map { |_,a| a.first[0, nbr_common_digits(a, min_common_length)] }]
end
end
def nbr_common_digits(a, min_common_length)
max_digits = a.map(&:size).min
return max_digits if max_digits == min_common_length + 1
(min_common_length..max_digits).find { |i|
a.map { |s| s[i] }.uniq.size > 1 } || max_digits
end
Example
arr = [["A","2491250873330","249111222333","2491250872214","2491250872213"],
["B","221709900000"],
["C","6590247968","6590247969","6598540040","65985400217"]]
doit(arr, 8)
#=> [["A", ["249125087", "249111222333"]],
# ["B", ["221709900000"]],
# ["C", ["659024796", "65985400"]]]
Explanation
Let's first consider the helper method, nbr_common_digits. Suppose
a = ["123467", "12345", "1234789"]
min_common_length = 2
then the steps are as follows.
max_digits = a.map(&:size).min
#=> 5 (the length of "12345")
max_digits == min_common_length + 1
#=> 5 == 2 + 1
#=> false, so do not return max_digits
b = (min_common_length..max_digits).find { |i| a.map { |s| s[i] }.uniq.size > 1 }
#=> (2..5).find { |i| a.map { |s| s[i] }.uniq.size > 1 }
#=> 4
At this point we must consider the possibility that b will equal nil, which occurs when the first 5 characters of all strings are equal. In that case we should return max_digits, which is why we require the following.
b || max_digits
#=> 4
In doit the steps are as follows.
min_common_length = 8
Firstly, we use Enumerable#group_by to group values by their first min_common_length digits.
arr.map { |label, *values| [label,
values.group_by { |s| s[0, min_common_length] }] }
#=> [["A", {"24912508"=>["2491250873330", "2491250872214", "2491250872213"],
# "24911122"=>["249111222333"]}],
# ["B", {"22170990"=>["221709900000"]}],
# ["C", {"65902479"=>["6590247968", "6590247969"],
# "65985400"=>["6598540040", "65985400217"]}]]
The second step is to compute the longest common lengths and replace values as required.
arr.map do |label, *values| [label,
values.group_by { |s| s[0, min_common_length] }.
map { |_,a| a.first[0, nbr_common_digits(a, min_common_length)] }]
end
#=> [["A", ["249125087", "249111222333"]],
# ["B", ["221709900000"]],
# ["C", ["659024796", "65985400"]]]
The first block variable in the second map's block (whose value equals a string with nbr_common_length characters--group_by's grouping criterion) is represented by an underscore (a legitimate local variable) to signify that it is not used in the block calculation.
This is an interesting problem. Here's my solution:
def lcn(lim, *arr)
# compute all substrings of lengths >= lim and build a lookup by length
lookup = lcn_explode(lim, arr)
# first pass: look for largest common number among all elements
res, = lcn_filter(arr, lookup) { |size| size == arr.size }
return res unless res.empty?
# second pass: look for largest common number among some elements
res, rem = lcn_filter(arr, lookup) { |size| size > 1 }
# append remaining candidates with no matches
res.concat(rem)
end
def lcn_explode(lim, arr)
memo = Hash.new { |h, k| h[k] = Array.new }
arr.uniq.each do |n|
lim.upto([n.size, lim].max) do |i|
memo[i] << [n[0, i], n]
end
end
memo
end
def lcn_filter(arr, lookup)
memo = []
lookup.keys.sort!.reverse_each do |i|
break if arr.empty?
matches = Hash.new { |h, k| h[k] = Array.new }
lookup[i].each do |m, n|
matches[m] << n if arr.include?(n)
end
matches.each_pair do |m, v|
next unless yield v.size
memo << m
# remove elements from input array so they won't be reused
arr -= v
end
end
return memo, arr
end
You use it like so:
p lcn(8, "97455589955", "97455589920", "97455589921") => ["974555899"]
Or:
m.each do |key, *arr|
p [key, *lcn(8, *arr)]
end
Which prints:
["A", "249125087221", "2491250873330", "249111222333"]
["B", "221709900000"]
["C", "659024796", "65985400"]
Your task can be splitten into two: calculating Largest Common Number and modifying original array.
Largest Common Number operates on arrays, therefore, it should a method of Array.
After calculating LCN you can just compare its length with the limit (i.e. 8).
class Array
def lcn
first.length.times do |index|
numb = first[0..index]
return numb unless self[1..-1].all? { |n| n.start_with?(numb) }
end
first
end
end
def task(m, limit = 8)
m.map { |i,*n| [i, n.lcn.length >= limit ? n.lcn : n].flatten }
end
task(m) # => [["A", "9745558995"], ["B", "2348045101518", "2348090001559"]]
In your solution you do not actually implement lcn finding and filtering output.

Can't get updated values in array after using .map method

I need to implement a method, which works that way:
# do_magic("abcd") # "Aaaa-Bbb-Cc-D"
# do_magic("a") # "A"
# do_magic("ab") # "Aa-B"
# do_magic("teSt") # "Tttt-Eee-Ss-T"
My decision was to convert a string into an array, iterate through this array and save the result. The code works properly inside the block, but I'm unable to get the array with updated values with this solution, it returns the same string divided by a dash (for example "t-e-S-t" when ".map" used or "3-2-1-0" when ".map!" used):
def do_magic(str)
letters = str.split ''
counter = letters.length
while counter > 0
letters.map! do |letter|
(letter * counter).capitalize
counter -= 1
end
end
puts letters.join('-')
end
Where is the mistake?
You're so close. When you have a block (letters.map!), the return of that block is the last evaluated statement. In this case, counter -= 1 is being mapped into letters.
Try
l = (letter * counter).capitalize
counter -= 1
l
You can try something like this using each_with_index
def do_magic(str)
letters = str.split("")
length = letters.length
new_letters = []
letters.each_with_index do |letter, i|
new_letters << (letter * (length - i)).capitalize
end
new_letters.join("-")
end
OR
using map_with_index equivalent each_with_index.map
def do_magic(str)
letters = str.split("")
length = letters.length
letters.each_with_index.map { |letter, i|
(letter * (length - i)).capitalize
}.join("-")
end
I suggest the following.
def do_magic(letters)
length = letters.size
letters.downcase.each_char.with_index.with_object([]) { |(letter, i), new_letters|
new_letters << (letter * (length - i)).capitalize }.join
end
do_magic 'teSt'
# => "TtttEeeSsT"
Let's go through the steps.
letters = 'teSt'
length = letters.size
#=> 4
str = letters.downcase
#=> "test"
enum0 = str.each_char
#=> #<Enumerator: "test":each_char>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: "test":each_char>:with_index>
enum2 = enum1.with_object([])
#=> #<Enumerator: #<Enumerator: #<Enumerator: "test":each_char>:
# with_index>:with_object([])>
Carefully examine the return values from the creation of the enumerators enum0, enum1 and enum2. The latter two may be thought of as compound enumerators.
The first element is generated by enum2 (the value of enum2.next) and the block variables are assigned values using disambiguation (aka decomposition).
(letter, i), new_letters = enum2.next
#=> [["t", 0], []]
letter
#=> "t"
i #=> 0
new_letters
#=> []
The block calculation is then performed.
m = letter * (length - i)
#=> "tttt"
n = m.capitalize
#=> "Tttt"
new_letters << n
#=> ["Tttt"]
The next element is generated by enum2, passed to the block and the block calculations are performed.
(letter, i), new_letters = enum2.next
#=> [["e", 1], ["Tttt"]]
letter
#=> "e"
i #=> 1
new_letters
#=> ["Tttt"]
Notice how new_letters has been updated. The block calculation is as follows.
m = letter * (length - i)
#=> "eee"
n = m.capitalize
#=> "Eee"
new_letters << n
#=> ["Tttt", "Eee"]
After the last two elements of enum2 are generated we have
new_letters
#=> ["Tttt", "Eee", "Se", "T"]
The last step is to combine the elements of new_letters to form a single string.
new_letters.join
#=> "TtttEeeSeT"

How to check an array that it contains equal number of characters or not using Ruby

I have an array like this ['n','n','n','s','n','s','n','s','n','s'] and I want to check if there are equal counts of characters or not. In the above one I have 6 ns and 4 ss and so they are not equal and I tried, but nothing went correct. How can I do this using Ruby?
Given array:
a = ['n','n','n','s','n','s','n','s','n','s']
Group array by it's elements and take only values of this group:
(f,s) = a.group_by{|e| e}.values
Compare sizes:
f.size == s.size
Result: false
Or you can try this:
x = ['n','n','n','s','n','s','n','s','n','s']
x.group_by {|c| c}.values.map(&:size).inject(:==)
You can go for something like this:
def eq_num? arr
return false if arr.size == 1
arr.uniq.map {|i| arr.count(i)}.uniq.size == 1
end
arr = ['n','n','n','s','n','s','n','s','n','s']
eq_num? arr #=> false
arr = ['n','n','n','s','n','s','s','s']
eq_num? arr #=> true
Works for more than two kinds of letters too:
arr = ['n','n','t','s','n','t','s','s','t']
eq_num? arr #=> true
Using Array#count is relatively inefficient as it requires a full pass through the array for each element whose instances are being counted. Instead use Enumerable#group_by, as others have done, or use a counting hash, as below (see Hash::new):
Code
def equal_counts?(arr)
arr.each_with_object(Hash.new(0)) { |s,h| h[s] += 1 }.values.uniq.size == 1
end
Examples
equal_counts? ['n','n','n','s','n','s','n','s','n','s']
#=> false
equal_counts? ['n','r','r','n','s','s','n','s','r']
#=> true
Explanation
For
arr = ['n','n','n','s','n','s','n','s','n','s']
the steps are as follows.
h = arr.each_with_object(Hash.new(0)) { |s,h| h[s] += 1 }
#=> {"n"=>6, "s"=>4}
a = h.values
#=> [6, 4]
b = a.uniq
#=> [6, 4]
b.size == 1
#=> false

I want to return the string that has the highest sum in an array for Ruby

So far I am able to return the sums of the strings:
puts "Please enter strings of #'s to find the number that has the greatest sum."
puts "Use commas to separate #'s."
user_input = gets.chomp
array = user_input.split(",")
array.map do |num_string|
num_string.chars.map(&:to_i).inject(:+)
end
But I wish to return the string that adds up to the highest value. For instance: If I have array = ["123","324","644"] I need it to return the sums of each value so the result should be 6,9,and 14 respectively. Since 1+2+3=6 etc. I am this far but now I need to return "644" as the answer since it is the string that sums to the highest value.
I suggest you use Enumerable#max_by:
arr = ["123","324","644"]
arr.max_by { |s| s.each_char.reduce(0) { |t,c| t+c.to_i } }
#=> "644"
Let's see how this works. Enumerable#max_by computes a value for each element of arr and returns the element of arr whose computed value is greatest. The calculation of the value for each element is done by max_by's block.
enum0 = arr.max_by
#=> #<Enumerator: ["123", "324", "644"]:max_by>
You can see the three elements of this enumerator. Sometimes it's not so obvious, but you can always see what they are by converting the enumerator to an array:
enum0.to_a
#=> ["123", "324", "644"]
Elements of enum0 are passed to the block by the method Enumerator#each (which in turn calls Array#each). You would find that:
enum0.each { |s| s.each_char.reduce(0) { |t,c| t+c.to_i } }
returns "644".
The first element of the enumerator ("123") is passed to the block by each and assigned to the block variable s. We can simulate that with the method Enumerator#next:
s = enum0.next
#=> "123"
Within the block we have another enumerator:
enum1 = s.each_char
#=> #<Enumerator: "123":each_char>
enum1.to_a
#=> ["1", "2", "3"]
enum1.reduce(0) { |t,c| t+c.to_i }
#=> 6
This last statement is equivalent to:
0 # initial value of t
0 + "1".to_i #=> 1 (new value of t)
1 + "2".to_i #=> 3 (new value of t)
3 + "3".to_i #=> 6 (new value of t)
6 is then returned by reduce.
For the next element of enum0:
s = enum0.next
#=> "324"
s.each_char.reduce(0) { |t,c| t+c.to_i }
#=> 9
and for the last element enum0:
s = enum0.next
#=> "644"
s.each_char.reduce(0) { |t,c| t+c.to_i }
#=> 14
Since 14 is the largest integer in [6, 9, 14], max_by returns the last element of arr, "644".
yourArray = ["123","324","644"]
highest = -1
pos = -1
yourArray.each_with_index{ |str, i|
sum = str.split("").map(&:to_i).reduce(:+)
highest = [highest,sum].max
pos = i if highest == sum
}
puts "highest value is " + yourArray[pos]
lets look at each step, map lets us enumerate the array and returns a new array with whatever we return from map:
yourArray.map{ |str| ... }
Here we're taking our strings in the array and splitting them into arrays of ["1","2","3"] for example, then the to_i portion converts this to an array such as [1,2,3] then finally reduce gives us our sum:
sum = str.split("").map(&:to_i).reduce(:+)
Here we're keeping track throughout the loop of the highest sum we've seen so far:
highest = [highest,sum].max
If ever the current sum is the highest, store its position in the array
pos = i if highest = sum
finally, use the stored array position to print out whatever exists there in the original array (the positions line up):
puts "highest value is " + yourArray[pos]

Resources