Checking that string contains only unique array elements, repeating letters - arrays

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

Related

Output is given in 2 lines after concatenation of elements with odd and even indexes in a given string

I wrote a function which takes all elements with odd indexes and concatenates them with all elements with even indexes in a given string n times:
def encrypt(text,n)
while n > 0
n -= 1
str_array = text.split("")
even_values = str_array.select.with_index { |_, i| i.even? }
odd_values = str_array.select.with_index { |_, i| i.odd? }
text = (odd_values+even_values).join
end
puts text
end
puts 'Type your message to encrypt:'
text = gets
puts 'Type number of times to run encryption:'
n = gets.to_i
encrypt(text,n)
The issue is that in case the input text has odd number of elements the function will return a text in 2 lines. At the same time if number of elements of input text is even the output is a 1 line text which is what I want.
Console output with 10 elements in input text:
Type your message to encrypt:
abcdefghij
Type number of times to run encryption:
1
bdfhjacegi
Console output with 11 elements in input text:
Type your message to encrypt:
abcdefghijk
Type number of times to run encryption:
1
bdfhj
acegik
For n = 1
def doit(str)
enum = [false, true].cycle
str.each_char.partition { enum.next }.map(&:join).join
end
doit "abcdefghij"
#=> "bdfhjacegi"
doit "abcdefghijk"
#=> "bdfhjacegik"
For n > 1
n.times.reduce(str) { |s,_| doit(s) }
For example:
4.times.reduce(str) { |s,_| doit(s) }
#=>"ejdichbgafk"
See Enumerable#reduce (a.k.a. inject)
Now consider the calculations performed by doit.
First consider the enumerator enum, created with the method Array#cycle:
enum = [false, true].cycle
#=> #<Enumerator: [false, true]:cycle>
We can use the method Enumerator#next to see what values this enumerator enum will generate:
enum.next
#=> false
enum.next
#=> true
enum.next
#=> false
... ad infinitum
Now let's reset the enumerator using Enumerator#rewind:
enum.rewind
#<Enumerator: [false, true]:cycle>
Suppose
str = "abcdefghijk"
Then
e = str.each_char
#=> #<Enumerator: "abcdefghijk":each_char>
We can convert this enumerator to an array to see what elements it will generate.
e.to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]
Continuing,
a = e.partition { enum.next }
#=> [["b", "d", "f", "h", "j"], ["a", "c", "e", "g", "i", "k"]]
b = a.map(&:join)
#=> ["bdfhj", "acegik"]
b.join
#=> "bdfhjacegik"
Note a.map(&:join) is shorthand for a.map { |arr| arr.join }.
See String#each_char, Enumerable#partition and Array#join.

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.

Iterate over an array n items at a time and continue the iteration

I'm trying to build a CLI. I want to print the name of each object stored in my array. This is how my array looks like:
my_arr = [#<MyObject::Obj:0x007f828daf33b0>, #<MyObject::Obj:0x007f358daf33b0>..]
Instead of showing a long list at once, I want the user to take action to display 200/1000 names at a time. This is my code:
my_arr.each_with_index do |my_obj, index|
puts "#{index} #{my_obj.name}"
end
I'm thinking to use case statement to build the user interaction part, but having issues finding ways to split my Array. How can I start iterating on my Array, break out from the iteration (ask for user input) and after that continue to iterate where I left off?
Ruby has an Enumerable#each_slice method that will give you an array in groups, which could allow you to do something similar to:
my_arr = my_arr.collect.with_index do |my_obj, index|
"#{index} #{my_obj.name}" # do this all the way up here to get the original index
end.each_slice(5)
length = my_arr.size - 1 # how many groups do we need to display
my_arr.each.with_index do |group, index|
puts group.join("\n") # show the group, which is already in the desired format
if index < length # if there are more groups to show,
# show a message and wait for input
puts "-- MORE --"
gets
end
end
You can use break and next. A short demo -
def foo_next(arr)
arr.each_with_index { |item, index|
next if index % 2 == 0
puts item
}
end
def foo_break(arr)
arr.each_with_index { |item, index|
puts item
break if index % 2 == 0
}
end
nums = (1..10).to_a
foo_next(nums) # prints 2 4 6 8 10
foo_break(nums) # prints 1
Use an enumerator to enable a stop/continue process:
arr = ('a'..'j').to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
enum = arr.to_enum
def taker n, enum
n.times.with_object [] { |_, o| o << enum.next }
end
Then take how ever many elements you want...
taker 2, enum
#=> ["a", "b"]
...and pick up from where you left off:
taker 3, enum
#=> ["c", "d", "e"]
taker 1, enum
#=> ["f"]
How you print the output and/or user-prompt is up to you.

How can I generate a percentage for a regex string match in Ruby?

I'm trying to build a simple method to look at about 100 entries in a database for a last name and pull out all the ones that match above a specific percentage of letters. My current approach is:
Pull all 100 entries from the database into an array
Iterate through them while performing the following action
Split the last name into an array of letters
Subtract that array from another array that contains the letters for the name I am trying to match which leaves only the letters that weren't matched.
Take the size of the result and divide by the original size of the array from step 3 to get a percentage.
If the percentage is above a predefined threshold, push that database object into a results array.
This works, but I feel like there must be some cool ruby/regex/active record method of doing this more efficiently. I have googled quite a bit but can't find anything.
To comment on the merit of the measure you suggested would require speculation, which is out-of-bounds at SO. I therefore will merely demonstrate how you might implement your proposed approach.
Code
First define a helper method:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
In short, if
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
then
a - b #=> [1]
whereas
a.difference(b) #=> [1, 3, 2, 2]
This method is elaborated in my answer to this SO question. I've found so many uses for it that I've proposed it be added to the Ruby Core.
The following method produces a hash whose keys are the elements of names (strings) and whose values are the fractions of the letters in the target string that are contained in each string in names.
def target_fractions(names, target)
target_arr = target.downcase.scan(/[a-z]/)
target_size = target_arr.size
names.each_with_object({}) do |s,h|
s_arr = s.downcase.scan(/[a-z]/)
target_remaining = target_arr.difference(s_arr)
h[s] = (target_size-target_remaining.size)/target_size.to_f
end
end
Example
target = "Jimmy S. Bond"
and the names you are comparing are given by
names = ["Jill Dandy", "Boomer Asad", "Josefine Simbad"]
then
target_fractions(names, target)
#=> {"Jill Dandy"=>0.5, "Boomer Asad"=>0.5, "Josefine Simbad"=>0.8}
Explanation
For the above values of names and target,
target_arr = target.downcase.scan(/[a-z]/)
#=> ["j", "i", "m", "m", "y", "s", "b", "o", "n", "d"]
target_size = target_arr.size
#=> 10
Now consider
s = "Jill Dandy"
h = {}
then
s_arr = s.downcase.scan(/[a-z]/)
#=> ["j", "i", "l", "l", "d", "a", "n", "d", "y"]
target_remaining = target_arr.difference(s_arr)
#=> ["m", "m", "s", "b", "o"]
h[s] = (target_size-target_remaining.size)/target_size.to_f
#=> (10-5)/10.0 => 0.5
h #=> {"Jill Dandy"=>0.5}
The calculations are similar for Boomer and Josefine.

Selecting the odd or even elements out of an array

I've been asked to write a piece of code that returns the odd elements of an array when a the second argument is true and even elements if it is false. So far I have written one half and I need a method that can select the odd elements.
def odds_and_evens(string, return_odds)
if return_odds != false
string.chars.to_a
end
end
puts odds_and_evens("abcdefgh", true)
If you add this code you have 2 handy methods to select odd or even values from an array
class Array
def odd_values
values_at(* each_index.select(&:odd?))
end
def even_values
values_at(* each_index.select(&:even?))
end
end
class Array
def odd_values
e = [false, true].cycle
select { e.next }
end
def even_values
e = [true, false].cycle
select { e.next }
end
end
arr = "Here is a simple illustration of these methods".split
arr.odd_values
#=> ["is", "simple", "of", "methods"]
arr.even_values
#=> ["Here", "a", "illustration", "these"]
def odds_and_evens(string, return_odds)
string.chars.select.with_index{|_, i| return_odds ? i.odd? : i.even?}
end
odds_and_evens("abcdefgh", true) # => ["b", "d", "f", "h"]
odds_and_evens("abcdefgh", false) # => ["a", "c", "e", "g"]
There are some excellent answers here on finding even and odd numbered characters, but if you ever needed the flexibility to find other types of numbers, you may end up rewriting with something more 'crude' such as this:
def odds_and_evens(string, return_odds)
new_string = string.chars.to_a
even_odd_string = []
new_string.each_with_index do |letter, index|
if return_odds != true && index %2 == 0
even_odd_string << letter
elsif return_odds != false && index %2 == 1
even_odd_string << letter
end
end
even_odd_string
end
puts odds_and_evens("abcdefgh", true)
Using Range#step:
def odds_and_evens(string, return_odds)
start = return_odds ? 1: 0
(start...string.size).step(2).map { |i| string[i] }
# OR string.chars.values_at(*(start...string.size).step(2))
end
odds_and_evens("abcdefgh", true) # => ["b", "d", "f", "h"]
odds_and_evens("abcdefgh", false) # => ["a", "c", "e", "g"]
Here is one more way this can be done:
def odds_and_evens(string, return_odds)
string.chars.each_slice(2).collect {|pair| return_odds ? pair.last : pair.first }
end
p odds_and_evens("abcdefgh", false)
#=> ["a", "c", "e", "g"]
p odds_and_evens("abcdefgh", true)
#=> ["b", "d", "f", "h"]
Another variant can be:
def odds_and_evens(string, return_odds)
indices = (0...string.size).select {|i| return_odds ? i.odd? : i.even? }
string.chars.values_at(*indices)
end
The accepted answer is okay but it’s slow.
This is 2-3 times faster. Also, it does not monkey-patch the Array class hence it can be used on other objects, for example, ActiveRecod::Relation
def self.even_elements(list)
list.values_at(*0.step(list.size - 1, 2))
end
def self.odd_elements(list)
list.values_at(*1.step(list.size - 1, 2))
end

Resources