How do I conditionally delete strings from an array? - arrays

I need to remove any strings from an array that are not of a certain length. The assignment recommends using map or map!. I have been playing with map!, delete_if, and keep_if, but I am getting nowhere. Can anyone help me? Below is one of my attempts:
dictionary = Array.new
file = File.open("words.txt").each do | line |
dictionary.push line
end
(wordlength = gets)
wordlength = wordlength.to_i
dictionary = dictionary.reject {|word| word.length != wordlength }
puts dictionary

You need to remove whitespaces from your input:
dictionary.push line.strip
By the way, the code for reading your file can be simplified:
dictionary = File.readlines("words.txt").map { |line| line.strip }
(As for the original question, both delete_if and reject/reject! work)
EDIT
The complete code could be something like this:
#!/usr/bin/env ruby
dictionary = File.readlines("words.txt").map { |line| line.strip }
wordlength = gets.to_i
dictionary.delete_if { |word| word.length != wordlength }
puts dictionary #test
Keep in mind that reject! and delete_if change the original array, so if you want to keep the original values you should use
new_dictionary = dictionary.reject { |word| word.length != wordlength }
or even
new_dictionary = dictionary.select {|word| word.length == wordlength }

You should need to use Array#delete_if.
dictionary.delete_if{|s| s.size != wordlength }

I'd go with #reject here.
dictionary = ["apple", "bug", "cup", "drain"]
dictionary.reject {|word| word.length != 3}

This code should work:
#!/usr/bin/env ruby
dictionary = File.open("words.txt").map do |line|
line.strip # remove line end and whitespaces at the beginning and the end of the line
end
wordlength = gets
wordlength = wordlength.to_i
dictionary = dictionary.reject { |word| word.length != wordlength }
puts dictionary #test
Some comments as it is your first ruby program ever:
It's more common in ruby to add items to an array using << operator.
You can use map method to transform each item in an array into something else and return an array of the transformed items.

Instead of delete_if or keep_if I'd just group the words by length:
file = "/usr/share/dict/words"
words = File.readlines(file).map { |line| line.chomp }.group_by(&:length)
This makes it trivial to retrieve words with different lengths:
words[5] # all words with length 5
#=> ["aalii", "Aaron", "abaca", "aback", "abaff", "abaft", "Abama", "abase", "abash", "abask", ...]
words[10] # all words with length 10
#=> ["abalienate", "abaptiston", "abasedness", "abbeystede", "abbreviate", "abdication", "abdicative", "abdominous", "aberdevine", "Aberdonian", ...]

|word| here is different from i as you would use in C++. i would be referring to the index of the object in dictionary. |word| here refers directly to the object in the dictionary array. e.g.
dictionary = ["animal", "animate"]
|word| would refer to the object "animal", i would refer to index 0 of dictionary. To make it clearer, Ruby even has a enumerable method:
.each_with_index do |element, index|
end
where |element| refers to the object, and |index| refers to the index of the object.
I don't recommend deleting through the very array that you're iterating through, it comes out with quirky results since the size of your array changes each time you delete an element.
Arup's suggestion of using delete_if should work fine. If that doesn't work, you can try another method (albeit less efficient) by setting each |word| equal to nil if it's length != wordlength, then compacting it (removing objects that are equal to nil).
dictionary = []
file = File.open("words.txt").each do | line |
dictionary << line.strip #strip removes whitespaces and newlines
end
wordlength = gets.chomp.to_i #chomp removes any newlines "\n"
dictionary = dictionary.each do |word|
word = nil if word.length != wordlength
end
puts dictionary.compact!

Related

Ruby - Understanding string vs array comparison

I'm working on a practice problem in Ruby. I already have the answer (below) but I'm not sure I understand how exactly one part of the answer works, specifically, how does new_word evaluate to a string if |word| is in an array. I may just be overlooking something super basic here but would appreciate any help. Thanks!
Prompt:
Write a method abbreviate_sentence that takes in a sentence string and returns a new sentence where every word longer than 4 characters has all of its vowels removed.
Answer:
words = sent.split(" ")
new_words = []
words.each do |word|
if word.length > 4
new_word = abbreviate_word(word)
new_words << new_word
else
new_words << word
end
end
return new_words.join(" ")
end
def abbreviate_word(word)
vowels = "aeiou"
new_word = ""
word.each_char do |char|
if !vowels.include?(char)
new_word += char
end
end
return new_word
end
puts abbreviate_sentence("follow the yellow brick road") # => "fllw the yllw brck road"
puts abbreviate_sentence("what a wonderful life") # => "what a wndrfl life" ```
Courtesy of #ggorlen:
You're iterating over an array of strings, so each |word| isn't an array, it's a string. This code could be sent.split(" ").map {|e| e.size > 4 ? e.gsub(/[aeiou]/, "") : e}.join(" ")

Replacing the old index with modified index in the string then pushing it to an array

I have a beginner question. I'm trying to simply replace the modified index back in the original string to create an array of strings. They consecutively uppcase the next index in the following element.
Here's what I have. Can anyone help me see what I'm missing?
def wave(str)
result = []
index = 0
while index < str.length
i = str[index]
if i == " "
index =+ 1
else
upper = i.upcase
val = str.rindex(upper) -1
result.push("#{str[0...val]}#{str[val..-1]}")
index += 1
end
end
result
end
I'm trying to get:
["Hello", "hEllo", "heLlo", "helLo", "hellO"]
from ---> wave(hello)
Thank you.
Your else part isn't quite right:
you don't need to find the right-most index because you already have index
upper is never used
the right range is off by one
Here's a working variant:
# ...
else
upper = i.upcase
result.push("#{str[0...index]}#{upper}#{str[index+1..-1]}")
index += 1
end
# ...
You can use each_char and with_index to make your code a little more idiomatic:
def wave(str)
result = []
str.each_char.with_index do |char, index|
next if char == " "
result.push("#{str[0...index]}#{char.upcase}#{str[index+1..-1]}")
end
result
end
The problem is that you are declaring a variable upper but you are not using it.
Try to rewrite your code and this time use it.
If you are not sure what's happening in each line of code just put print in front of it.
Another option is to use Enumerable#map with Object#tap in this way:
str = 'hello'
str.size.times.map { |n| str.dup.tap { |s_dup| s_dup[n] = str[n].upcase } }
#=> ["Hello", "hEllo", "heLlo", "helLo", "hellO"]
Needs to duplicate the original string for avoiding change the original string itself which would return ["HELLO", "HELLO", "HELLO", "HELLO", "HELLO"]
For skipping spaces:
str.size.times.flat_map { |n| str.dup.tap { |s_dup| s_dup[n] = str[n].upcase } unless str[n] == ' ' }

Taking a string, reversing the letter in each word while leaving the word in its original position

I am trying to take a sentence, and reverse the positions of the letters in each word.
Below is my code that does not work:
def test(sentence)
array = []
array << sentence.split
array.collect {|word| word.reverse}
end
My problem is with:
array << sentence.split
It says it divides each word, but when I use interpolation, it reverses the whole sentence. Below is a similar code that works:
def test2
dog = ["Scout", "kipper"]
dog.collect {|name| name.reverse}
end
But it does not accept a sentence, and it already has the array defined.
I'm thinking you want to split, then map each element of the array to its reversed version then rejoin the array into a string:
def test(sentence)
sentence.split.map {|word| word.reverse}.join(" ")
end
More concise using symbol-to-proc (credit #MarkThomas in comments)
sentence.split.map(&:reverse).join " "
Unlike methods that break up the sentence into words, reverses each word and then rejoins them, the use of Array#gsub with a regular expression preserves non-word characters (such as the comma in the example below) and multiple spaces.
"vieille, mère Hubbard".gsub(/\b\p{L}+\b/, &:reverse)
#=> "ellieiv, erèm drabbuH"
def reverse(str)
str.split.map { |s| s.length < 5 ? s : s.reverse }.join(' ')
end
puts reverse ("this is a catalogy")
This should reverse each word thats length upto 5

Sort an array of words by the last letter

I am trying to sort an array of words by their last character.
def array_sort_by_last_letter_of_word(array)
list = array[-1,1]
list.sort { |a,b| b <=> a }
end
puts array_sort_by_last_letter_of_word ['sky', 'puma', 'maker']
The output I get is just "maker".
def array_sort_by_last_letter_of_word(array)
array.sort_by { |word| word[-1] }
end
Rails version (using String#last):
def array_sort_by_last_letter_of_word(array)
array.sort_by(&:last)
end
Andrey's answer is definitely the way to do this. But to understand what's going wrong with your code:
list = array[-1,1]
This syntax is for getting a subarray of an array. The pattern is array[start, length] to get an array starting at index start with length length. So you are asking for an array starting at -1 (which means the last index) of length 1 i.e. just the last element of the array. So last = ['maker']. That's why your sort method is just returning that element.
You want to get the last character inside the sort block, which is what determines how strings are compared
array.sort { |a,b| a[-1] <=> b[-1] }
Also note that a needs to be left of b, otherwise the array is sorted in reverse.

Finding length of all strings in an array in Ruby

dictionary = File.foreach('dictionary.txt').map { |line| line.split('\n') }
dictionary.each{|word|
puts word.length
if word.length == 5
puts word
end
}
It says the value for each |word| is only 1. Anyone have a clue why? Thanks.
You’re mapping a function that splits a line into lines to every line. Each line will contain one line, resulting in a one-element array. Maybe you meant line.chomp?
dictionary = File.foreach('dictionary.txt').map &:chomp

Resources