Delete_if over an array with strings and numbers, Refactored - ruby

I'm confused as to why the following will not work.
def array_mod(source_array, letter_to_delete)
source_array.delete_if {|x|
String===x && x.include?letter_to_delete }
end
Essentially I have a function which accepts an array, which, for testing purposes will have both strings and numbers. The second parameter is a letter. The function should iterate over the array, skip the Fixnum values, and delete_if the elements which are a) strings and b) contain the offending letter. I've created this function already but am trying to refactor it to make it more professional. Thank you guys for helping me get better!
def array_mod(source_array, letter_to_delete)
return source_array.delete_if do |x|
if x.is_a?(Fixnum) ==true
next
else
x.include?letter_to_delete
end
end
end

You are missing parenthesis:
def array_mod(source_array, letter_to_delete)
source_array.delete_if {|x| x.is_a?(String) && x.include?(letter_to_delete) }
end

Related

Capitalizing Vowels in a String with Ruby

I have a method that takes in a string as an argument, replaces each letter with the next letter in the alphabet and then capitalizes every vowel. I have gotten both of those to work individually (the replacing and capitalization), but at this point, I just don't know how to make them work together.
def LetterChanges(str)
new_str = str.downcase.split("")
new_str.each do |x|
x.next!
end
new_str.to_s.tr!('aeiou','AEIOU')
return new_str.join("")
end
LetterChanges("abcdef")
new_str.to_s is not stored anywhere. It doesn't affect the original array.
return new_str.join("").tr('aeiou', 'AEIOU')
This will convert the array back to a string and you can operate on that and return it.
That could be resolved with gsub.
"abcdef".gsub(/./){|char| char.next}.gsub(/[aeiou]/){|vowel| vowel.upcase}
#=> "bcdEfg"
so that method could be
def letter_changes_gsub(str)
str.gsub(/./){|char| char.next}.gsub(/[aeiou]/){|vowel| vowel.upcase}
end
That is faster and more simple that work with arrays.
Other answers already showed you how to combine both parts of your code. But there's another issue: String#next is continuing witch "aa" after "z":
"z".next #=> "aa"
You could add an if statement to handle this case:
str.chars.map do |char|
if char == 'z'
'a'
else
char.next
end
end.join
or:
str.chars.map { |char| char == 'z' ? 'a' : char.next }.join
But there's a much simpler way: let String#tr perform the entire substitution:
str.downcase.tr('a-z', 'bcdEfghIjklmnOpqrstUvwxyzA')
Or slightly shorter:
str.downcase.tr('a-z', 'bcdEfghIjk-nOp-tUv-zA')
2.1.0 :012 > 'abcdef'.split('').map(&:next).join.tr('aeiou', 'AEIOU')
=> "bcdEfg"
I would not recommend doing this in one line, of course. But to get at your confusion of how these methods might string together, here is one solution that works. When in doubt, use IRB to call each method and watch how Ruby responds. That will help you figure out where your code is breaking down.
In practice, I would break this into multiple methods. It's too many things for one method to do. And also a lot harder to find bugs (and test), as you found out.
def rotate(string)
string.split('').map(&:next).join
end
def capitalize_vowels(string)
string.tr('aeiou', 'AEIOU')
end
How about:
def string_thing(string)
string.downcase.tr('abcdefghijklmnopqrstuvwxyz','bcdEfghIjklmnOpqrstUvwxyzA')
end
#tr just will replace each character in the first parameter with the corresponding one in the second parameter.
This can be achieved with the combination of gsub and tr:
"abcdef".gsub(/[A-z]/) { |char| char.next }.tr('aeiou', 'AEIOU')
#=> "bcdEfg"
"Fun times!".gsub(/[A-z]/) { |char| char.next }.tr('aeiou', 'AEIOU')
#=> "GvO Ujnft!"

Can i use a ternary operator instead of while loop

I'm trying to reduce the while loop below to a single line
def this_method(week)
i = 0
while i < array.length
yield(week[i])
i += 1
end
end
week.each do |week|
puts week
end
Like others, I'm confused about the example (array is not defined, and this_method is never called). But you certainly don't need the while loop. I'd just use the Integer#times method, since you're making no use of the array values:
array.length.times {|i| yield week[i]}
#each_index (which ram suggested) works just as well.
But if array is actually meant to be week, then it gets even simpler:
week.each {|x| yield x}
I'm not sure why you'd want to create a method that just recycles #each though.
For since line you can use Array#each_index:
array.each_index {|i| yield week[i] }
No, you can't. The ternary operator is a conditional expression, the while is a loop expression.
However, in Ruby you normally use enumerators, not while. Your code can be rewritten as
def this_method(week)
array.each_with_index { |item, i| yield(week[i]) }
end
What is not clear to me, is there the array variable comes from. Even in your example, there is no definition of such variable.
if in any form check conditions only once.
while on other hand, can check conditions many times.
Well, if you don't like other answers with enumerators you can use while in a different form:
def this_method(week)
i = -1
yield(week[i]) while (i+=1) < array.length
end

Find value in an array of hashes

taglist = [{:name=>"Daniel_Xu_Forever", :tag=>["helo", "world"]},
{:name=>"kcuf", :tag=>["hhe"]},
{:name=>"fine", :tag=>[]},
{:name=>"how hare you", :tag=>[]},
{:name=>"heki", :tag=>["1", "2", "3"]},
{:name=>"railsgirls", :tag=>[]},
{:name=>"_byoy", :tag=>[]},
{:name=>"ajha", :tag=>[]},
{:name=>"nimei", :tag=>[]}]
How to get specified name's tag from taglist
For example , I want to extract user "fine"'s tag?
Could this be achieved without do iterator?
This will return the contents of the :tag key for any users name which == 'fine'
taglist.select { |x| x[:name] == 'fine' }.map { |u| u[:tag] }
First you select out only the users you are interested with .select.
And then use .map to collect an array of only what you want.
In this case the end result will be: []
Is do really an iterator?
taglist.find{|tl| tl[:name] == 'fine'}[:tag]
Just to be silly how about:
eval taglist.to_s[/:name=>"fine", :tag=>(.*?)}/, 1]
#=> []
No, it cannot be done without a loop.
And even if you find a solution where your code avoids a loop, for sure the library function that you're calling will include a loop. Finding an element in an array requires a loop. Period.
For example, take this (contrived) example
pattern = "fine"
def pattern.===(h); self == h[:name]; end
taglist.grep(pattern)
which does not seem to use a loop, but calls grep which is implemented using a loop.
Or another, equally contrived, example
class Hash; def method_missing(sym); self[sym]; end; end
taglist.group_by(&:name)["fine"]
which again does seem to call group_by without a loop, but actually it does.
So the answer is, no.
So my first answer missed the no do rule.
Here is an answer that doesn't use a do block.
i=0
begin
if taglist[i][:name] == 'fine'
tag = taglist[i][:tag]
break
end
i+=1
end while i < taglist.length - 0
Technically I think this is still using a block. But probably satisfies the restriction.

Remove string values that differ only in case from an array (Ruby)

So, here's the preface:
I'm quite a beginner in Ruby. I'm working on a anagram finding script (find all anagrams in a text file). The essence is: I create a dictionary where key is word code and value is an array with words that refer to this code. It is like that: {"abdeis"=>["abides", "biased"] ,"achr"=>["char"], ... }. In the end I just print out the values with length of >1. So good so far.
Here's the deal: I want to modify the output, so that such cases are omitted: ["Wood", "wood", "WooD"] - all are different in case only. But such cases should stay: ["Doom", "DooM", "mood"].
My current piece of code:
def print_anagram(anagram_dict)
anagram_list = anagram_dict.values
anagram_list.each { |i|
if i.length > 1
print i.join("\t")
print "\n"
else
next
end
}
end
anagram_dict is a dictionary mentioned above.
What checks should I make to throw these cases away? The things I think of seem way to bulky to me. Thanks in advance!
def print_anagram(anagram_dict)
anagram_list = anagram_dict.values
anagram_list.each do |i|
next if i.map(&:downcase).uniq.length == 1
if i.length > 1
print i.join("\t")
print "\n"
else
next
end
end
end
What this does:
make all Strings lowercase
get only unique elements from the array
if you only have one unique element, all elements are the same
map(&:downcase) is a shorter way of doing: map { |element| element.downcase }
Does this do what you want?
def print_anagram(anagram_dict)
anagram_list = anagram_dict.values.uniq{|word| word.downcase}
# rest of your code

How to finetune map function and avoid using flatten

I have the following code to list all possible permutations of a give string. But due to my awkward list (ruby array) manipulation and limited knowledge on functional programming, I have to use flatten to get the result array. It is pretty much a hack. How can I refactor the code and avoid using (abusing) flatten?
class String
def remove_char_at(i)
if i==0
self[1..-1]
else
self[0..i-1] + self[i+1..-1]
end
end
end
def permute(str,prefix="")
if str.size==0
prefix
else
str.chars.each_with_index.map do |s,i|
permute(str.remove_char_at(i),prefix+s)
end.flatten
end
end
You can find intresting things about functional programming in first chapters of SICP
def permute2(str,prefix="")
if str.size==0
[prefix] #revise for concatenate with memo
else
str.chars.each_with_index.inject([]) do |memo, ary|
s = ary[0]
i = ary[1]
memo += permute2(str.remove_char_at(i),prefix+s) #memoize
end
end
end
Ruby has done much of the hard work for you. To get all permutations for a string, myString, do the following:
myString.split('').permutation.map(&:join).uniq
This splits the string components into an array; gets all the permutations of the array; joins those back into strings; weeds out duplicates.
class String
def remove_char_at(i)
if i==0
self[1..-1]
else
self[0..i-1] + self[i+1..-1]
end
end
end
can be refactored as follows by using ... instead of ..
class String
def remove_char_at(i)
self[0...i] + self[i+1..-1]
end
end
I'm specifically answering the How can I refactor the code and avoid using (abusing) flatten? part:
Instead of map + flatten, you can just use flat_map which was introduced in 1.9.2.

Resources