How to skip over whitespaces in .map. Ruby - ruby

This is my code:
def weirdcase (string)
string.chars.map.with_index { |letter, index|
unless index.odd?;
letter = letter.upcase
else
letter
end }.compact.join("")
end
This is what it's supposed to do:
"ThIs Is A TeSt"
And this is what I got:
"ThIs iS A TeSt"
It's giving me the wrong string in return because it's counting/including the white spaces in my
code. All I need to do is find a way to skip the white spaces then I'm good to go.
Thanks!

The problem
I assume that the objective is to capitalize, for each word, all letters at even indices (the first letter of the word having index zero).
Here are two ways to do that. Both methods use String#gsub with a regular expression. Depending on requirements it may be necessary to change str.gsub... to str.downcase.gsub... for both methods.
Use a regular expression to match one- or two-characters strings, two if possible, and capitalize those strings.
def weirdcase(str)
str.gsub(/(?<=\A| |[^ ]{2})[^ ]{1,2}/) { |s| s.capitalize }
end
weirdcase "this is a sentence for testing"
#=> "ThIs Is A SeNtEnCe FoR TeStInG"
The regular expression reads, "match one or two characters other than spaces, two if possible ([^ ]{1,2}), that are immediately preceded by one of the following: the beginning of the string (\A), a space or two characters other than spaces. (?<=\A| |[^ ]{2}) is a positive lookbehind.
s.capitalize invokes the method String#capitalize on the match.
Use a cycling enumerator
def weirdcase(str)
enum = [:upcase, :downcase].cycle
str.gsub(/./) do |s|
if s == ' '
enum.rewind
' '
else
s.public_send(enum.next)
end
end
end
weirdcase "this is a sentence for testing"
#=> "ThIs Is A SeNtEnCe FoR TeStInG"
The regular expression /./ matches each character in the string.
See Array#cycle, Enumerator#rewind, Enumerator#next and Object#public_send.
Note the following.
enum = [:upcase, :downcase].cycle
#=> #<Enumerator: [:upcase, :downcase]:cycle>
enum.next
#=> :upcase
enum.next
#=> :downcase
enum.next
#=> :upcase
enum.rewind
#=> #<Enumerator: [:upcase, :downcase]:cycle>
enum.next
#=> :upcase
enum.next
#=> :downcase
... ad infinitum

Related

I could not get the intended output from the written code

I have to write a method called consonant_cancel that takes in a sentence and returns a new sentence where every word begins with its first vowel. The intended output is for given test functions are:
puts consonant_cancel("down the rabbit hole") #=> "own e abbit ole"
puts consonant_cancel("writing code is challenging") #=> "iting ode is allenging"
But I am getting "own e abbit it ole e" and "iting ing ode e is allenging enging ing" with this code.
def consonant_cancel(sentence)
arr = []
vowels = 'aeiou'
words = sentence.split
words.each do |word|
word.each_char.with_index do |char, i|
if vowels.include?(char)
arr << word[i..-1]
end
end
end
return arr.join(' ')
end
puts consonant_cancel("down the rabbit hole") #=> "own e abbit ole"
puts consonant_cancel("writing code is challenging") #=> "iting ode is allenging"
Can you guys help me to debug it?
You can use String#gsub with a regular expression. There is no need to break the string into pieces for processing and subsequent recombining.
def consonant_cancel(str)
str.gsub(/(?<![a-z])[a-z&&[^aeiou]]+/i,'')
end
consonant_cancel("down the rabbit hole")
#=> "own e abbit ole"
consonant_cancel("writing code is challenging")
#=> "iting ode is allenging"
See the section "Character Classes" in the doc for Regexp for an explanation of the && operator.
We can write the regular expression in free-spacing mode1 to make it self-documenting.
/
(?<! # Begin a negative lookbehind
[a-z] # Match a lowercase letter
) # End negative lookbehind
[a-z&&[^aeiou]]+ # Match one or more lowercase letters other than vowels
/ix # Invoke case-indifference and free-spacing modes
The negative lookahead ensures that no string of letters immediately preceded by a letter is matched. The line
[a-z&&[^aeiou]]+
can alternatively be written
[b-df-hj-np-tv-z]+
1. See the section "Free-Spacing Mode and Comments" in the doc for Regexp.
If we adding in a puts to see what's happening in your loop:
def consonant_cancel(sentence)
arr = []
vowels = 'aeiou'
words = sentence.split
words.each do |word|
word.each_char.with_index do |char, i|
if vowels.include?(char)
puts char
arr << word[i..-1]
end
end
end
return arr.join(' ')
end
Then running consonant_cancel "hello world" we see:
irb(main):044:0> consonant_cancel "hello world"
e
o
o
=> "ello o orld"
irb(main):045:0>
You'll see the same issue with any word with multiple consonants, because of the way you're looping over the characters in a word and checking for consonants.
An easier way to accomplish this would be with regular expressions.
words.split.map { |w| w.sub(/^[^aeiou]*/i, "") }.join(' ')
word.each_char.with_index
This loop iterates all chars (and vowels) of the word. break it after the first vowel found, so it does not repeat the side-effects for subsequent vowels of this word.
As an alternative, here's another regex-based solution
def consonant_cancel(sentence)
sentence.scan(/\b[^aeiou]*(.+?)\b/i).join(" ")
end

Trying to remove punctuation without using regex

I am trying to remove punctuation from an array of words without using regular expression. In below eg,
str = ["He,llo!"]
I want:
result # => ["Hello"]
I tried:
alpha_num="abcdefghijklmnopqrstuvwxyz0123456789"
result= str.map do |punc|
punc.chars {|ch|alpha_num.include?(ch)}
end
p result
But it returns ["He,llo!"] without any change. Can't figure out where the problem is.
include? block returns true/false, try use select function to filter illegal characters.
result = str.map {|txt| txt.chars.select {|c| alpha_num.include?(c.downcase)}}
.map {|chars| chars.join('')}
p result
str=["He,llo!"]
alpha_num="abcdefghijklmnopqrstuvwxyz0123456789"
Program
v=[]<<str.map do |x|
x.chars.map do |c|
alpha_num.chars.map.include?(c.downcase) ? c : nil
end
end.flatten.compact.join
p v
Output
["Hello"]
exclusions = ((32..126).map(&:chr) - [*'a'..'z', *'A'..'Z', *'0'..'9']).join
#=> " !\"\#$%&'()*+,-./:;<=>?#[\\]^_`{|}~"
arr = ['He,llo!', 'What Ho!']
arr.map { |word| word.delete(exclusions) }
#=> ["Hello", "WhatHo"]
If you could use a regular expression and truly only wanted to remove punctuation, you could write the following.
arr.map { |word| word.gsub(/[[:punct:]]/, '') }
#=> ["Hello", "WhatHo"]
See String#delete. Note that arr is not modified.

Ruby method that uppercases even indexed letters and lowercases odd

Directions:
Write a method that accepts a string, and returns the same string with all even indexed characters in each word upper cased, and all odd indexed characters in each word lower cased. The indexing just explained is zero based, so the zero-ith index is even, therefore that character should be upper cased.
The passed in string will only consist of alphabetical characters and spaces(' '). Spaces will only be present if there are multiple words. Words will be separated by a single space(' ').
My code:
(someone please refactor or explain to me a cleaner/shorter solution)
def weirdcase(string)
arr = string.split(' ')
arr.map! {|word|
char = word.chars
char.each_with_index do |letter, i|
i % 2 == 0 ? letter.upcase! : letter.downcase!
end
}
arr.map! {|a| a.push(' ').join('')}
x = arr.join('').to_s
x[0...-1]
end
This is one way you could do that, using Array#cycle to create an enumerator and String#gsub to replace every character in the string with its value upcased or downcased.
def weirdcase(str)
enum = [:upcase, :downcase].cycle
str.gsub(/./) do |s|
if s == ' '
enum.rewind
s
else
s.public_send(enum.next)
end
end
end
weirdcase "Mary had a little lamb"
#=> "MaRy hAd a lItTlE LaMb"
By making gsub's argument /./ each character in the string is replaced by the value returned by the block, which, if that character is not a space, is that character either upcased or downcased, depending on the symbol generated by the enumerator enum, which alternates between :upcase and :downcase for each word.
Note that
enum = [:upcase, :downcase].cycle
#=> #<Enumerator: [:upcase, :downcase]:cycle>
enum.next
#=> :upcase
enum.next
#=> :downcase
enum.next
#=> :upcase
and so on. See also Enumerator#next.
Enumerator#rewind is needed to begin anew the alternating of case with each word.
One could replace s.public_send(enum.next) with
enum.next == :upcase ? s.upcase : s.downcase
You could also use gsub to change two adjacent characters at a time:
def weirdcase(string)
string.gsub(/(.)(.?)/) { "#{$1.upcase}#{$2.downcase}" }
end
weirdcase "Mary had a little lamb"
#=> "MaRy hAd a lItTlE LaMb"
The ? makes the second character optional, which is needed for odd-length strings:
weirdcase "foo"
#=> "FoO"
Or a using each_char and with_index:
def weirdcase(string)
string.each_char.map.with_index { |char, index|
if index.odd?
char.downcase
else
char.upcase
end
}.join
end
If you want to change each word separately:
"Mary had a little lamb".split(' ').map { |word| weirdcase(word) }.join(' ')
#=> "MaRy HaD A LiTtLe LaMb"
or again with gsub:
"Mary had a little lamb".gsub(/\S+/) { |word| weirdcase(word) }
#=> "MaRy HaD A LiTtLe LaMb"

Remove a string pattern and symbols from string

I need to clean up a string from the phrase "not" and hashtags(#). (I also have to get rid of spaces and capslock and return them in arrays, but I got the latter three taken care of.)
Expectation:
"not12345" #=> ["12345"]
" notabc " #=> ["abc"]
"notone, nottwo" #=> ["one", "two"]
"notCAPSLOCK" #=> ["capslock"]
"##doublehash" #=> ["doublehash"]
"h#a#s#h" #=> ["hash"]
"#notswaggerest" #=> ["swaggerest"]
This is the code I have
def some_method(string)
string.split(", ").map{|n| n.sub(/(not)/,"").downcase.strip}
end
All of the above test does what I need to do except for the hash ones. I don't know how to get rid of the hashes; I have tried modifying the regex part: n.sub(/(#not)/), n.sub(/#(not)/), n.sub(/[#]*(not)/) to no avail. How can I make Regex to remove #?
arr = ["not12345", " notabc", "notone, nottwo", "notCAPSLOCK",
"##doublehash:", "h#a#s#h", "#notswaggerest"].
arr.flat_map { |str| str.downcase.split(',').map { |s| s.gsub(/#|not|\s+/,"") } }
#=> ["12345", "abc", "one", "two", "capslock", "doublehash:", "hash", "swaggerest"]
When the block variable str is set to "notone, nottwo",
s = str.downcase
#=> "notone, nottwo"
a = s.split(',')
#=> ["notone", " nottwo"]
b = a.map { |s| s.gsub(/#|not|\s+/,"") }
#=> ["one", "two"]
Because I used Enumerable#flat_map, "one" and "two" are added to the array being returned. When str #=> "notCAPSLOCK",
s = str.downcase
#=> "notcapslock"
a = s.split(',')
#=> ["notcapslock"]
b = a.map { |s| s.gsub(/#|not|\s+/,"") }
#=> ["capslock"]
Here is one more solution that uses a different technique of capturing what you want rather than dropping what you don't want: (for the most part)
a = ["not12345", " notabc", "notone, nottwo",
"notCAPSLOCK", "##doublehash:","h#a#s#h", "#notswaggerest"]
a.map do |s|
s.downcase.delete("#").scan(/(?<=not)\w+|^[^not]\w+/)
end
#=> [["12345"], ["abc"], ["one", "two"], ["capslock"], ["doublehash"], ["hash"], ["swaggerest"]]
Had to delete the # because of h#a#s#h otherwise delete could have been avoided with a regex like /(?<=not|^#[^not])\w+/
You can use this regex to solve your problem. I tested and it works for all of your test cases.
/^\s*#*(not)*/
^ means match start of string
\s* matches any space at the start
#* matches 0 or more #
(not)* matches the phrase "not" zero or more times.
Note: this regex won't work for cases where "not" comes before "#", such as not#hash would return #hash
Fun problem because it can use the most common string functions in Ruby:
result = values.map do |string|
string.strip # Remove spaces in front and back.
.tr('#','') # Transform single characters. In this case remove #
.gsub('not','') # Substitute patterns
.split(', ') # Split into arrays.
end
p result #=>[["12345"], ["abc"], ["one", "two"], ["CAPSLOCK"], ["doublehash"], ["hash"], ["swaggerest"]]
I prefer this way rather than a regexp as it is easy to understand the logic of each line.
Ruby regular expressions allow comments, so to match the octothorpe (#) you can escape it:
"#foo".sub(/\#/, "") #=> "foo"

Converting string to proper title case

I have this exercise:
Write a Title class which is initialized with a string.
It has one method -- fix -- which should return a title-cased version of the string:
Title.new("a title of a book").fix =
A Title of a Book
You'll need to use conditional logic - if and else statements - to make this work.
Make sure you read the test specification carefully so you understand the conditional logic to be implemented.
Some methods you'll want to use:
String#downcase
String#capitalize
Array#include?
Also, here is the Rspec, I should have included that:
describe "Title" do
describe "fix" do
it "capitalizes the first letter of each word" do
expect( Title.new("the great gatsby").fix ).to eq("The Great Gatsby")
end
it "works for words with mixed cases" do
expect( Title.new("liTTle reD Riding hOOD").fix ).to eq("Little Red Riding Hood")
end
it "downcases articles" do
expect( Title.new("The lord of the rings").fix ).to eq("The Lord of the Rings")
expect( Title.new("The sword And The stone").fix ).to eq("The Sword and the Stone")
expect( Title.new("the portrait of a lady").fix ).to eq("The Portrait of a Lady")
end
it "works for strings with all uppercase characters" do
expect( Title.new("THE SWORD AND THE STONE").fix ).to eq("The Sword and the Stone")
end
end
end
Thank you #simone, I incorporated your suggestions:
class Title
attr_accessor :string
def initialize(string)
#string = string
end
IGNORE = %w(the of a and)
def fix
s = string.split(' ')
s.map do |word|
words = word.downcase
if IGNORE.include?(word)
words
else
words.capitalize
end
end
s.join(' ')
end
end
Although I'm still running into errors when running the code:
expected: "The Great Gatsby"
got: "the great gatsby"
(compared using ==)
exercise_spec.rb:6:in `block (3 levels) in <top (required)>'
From my beginner's perspective, I cannot see what I'm doing wrong?
Final edit: I just wanted to say thanks for all the effort every one put in in assisting me earlier. I'll show the final working code I was able to produce:
class Title
attr_accessor :string
def initialize(string)
#string = string
end
def fix
word_list = %w{a of and the}
a = string.downcase.split(' ')
b = []
a.each_with_index do |word, index|
if index == 0 || !word_list.include?(word)
b << word.capitalize
else
b << word
end
end
b.join(' ')
end
end
Here's a possible solution.
class Title
attr_accessor :string
IGNORES = %w( the of a and )
def initialize(string)
#string = string
end
def fix
tokens = string.split(' ')
tokens.map do |token|
token = token.downcase
if IGNORES.include?(token)
token
else
token.capitalize
end
end.join(" ")
end
end
Title.new("a title of a book").fix
Your starting point was good. Here's a few improvements:
The comparison is always lower-case. This will simplify the if-condition
The list of ignored items is into an array. This will simplify the if-condition because you don't need an if for each ignored string (they could be hundreds)
I use a map to replace the tokens. It's a common Ruby pattern to use blocks with enumerations to loop over items
There are two ways you can approach this problem:
break the string into words, possibly modify each word and join the words back together; or
use a regular expression.
I will say something about the latter, but I believe your exercise concerns the former--which is the approach you've taken--so I will concentrate on that.
Split string into words
You use String#split(' ') to split the string into words:
str = "a title of a\t book"
a = str.split(' ')
#=> ["a", "title", "of", "a", "book"]
That's fine, even when there's extra whitespace, but one normally writes that:
str.split
#=> ["a", "title", "of", "a", "book"]
Both ways are the same as
str.split(/\s+/)
#=> ["a", "title", "of", "a", "book"]
Notice that I've used the variable a to signify that an array is return. Some may feel that is not sufficiently descriptive, but I believe it's better than s, which is a little confusing. :-)
Create enumerators
Next you send the method Enumerable#each_with_index to create an enumerator:
enum0 = a.each_with_index
# => #<Enumerator: ["a", "title", "of", "a", "book"]:each_with_index>
To see the contents of the enumerator, convert enum0 to an array:
enum0.to_a
#=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]]
You've used each_with_index because the first word--the one with index 0-- is to be treated differently than the others. That's fine.
So far, so good, but at this point you need to use Enumerable#map to convert each element of enum0 to an appropriate value. For example, the first value, ["a", 0] is to be converted to "A", the next is to be converted to "Title" and the third to "of".
Therefore, you need to send the method Enumerable#map to enum0:
enum1 = enum.map
#=> #<Enumerator: #<Enumerator: ["a", "title", "of", "a",
"book"]:each_with_index>:map>
enum1.to_a
#=> [["a", 0], ["title", 1], ["of", 2], ["a", 3], ["book", 4]]
As you see, this creates a new enumerator, which could think of as a "compound" enumerator.
The elements of enum1 will be passed into the block by Array#each.
Invoke the enumerator and join
You want to a capitalize the first word and all other words other than those that begin with an article. We therefore must define some articles:
articles = %w{a of it} # and more
#=> ["a", "of", "it"]
b = enum1.each do |w,i|
case i
when 0 then w.capitalize
else articles.include?(w) ? w.downcase : w.capitalize
end
end
#=> ["A", "Title", "of", "a", "Book"]
and lastly we join the array with one space between each word:
b.join(' ')
=> "A Title of a Book"
Review details of calculation
Let's go back to the calculation of b. The first element of enum1 is passed into the block and assigned to the block variables:
w, i = ["a", 0] #=> ["a", 0]
w #=> "a"
i #=> 0
so we execute:
case 0
when 0 then "a".capitalize
else articles.include?("a") ? "a".downcase : "a".capitalize
end
which returns "a".capitalize => "A". Similarly, when the next element of enum1 is passed to the block:
w, i = ["title", 1] #=> ["title", 1]
w #=> "title"
i #=> 1
case 1
when 0 then "title".capitalize
else articles.include?("title") ? "title".downcase : "title".capitalize
end
which returns "Title" since articles.include?("title") => false. Next:
w, i = ["of", 2] #=> ["of", 2]
w #=> "of"
i #=> 2
case 2
when 0 then "of".capitalize
else articles.include?("of") ? "of".downcase : "of".capitalize
end
which returns "of" since articles.include?("of") => true.
Chaining operations
Putting this together, we have:
str.split.each_with_index.map do |w,i|
case i
when 0 then w.capitalize
else articles.include?(w) ? w.downcase : w.capitalize
end
end
#=> ["A", "Title", "of", "a", "Book"]
Alternative calculation
Another way to do this, without using each_with_index, is like this:
first_word, *remaining_words = str.split
first_word
#=> "a"
remaining_words
#=> ["title", "of", "a", "book"]
"#{first_word.capitalize} #{ remaining_words.map { |w|
articles.include?(w) ? w.downcase : w.capitalize }.join(' ') }"
#=> "A Title of a Book"
Using a regular expression
str = "a title of a book"
str.gsub(/(^\w+)|(\w+)/) do
$1 ? $1.capitalize :
articles.include?($2) ? $2 : $2.capitalize
end
#=> "A Title of a Book"
The regular expression "captures" [(...)] a word at the beginning of the string [(^\w+)] or [|] a word that is not necessarily at the beginning of string [(\w+)]. The contents of the two capture groups are assigned to the global variables $1 and $2, respectively.
Therefore, stepping through the words of the string, the first word, "a", is captured by capture group #1, so (\w+) is not evaluated. Each subsequent word is not captured by capture group #1 (so $1 => nil), but is captured by capture group #2. Hence, if $1 is not nil, we capitalize the (first) word (of the sentence); else we capitalize $2 if the word is not an article and leave it unchanged if it is an article.
def fix
string.downcase.split(/(\s)/).map.with_index{ |x,i|
( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? ) ? x.capitalize : x
}.join
end
Meets all conditions:
a, is, of, the, and all lowercase
capitalizes all other words
all first words are capitalized
Explanation
string.downcase calls one operation to make the string you're working with all lower case
.split(/(\s)/) takes the lower case string and splits it on white-space (space, tab, newline, etc) into an array, making each word an element of the array; surrounding the \s (the delimiter) in the parentheses also retains it in the array that's returned, so we don't lose that white-space character when rejoining
.map.with_index{ |x,i| iterates over that returned array, where x is the value and i is the index number; each iteration returns an element of a new array; when the loop is complete you will have a new array
( i==0 || x.match(/^(?:a|is|of|the|and)$/).nil? ) if it's the first element in the array (index of 0), or the word matches a,is,of,the, or and -- that is, the match is not nil -- then x.capitalize (capitalize the word), otherwise (it did match the ignore words) so just return the word/value, x
.join take our new array and combine all the words into one string again
Additional
Ordinarily, what is inside parentheses in regex is considered a capture group, meaning that if the pattern inside is matched, a special variable will retain the value after the regex operations have finished. In some cases, such as the \s we wanted to capture that value, because we reuse it, in other cases like our ignore words, we need to match, but do not need to capture them. To avoid capturing a match you can pace ?: at the beginning of the capture group to tell the regex engine not to retain the value. There are many benefits of this that fall outside the scope of this answer.
Here is another possible solution to the problem
class Title
attr_accessor :str
def initialize(str)
#str = str
end
def fix
s = str.downcase.split(" ") #convert all the strings to downcase and it will be stored in an array
words_cap = []
ignore = %w( of a and the ) # List of words to be ignored
s.each do |item|
if ignore.include?(item) # check whether word in an array is one of the words in ignore list.If it is yes, don't capitalize.
words_cap << item
else
words_cap << item.capitalize
end
end
sentence = words_cap.join(" ") # convert an array of strings to sentence
new_sentence =sentence.slice(0,1).capitalize + sentence.slice(1..-1) #Capitalize first word of the sentence. Incase it is not capitalized while checking the ignore list.
end
end

Resources