Array of arrays with vowels/consonants as elements - arrays

I just started with Ruby. I need to build a method that takes two letters as arguments and returns an array of two arrays containing the same two letters and the letters included between them. The first array should contain only vowels whereas the second array only consonants. E.g.:
def alphamek('a', 'd')
should return:
[['a'], ['b', 'c', 'd']]
I tried this:
def alphamek(letter1, letter2)
first_array = (letter1..letter2).scan[aeiou].to_a
second_array = (letter1..letter2).scan[^aeiou].to_a
multi_array = [[first_array], [second_array]]
end
but it doesn't seem to work. Any ideas?

Not really that hard if you work it from a regular expression perspective and leverage a tool like partition:
VOWEL = /[aeiou]/i
def alphamek(a, b)
(a..b).partition { |l| VOWEL.match(l) }
end

Another way of doing that is to use the methods Array#& and Array#-.
VOWELS = %w| a e i o u |
#=> [“a“, ”e”, ”i”, ”o”, ”u”]
def doit(first_letter, last_letter)
letters = (first_letter..last_letter).to_a
[VOWELS & letters, letters - VOWELS]
end
doit 'f', 't'
#=> [["i", "o"], ["f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t"]]
doit 'f', 'o'
#=> [["i", "o"], ["f", "g", "h", "j", "k", "l", "m", "n"]]
doit 'v', 'z'
#=> [[], ["v", "w", "x", "y", "z"]]

You are calling scan on the Range (letter1..letter2). That method does not exist.
What you can do is call select since Ranges are enumerable, see the documentation on an explanation for select: http://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-select
Here's a working alternative that closely resembles your approach (as you intended it to work):
def alphamek(letter1, letter2)
vowels = 'aeiou'
# select all letters that are vowels
first_array = (letter1..letter2).select { |letter| vowels.include?(letter) }
# reject all letters that are vowels
second_array = (letter1..letter2).reject { |letter| vowels.include?(letter) }
return first_array, second_array # => [[...], [...]]
end
reject is simply the opposite of select, I prefer to use it instead of inverting the condition.
Anyway, there is an even better approach to this partitioning:
def alphamek(letter1, letter2)
vowels = 'aeiou'
(letter1..letter2).partition { |letter| vowels.include?(letter) }
end
This does the same as the other approach. partition splits the enumerable into two arrays, the first one contains the values for which the block evaluates to true, the second those for which it evaluates to false.
See partition in the docs: http://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-partition

Related

How to Sort Everything in an Array Except Special Chars - Ruby

I am trying to sort an array of letters alphabetically, but keep the special character in the same spot, in ruby.
For example,
word = ["f", "a", "s", "t", "-", "c", "a", "r", "s"]
How can I sort the whole thing alphabetically, but keep the "-" where it is. If I sort how it is now then the "-" will go to the front which I don't want. I have tried fifteen different ways, and I can't figure it out. Can you help?
Some really verbose way to do this, to explain the logic around what you need to achieve. There's some 'cleaner' methods to achieve this, but I feel this gives more understanding.
Adding an additional special character to the array for better test coverage:
let(:input) { ["f", "a", "s", "t", "-", "c", "a", "r", "s", "/"] }
let(:desired_output) { ["a", "a", "c", "f", "-", "r", "s", "s", "t", "/"] }
it "takes the input and gives the desired output" do
expect(sort_alphanumeric_characters(input)).to eq(desired_output)
end
Call .map and .select on the array to enumerate over the values and then call .with_index as you'll need to retain indicies for later.
def sort_alphanumeric_characters(word_as_array)
# assuming you mean non-alphanumeric
# collect those indicies which are 'special' characters
# the regex matches the string with anything outside of the alphanumeric range. Note the '^'
special_character_indicies = word_as_array.map.with_index { |val, indx| indx if val =~ /[^a-zA-Z0-9]/ }.compact
# collect all characters by index that were not yielded as 'special'
alphanumeric_array = word_as_array.select.with_index { |char, indx| char unless special_character_indicies.include? indx }
# sort the alphanumeric array
sorted_alphanumeric_array = alphanumeric_array.sort
# use Array#insert to place the 'special' by index
special_character_indicies.each do |special_indx|
special_char = word_as_array[special_indx]
sorted_alphanumeric_array.insert(special_indx, special_char)
end
# return desired output
sorted_alphanumeric_array
end
As soon as I posted I had a lightning bolt (love it when that happens). This is really not that great of a solution, but it did work!!
def scramble_words(str)
idx = 0
chars = str.delete("^a-z").chars
first_ele = chars.shift
last_ele = chars.pop
str.chars.each_with_index {|c, i| idx = i if c =~ /[^a-z" "]/ }
(first_ele + chars.sort.join + last_ele).insert(idx, str[idx])
end
p scramble_words('card-carrying') == 'caac-dinrrryg'
p scramble_words("shan't") == "sahn't"
p scramble_words('-dcba') == '-dbca'

Return an array of vowels used within a string

I am struggling with a ruby challenge the task is
Write a method that will take a string and
return an array of vowels used in that string.
Example:
count_vowels("The quick brown fox") should return ["e","u","i","o","o"]
count_vowels("Hello World") should return ["e","o","o"]
so far i have tried experimenting with blocks and other array methods like
def vowels(string)
string_array = string.chars
vowels = ["a", "e", "i", "o", "u"]
p string_array & vowels
end
also
def vowels (string)
# Your code here
arr =(string).downcase.chars
new =[]
values = ["a","e","i","o","u"]
arr. { |words| values.include?(words.each) }
end
For academic purposes, here's another approach:
def vowels(str)
# Delete all the non-vowel characters from the string and return the remaining characters
str.downcase.tr('^aeiou', '').chars
end
vowels("The quick brown fox")
# => ["e", "u", "i", "o", "o"]
vowels("Hello World")
# => ["e", "o", "o"]
This, coincidentally, is based on an example in the String#tr documentation.
Below code piece should help you
Edited:-
def count_vowels(word)
word.downcase.chars.select{|c| %[a,e,i,o,u].include?(c)}
end
You can avoid splitting the string into an array of chars first by using String.scan with a regexp.
string = "The quick brown fox"
string.scan(/(a|e|i|o|u)/i).flatten
#=> ["e", "u", "i", "o", "o"]
string = "Hello World"
string.scan(/(a|e|i|o|u)/i).flatten
#=> ["e", "o", "o"]
The i at the end makes the regexp case-insensitive.
It probably depends on the size of the string if it is faster to build a regexp and scan the string in one go or to split the string into an array first and then iterate over all chars and compare them against another array.

best way to filter two strings into 1 in ruby

I am trying to figure out a way to filter two arrays into one based on guessing the letters within one o them.. so basically hangman. But if I had
word_array = ["b", "u", "s", "b", "o", "i"]
hidden_array = Array.new(word_array.length, "-")
p hidden_array
I would want to then print to the console ["b", "-", "-", "b", "-", "-"] if "b" were guessed. What would be a good beginner way to create this array that will change over time? Should it maybe be a hash? Thanks!
All of the solutions so far revolve around arrays, but don't forget a string is basically a character array anyway. Just use strings:
word = 'busboi'
guesses = 'bs'
word.tr('^'+guesses, '-')
# => "b-sb--"
The String#tr method converts all letters in the first argument to the mapping in the second argument, so you can do things like ROT13, simple cyphers and such, or in this case use the negation feature ^ to invert the first set and replace all non-matching characters.
You can keep track of the found letters in an array and make a method to do the printing
word_array = ["b", "u", "s", "b", "o", "i"]
found_letters = []
def hangman_prompt(found_letters)
word_array.map do |char|
found_letters.include?(char) ? char : "-"
end.join(" ")
end
Then you could use this in an input loop like so:
loop do
puts hangman_prompt(found_letters)
puts "what is your guess?"
input = gets.chomp
if word_array.include? input
found_letters << input
end
end
I'm using Array#map here which creates a new array of the same length. Each of the original array items are passed to the block, which determines how they could be copied to the new array.
One way to do it:
word_array = ["b", "u", "s", "b", "o", "i"]
word_array_i = word_array.map.with_index { |e,i| [e,i] }
#=> [["b", 0], ["u", 1], ["s", 2], ["b", 3], ["o", 4], ["i", 5]]
p hidden_array = Array.new(word_array.length, "_")
until hidden_array == word_array
puts 'make a guess'
guess = gets.chomp
if word_array.include? guess
puts 'correct guess'
ar = word_array_i.select { |arr| arr.first == guess }
.flatten.select { |e| e.class == Fixnum }
ar.each { |e| hidden_array[e] = guess }
else
puts 'incorrect guess'
end
p hidden_array
puts
end
puts 'game complete'
Key methods to research here Array#include?, Enumerator#with_index.
I wouldn't use arrays, just strings.
Code
def replace_underscores(word, hidden_word, guess)
word.scan(Regexp.new(guess)) { hidden_word[Regexp.last_match.begin(0)] = guess }
hidden_word
end
Example
word = "busboi"
hidden_word = "_" * word.length
#=> "______"
replace_underscores(word, hidden_word, "a") # guess "a"
#=> "______"
replace_underscores(word, hidden_word, "b") # guess "b"
#=> "b__b__"
replace_underscores(word, hidden_word, "r") # guess "r"
#=> "b__b__"
replace_underscores(word, hidden_word, "o") # guess "o"
#=> "b__bo_"
replace_underscores(word, hidden_word, "u") # guess "u"
#=> "bu_bo_"
replace_underscores(word, hidden_word, "s") # guess "s"
#=> "busbo_"
To check if the hidden_word has been guessed:
def guessed?(hidden_word)
hidden_word.count('_').zero?
end
guessed?(hidden_word)
#=> false
Let's permit one more guess.
replace_underscores(word, hidden_word, "i") # guess "i"
#=> "busboi"
guessed?(hidden_word)
#=> true
Notes
I've used the method String#scan with a block that is executed for each match. Within the block the MatchData object is retrieved with the class method Regexp::last_match. (Alternatively, one could substitute the global variable $~ for Regexp.last_match. For details, search for "special global variables" at Regexp.) The method MatchData.begin is used to obtain the index of the character in str which is to be replaced by the letter just guessed.

Checking which words in a list could be made from user input letters

I'm trying to let users put in a list of letters separated by commas, and find out what words could be made from those letters. The program uses a list of words to compare against the input letters.
This is the code that I have tried. I have tried it several different ways, and this was my least unsuccessful trial.
#import list, get user input and separate each letter
list_of_words = IO.foreach("referencewords.txt")
letter_choice = gets.chomp
letter_choice = letter_choice.split(/,*/)
#make new blank array for possible words
final_word_array = []
list_of_words.each do |word|
final_word_array.push(word) if letter_choice.include?(word)
end
#show possible words to the user
final_word_array.each do |word|
puts word
end
When I run this code, I get nothing in my final_word_array. My question is, why am I not getting a list of possible words in my 'final_word_array'?
Let's see what's happening.
Your letter_choice local variable ends up being an array of letters after you split it with the regex.
# assume input was "a, b, c, d"
letter_choice = "a, b, c, d"
letter_choice = letter_choice.split(/,*/) # => ['a', 'b', 'c', 'd']
Later, you are looking for a whole work in a list of letters. Assume you have a word "bad" in your word list. You would be doing something like this:
if letter_choice.include?(word)
# would be
['a', 'b', 'c', 'd'].include?("bad") # => false
So, the code is behaving correctly. I would suggest using Sets. Make list_of_words to hold an array of sets that are made out of letters in your words. Then just check if your letters are a subset of your the word.
word.subset?(letter_choice_set)
Update: Forgot to mention that this option would not account for repeating letters. Words like "badd" would also match, even if user inputs only one "d".
Maybe this could help:
list_of_words = IO.foreach("referencewords.txt")
letter_choice = gets.chomp.split(/,*/)
final_word_array = []
list_of_words.each do |word|
letters_of_word = word.split('').uniq
final_word_array.push(word) if (letters_of_word && letter_choice) == letters_of_word
end
#show possible words to the user
final_word_array.each do |word|
puts word
end
Sample list of words:
a = ["cat", "bat", "dog", "god", "rabbit"]
Conversion:
h = a.group_by{|s| s.each_char.sort}
# => {
# ["a", "c", "t"]=>["cat"],
# ["a", "b", "t"]=>["bat"],
# ["d", "g", "o"]=>["dog", "god"],
# ["a", "b", "b", "i", "r", "t"]=>["rabbit"]
#}
Sample user input:
s = "o,d,g\n"
Possible words:
h[s.chomp.split(",").sort] # => ["dog", "god"]
If some or all of the letters can be used for form words, you could do the following.
Code
words_by_letters = word_list.each_with_object(Hash.new { |h,k| h[k]=[] }) { |word, h|
h[word.each_char.sort] << word }
def extract_words(words_by_letters, letters)
(1..letters.size).flat_map do |n|
letters.combination(n).map(&:sort).uniq.flat_map do |word_set|
words_by_letters[word_set]
end.compact
end.reject(&:empty?)
end
Examples
word_list = ["a", "at", "cat", "bat", "do", "dog", "god", "act", "rot", "robot"]
words_by_letters = word_list.each_with_object(Hash.new { |h,k| h[k]=[] }) { |word, h|
h[word.each_char.sort] << word }
#=> {["a"]=>["a"],
# ["a", "t"]=>["at"],
# ["a", "c", "t"]=>["cat", "act"],
# ["a", "b", "t"]=>["bat"],
# ["d", "o"]=>["do"],
# ["d", "g", "o"]=>["dog", "god"],
# ["o", "r", "t"]=>["rot"],
# ["b", "o", "o", "r", "t"]=>["robot"]}
extract_words(words_by_letters, ['c', 'a', 't'])
#=> ["a", "at", "cat", "act"]
extract_words(words_by_letters, ['o', 'g', 'd'])
#=> ["do", "dog", "god"]
extract_words(words_by_letters, ['o', 'o', 't', 'r', 'b'])
#=> ["rot", "robot"]

How can I group items in a ruby array based on characteristics of each element?

I have a letter array created by splitting any given word. I have a constant array of all five vowels, and I use that to classify each letter in the letter array as either a consonant or a vowel.
VOWELS = ["a","e","i","o","u"]
letters = "compared".split("")
# => ["c", "o", "m", "p", "a", "r", "e", "d"]
word_structure = letters.map { |letter| VOWELS.include?(letter) ? "v" : "c" }
# => ["c", "v", "c", "c", "v", "c", "v", "c"]
I want to somehow achieve two things:
Group adjacent letters in the "letters" array that have the same "word_structure".
Take those groups and return each possible VCV combination as another array. V represents a grouping of all adjacent vowels, and C represents a grouping of all adjacent consonants.
.
groups = ["c", "o", "mp", "a", "r", "e", "d"]
vcv_groups = ["-co", "ompa", "are", "ed-"]
In this example, the first VCV group begins with a "-" because there is no first grouping of vowels. The next two groupings fit the pattern fully, and the last has another "-" because there was no final vowel to complete the pattern.
I've experimented with Enumerable#chunk, Enumerable#partition and Enumerable#slice_before, but they're all just confusing to me. If somebody understands a simple way to achieve this, I would really appreciate the help.
You can do that with a regex (followed by a messy bit to insert hyphens as required):
VOWELS = 'aeiou'
R = /
(?= # begin positive look-ahead
( # begin capture group 1
(?: # begin a non-capture group
[#{VOWELS}]+ # match one or more vowels
| # or
\A # match the beginning of the string
) # end non-capture group
[^#{VOWELS}]+ # match one or more consonants
(?: # begin a non-capture group
[#{VOWELS}]+ # match one or more vowels
| # or
\z # match end of string
) # end non-capture group
) # end capture group 1
) # end positive lookahead
/x # extended mode
def extract(str)
arr = str.scan(R).flatten
arr[0].insert(0, '-') unless VOWELS.include?(arr[0][0])
arr[-1] << '-' unless VOWELS.include?(arr[-1][-1])
arr
end
extract 'compare' #=> ["-co", "ompa", "are"]
extract 'compared' #=> ["-co", "ompa", "are", "ed-"]
extract 'avacados' #=> ["ava", "aca", "ado", "os-"]
extract 'zzz' #=> ["-zzz-"]
extract 'compaaared' #=> ["-co", "ompaaa", "aaare", "aare", "are", "ed-"]
"compare"
.split(/([aeiou]+)/).unshift("-").each_cons(3)
.each_slice(2).map{|(v1, c, v2), _| v2 ||= "-"; [v1, c, v2].join}

Resources