Get the longest prefix in arrays Ruby - arrays

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.

Related

Checking that string contains only unique array elements, repeating letters

I am creating a method to test if a string contains only array elements in a game of scrabble.
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a"] if word is "beach"
Originally I used this block:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.index{ |x| !letters_in_hand.include?(x) }.nil?
end
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a", "c"]
if word = "beach", method returns true. However, if word = "beeeach", it will still return true even though the array only contains 1 "e".
SO, I tried deleting the array element after it was compared:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.each do
if letters_in_hand.include?(input[i])
letters_in_hand.delete(input[i])
else
return false
end
end
end
BUT, given "beacch", false is returned even though there are 2 c's in the array. So it seems every like letter is being deleted.
Send help!
The trouble with Ruby array operations is most treat them as a set of unique values, as in:
%w[ a a b b c c ] - %w[ a b c ]
# => []
Where that removes every a, b and c, not just the first. The same goes for delete unless you use a very specific index, but that gets messy in a hurry since deleting shuffles the remaining indexes, etc.
I'd consider storing the hand as a letter/count pair, as in:
def hand_count(str)
str.chars.group_by(&:itself).map { |l,a| [ l, a.length ] }.to_h
end
Where that gives you a hash instead of an array:
hand_count('example')
# => {"e"=>2, "x"=>1, "a"=>1, "m"=>1, "p"=>1, "l"=>1}
So now you can write a "sub" method:
def hand_sub(str, sub)
hand = hand_count(str)
hand_count(sub).each do |l, c|
# Unless the letter is present in the hand...
unless (hand.key?(l))
# ...this isn't possible.
return false
end
# Subtract letter count
hand[l] -= c
# If this resulted in a negative number of remaining letters...
if (hand[l] < 0)
# ...this isn't possible.
return false
end
end
# Convert back into a string...
hand.map do |l, c|
# ...by repeating each letter count times.
l * c
end.join
end
Where that works quite simply:
hand_sub('example', 'exam')
# => "epl"
hand_sub('example', 'expel')
# => "am"
hand_sub('example', 'plexi')
# => false
hand_sub('example', 'ell')
# => false
def uses_available_letters?(word, hand)
result = true
word.split('').each do |letter|
if hand.include?(letter)
hand.delete_at(hand.index(letter))
else
result = false
end
end
result
end
2.6.5 :065 > uses_available_letters?('beeeach', %w[b l e h c a e e])
=> true
2.6.5 :066 > uses_available_letters?('beach', %w[b l e h c a])
=> true
2.6.5 :067 > uses_available_letters?('beeeach', %w[b l e h c a])
=> false
2.6.5 :068 > uses_available_letters?('beeeach', %w[b l e d h e c e r a])
=> true
Here are two ways that could be done.
Compare counting hashes
def uses_available_letters?(word, letters_in_hand)
word_tally = word.each_char.tally
letters_in_hand_tally = letters_in_hand.tally
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
letters_in_hand = ["b", "l", "c", "e", "o", "h", "e", "a"]
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("beleche", letters_in_hand)
#=> false
When word = "beleche",
word_tally
#=> {"b"=>1, "e"=>3, "l"=>1, "c"=>1, "h"=>1}
letters_in_hand_tally
#=> {"b"=>1, "l"=>1, "c"=>1, "e"=>2, "o"=>1, "h"=>1, "a"=>1}
See Enumerable#tally, which was introduced in Ruby v2.7. To support earlier versions of Ruby one could use the form of Hash::new that takes an argument (here zero), called the default value, and no block.
def uses_available_letters?(word, letters_in_hand)
word_tally = tally_ho(word.chars)
letters_in_hand_tally = tally_ho(letters_in_hand)
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
def tally_ho(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end
If letters_in_hand_tally does not have a key k, letters_in_hand_tally[k] #=> nil, resulting in the comparison nil >= v, which would raise an exception. It is for that reason I wrote letters_in_hand_tally[k].to_i, as nil.to_i #=> 0 and all values of word_tally will be positive integers.
One could alternatively write the block as follows:
{ |k,v| letters_in_hand_tally.key?(k) && letters_in_hand_tally[k] >= v }
Treat duplicate letters in letters_in_hand as distinct letters
This second method assumes letters_in_hand is given by a string, rather than an array. For example:
letters_in_hand = "blceohea"
Of course we could always form that string from the array as a first step, but I think it's more pleasing for letters_in_hand to be a string, just as word is. The following method makes use of the fact that String#index accepts an optional second argument that equals the index into the string where the search is to begin:
def uses_available_letters?(word, letters_in_hand)
start_index = Hash.new(0)
word.each_char.all? do |c|
i = letters_in_hand.index(c, start_index[c])
return false if i.nil?
start_index[c] = i + 1
end
true
end
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("belecher", letters_in_hand)
#=> false

I need some clarification on how this 2D array can be created in Ruby?

I'm currently learning Ruby through App Academy Open, and came across a problem that I solved differently than the course solution. I could use some clarification on how the course solution works.
We have to define a function "zip" that takes any number of arrays as arguments (but all arrays the same length). The function should return a 2D array where each subarray contains the elements at the same index from each argument.
zip(['a','b','c'],[1,2,3])
should return:
[['a',1],['b',2],['c',3]]
Here is my solution:
def zip(*arrs)
main_arr = Array.new(arrs[0].length) {Array.new}
arrs.each do |array|
array.each_with_index do |ele, ele_idx|
main_arr[ele_idx] << ele
end
end
main_arr
end
And here is the course solution:
def zip(*arrays)
length = arrays.first.length
(0...length).map do |i|
arrays.map { |array| array[i] }
end
end
Can someone explain how the 2D array is being built within the mapped range above? I'm a bit confused as a beginner and could use some clarification.
EDIT:
Thank you very much iGian. Explanation really helped.
Running the code below should be self explanatory, see the comments:
def zip_steps(*arrays)
# get the size of the array
length = arrays.first.length
# mapping the range up to use as indexing
p (0...length).map { |i| i } #=> [0, 1, 2]
# map the arrays
p arrays.map { |array| array } #=> [["a", "b", "c"], [1, 2, 3]]
# map the arrays returning a specific element at index 1, for example
p arrays.map { |array| array[1] } #=> ["b", 2]
# put arrays mapping inside the range mapping
# where instead of returning the element 1
# it returns the element i
(0...length).map do |i|
arrays.map { |array| array[i] }
end
end
ary1 = ['a','b','c']
ary2 = [1,2,3]
p zip_steps(ary1, ary2) #=> [["a", 1], ["b", 2], ["c", 3]]

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

Nested loops in Ruby

I am trying to count the number of similar prefix beginnings to a string in Ruby. e.g; input "ababaa" should output 11;
ababaa = 6
babaa = 0
abaa = 3
baa = 0
aa = 1
a = 1
I have got as far as the code below, using a nested loop to go through each of the above as an array, however it looks as though Ruby is currently outputting the count of just the first Array object, "ababaa".
Solved, thanks :)
def string_suffix(string)
num = 0
ary = []
string.length.times do
ary << string[num..string.length]
num = num + 1
end
result = 0
ary.each do |x| # ["ababaa", "babaa", "abaa", "baa", "aa", "a"]
x.chars.each_with_index do |c,index|
break unless c == string[index]
result = result + 1
end
end
return result
end
I have looked far and wide and still cannot solve the issue, It looks like the (final, nested) array is breaking after the first iteration of the 'ary' Array and just returning that output.
You are returning the result while you are still in the loop. You need to move result = 0 out of the loop, and move the return result statement outside of the loop too. At the moment the function is going through the first iteration of the loop ("ababaa", for which all characters match), but you want result to equal the sum of all results.
Additionally, instead of doing:
count = 0
x.chars.each do |x|
if x == string[count]
count = count + 1
result = result + 1
else
count = count + 1
end
end
You could use the function each_with_index, to get
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
end
end
However, since you are trying to count how many characters in the substring are a prefix of string, you want to break when you first find a character c that is not equal to string[index], so that you don't end up counting extra characters. The loop then becomes:
x.chars.each_with_index do |c,index|
if c == string[index]
result = result + 1
else
break
end
end
I noticed you are returning the result inside your second loop, at the end. This means that after you've gone through the first item in your array the function returns just the result for the first item. Move your return statement to outside the loop.
As I understand, the problem is this: given a string s, for each i = 0..s.size-1, compute the number of leading characters of s[0..-i-1] that match the corresponding characters (i.e., at same offsets) of s[i..-1], and sum these s.size subtotals.
Here's a Ruby-like way to do that, using Enumerable#reduce (aka inject) and Enumerable#take_while:
str = "ababaa"
arr = str.chars
(0...arr.size).reduce(0) do |tot,i|
tot + arr[0..-i-1].zip(arr[i..-1]).take_while { |x,y| x == y }.size
end
#=> 11
The steps:
arr = str.chars
#=> ["a", "b", "a", "b", "a", "a"]
r = 0...arr.size
#=> 0...6
When the first element of r is passed to the block, the block variables are set to:
tot = 0
i = 0
The block calculation is therefore as follows:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-1].zip(arr[0..-1])
#=> arr.zip(arr)
#=> ["a", "b", "a", "b", "a", "a"].zip(["a", "b", "a", "b", "a", "a"])
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]
tot + b.size
#=> 0 + 6
#=> 6
Note that this calculation will always equal arr.size for the first element of arr passed to the block.
When the next element of arr is passed to the block, the block variable i is set to 1. tot, which we just computed, equals 6. The block calculation is therefore:
a = arr[0..-i-1].zip(arr[i..-1])
#=> arr[0..-2].zip(arr[1..-1])
#=> ["a", "b", "a", "b", "a"].zip(["b", "a", "b", "a", "a"])
#=> [["a", "b"], ["b", "a"], ["a", "b"], ["b", "a"], ["a", "a"]]
b = a.take_while { |x,y| x == y }
#=> []
tot + b.size
#=> 6 + 0
#=> 6
The remaining calculations are similar. After all elements of arr have been sent to the block, reduce returns the value of tot.

Resources