Ruby method that uppercases even indexed letters and lowercases odd - ruby

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"

Related

Why does ruby's IO readlines method behave differently when followed by a filter

I'm building a little Wordle inspired project for fun and am gathering the words from my local dictionary. Originally I was doing this:
word_list = File.readlines("/usr/share/dict/words", chomp: true)
word_list.filter { |word| word.length == 5 }.map(&:upcase)
The first line takes absolutely ages. However when doing this:
word_list = File.readlines("/usr/share/dict/words", chomp: true).filter { |word| word.length == 5 }.map(&:upcase)
it completes in a matter of seconds. I can't work out how the filter block is being applied to the lines being read before they're assigned memory (which I'm assuming is what is causing the slow read time), clearly each method isn't being fully applied before the next is called but that is how I thought method chaining works.
Let's create a file.
File.write('t', "dog horse\npig porcupine\nowl zebra\n") #=> 34
then
a = File.readlines("t", chomp:true)
#=> ["dog horse", "pig porcupine", "owl zebra"]
so your block variable word holds a string of two words. That's obviously not what you want.
You could use IO::read to "gulp" the file into a string.
s = File.read("t")
#=> "dog horse\npig porcupine\nowl zebra\n"
then
a = s.scan(/\w+/)
#=> ["dog", "horse", "pig", "porcupine", "owl", "zebra"].
b = a.select { |word| word.size == 5 }
#=> ["horse", "zebra"]
c = b.map(&:upcase)
#=> ["HORSE", "ZEBRA"]
We could of course chain these operations:
File.read("t").scan(/\w+/).select { |word| word.size == 5 }.map(&:upcase)
#=> ["HORSE", "ZEBRA"]
scan(/\w+/) matches each string of word characters (letters, digits and underscores). To match only letters change that to scan(/[a-zA-Z]+/).
You could use IO#readlines, which reads lines into an array, by extracting words for each line, filtering the resulting array to keep ones having 5 characters, and then adding those words, after upcasing, to a previously-defined empty array.
File.readlines('t')
.each_with_object([]) { |line,arr| line.scan(/\w+/) }
.select { |word| word.size == 5 }
.map(&:upcase)
.each { |word| arr << word } #=> ["HORSE", "ZEBRA"]
You could add the optional parameter chomp: true to readline's arguments, but there is no reason to do so.
Better would be to use IO#foreach which, without a block, returns an enumerator which can be chained, avoiding for the temporary array created by readlines.
File.foreach('t').with_object([]) do |line,arr|
line.scan(/\w+/)
.select { |word| word.size == 5 }
.map(&:upcase)
.each { |word| arr << word }
end
#=> ["HORSE", "ZEBRA"]

How to skip over whitespaces in .map. 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

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

How to replace text in a ruby string

I am trying to write a very simple method in Ruby which takes a string and an array of words and checks if the string contains any of the words and if it does it replaces them with their uppercase.
I made an attempt but its not great due to my level of Ruby skills.
def(my_words,my_sentence)
#split the sentence up into an array of words
my_sentence_words = my_sentence.split(/\W+/)
#nested loop that checks the words array for each brand
my_sentence_words.each do |i|
my_words.each do |i|
#if it finds a brand in the words and sets them to be uppercase
if my_words[i] == my_sentence_words[i]
my_sentence_words[i] == my_sentence_words[i].up.case
end
end
end
#put the words array into one string
words.each do |i|
new_sentence = ("" + my_sentence_words[i]) + " "
end
end
I am getting: can't convert string into integer error
def convert(mywords,sentence)
regex = /#{mywords.join("|")}/i
sentence.gsub(regex) { |m| m.upcase }
end
convert(%W{ john james jane }, "I like jane but prefer john")
#=> "I like JANE but prefer JOHN"
This will work better. It loops through the brands, searches for each, and replaces with the uppercase version.
brands = %w(sony toshiba)
sentence = "This is a sony. This is a toshiba."
brands.each do |brand|
sentence.gsub!(/#{brand}/i, brand.upcase)
end
Results in the string.
"This is a SONY. This is a TOSHIBA."
For those who like Ruby foo!
sentence.gsub!(/#{brands.join('|')}/i) { |b| b.upcase }
And in a function
def capitalize_brands(brands, sentence)
sentence.gsub(/#{brands.join('|')}/i) { |b| b.upcase }
end
You get this error because i doesn't start from 0 as you expected, in each method i is an element of array, and has string type, it's a first word from your sentence:
my_sentence_words = ["word"]
my_sentence_words.each do |i|
puts i.length #=> 4
puts i.type #=> String
puts i #=> word
end
So you try to call my_sentence_words[word] instead of my_sentence_words[0]. You can try method each_index that passes index of element instead of element itself`:
def check(str, *arr)
upstr = str.split(' ')
upstr.eachindex do |i| #=> i is index
arr.each_index do |j|
upstr[i].upcase! if upstr[i] == arr[j]
end
end
upstr
end
check("This is my sentence", "day", "is", "goal", "may", "my")
#=>["This", "IS", "MY", "sentence"]

Resources