I've been trying to solve a simple quiz question to find all the possible permutation of a string using Ruby and recursion.
I have the following Ruby code:
def permutation(string)
return [string] if string.size < 2
chr = string.chars.first
perms = permutation(string[1..-1])
result = []
for perm in perms
for i in (0..perm.size)
result << (perm[0..i] + chr + perm[i..-1])
end
end
return result
end
Whenever I try to test the code with puts permutation("abc") I get the following output:
cacbc
cbabc
cbcac
cbca
cacb
cbab
cba
Theoretically speaking it's supposed to be a very simple and straightforward problem, but I'm sure I'm doing something wrong. Most probably it's something with the ranges of the loops. And I know that Ruby Array class has instance method permutation to do that but I'm trying to solve it for practising.
Please note that the complexity is O(N!) for the current implementation. Is there anyway to enhance the performance further?
To see what the difficulty may be, let's try it with an even simpler example:
string = "ab"
Your desired result is ["ab", "ba"]. Let's see what you get:
string.size #=> 2
so we don't return when
return [string] if string.size < 2
#=> return ["ab"] if "ab".size < 2
is executed.
Next we calculate:
chr = string.chars.first #=> "a"
Notice that a more direct way of making this calculation is as follows:
chr = string[0] #=> "a"
or, better, using String#chr,
chr = string.chr #=> "a"
The latter illustrates why chr is not the best choice for the variable name.
Next
perms = permutation(string[1..-1])
#=> = permutation("b")
I will now indent the return values to emphasize that we are calling permutation a second time. permuation's argument is:
string #=> "b"
Now when we execute:
return [string] if string.size < 2
#=> return ["b"] if "b".size < 2
we return ["b"], so (back to original call to permutation):
perms = ["b"]
to go with chr => "a", calculated earlier. Next:
result = []
for perm in perms
for i in (0..perm.size)
result << (perm[0..i] + chr + perm[i..-1])
end
end
As perms contains only the single element "b", the two for loops simplify to:
for i in (0.."b".size)
result << ("b"[0..i] + "a" + "b"[i..-1])
end
which is:
for i in (0..1)
result << ("b"[0..i] + "a" + "b"[i..-1])
end
Notice that "b"[0..0], "b"[0..1] and "b"[0..-1] all equal "b"[0], which is just "b", and "b"[1..-1] #=> ''. Therefore, when i => 0, we execute:
result << ("b"[0..0] + "a" + "b"[0..-1])
#=> result << ("b" + "a" + "b")
#=> result << "bab"
and when i => 1:
result << ("b"[0..1] + "a" + "b"[1..-1])
#=> result << ("b" + "a" + "")
#=> result << "ba"
so:
result => ["bab" + "ba"]
which clearly is not what you want.
What you need to do is is change the double for loops to:
for perm in perms
result << chr + perm
for i in (1..perm.size-1)
result << (perm[0..i-1] + chr + perm[i..-1])
end
result << perm + chr
end
which could be written more compactly by employing the method String#insert:
for perm in perms
for i in (0..perm.size)
result << perm.dup.insert(i,chr)
end
end
which you would normally see written like this:
perms.each_with_object([]) do |perm, result|
(0..perm.size).each { |i| result << perm.dup.insert(i,chr) }
end
Notice that we have to .dup the string before sending insert, as insert modifies the string.
Doing it like this, you don't need result = []. Neither do you need return result, as parms.each_with_object returns result and if there is no return statement, the method returns the last quantity calculated. Also, you don't need the temporary variable perms (or ch, if desired).
Putting this altogether, we have:
def permutation(string)
return [string] if string.size < 2
ch = string[0]
permutation(string[1..-1]).each_with_object([]) do |perm, result|
(0..perm.size).each { |i| result << perm.dup.insert(i,ch) }
end
end
Let's try it:
permutation("ab")
#=> ["ab", "ba"]
permutation("abc")
#=> ["abc", "bac", "bca", "acb", "cab", "cba"]
permutation("abcd")
#=> ["abcd", "bacd", "bcad", "bcda", "acbd", "cabd",
# "cbad", "cbda", "acdb", "cadb", "cdab", "cdba",
# "abdc", "badc", "bdac", "bdca", "adbc", "dabc",
# "dbac", "dbca", "adcb", "dacb", "dcab", "dcba"]
Eki, which one are you in the picture?
You can use Array#permutation:
def permutation(string)
string.permutation(string.size).to_a
end
permutation('abc'.chars)
# => [["a", "b", "c"], ["a", "c", "b"], ["b", "a", "c"], ["b", "c", "a"],
# ["c", "a", "b"], ["c", "b", "a"]]
UPDATE Without usign Array#permutation:
def permutation(string)
return [''] if string.empty?
chrs = string.chars
(0...string.size).flat_map { |i|
chr, rest = string[i], string[0...i] + string[i+1..-1]
permutation(rest).map { |sub|
chr + sub
}
}
end
permutation('abc')
# => ["abc", "acb", "bac", "bca", "cab", "cba"]
Related
How can I check how many times a phrase occurs in a string?
For example, let's say the phrase is donut
str1 = "I love donuts!"
#=> returns 1 because "donuts" is found once.
str2 = "Squirrels do love nuts"
#=> also returns 1 because of 'do' and 'nuts' make up donut
str3 = "donuts do stun me"
#=> returns 2 because 'donuts' and 'do stun' has all elements to make 'donuts'
I checked this SO that suggests using include, but it only works if donuts is spelled in order.
I came up with this, but it doesn't stop spelling after all elements of "donuts"is spelled. i.e. "I love donuts" #=> ["o", "d", "o", "n", "u", "t", "s"]
def word(arr)
acceptable_word = "donuts".chars
arr.chars.select { |name| acceptable_word.include? name.downcase }
end
How can I check how many occurrences of donuts are there in a given string? No edge cases. Input will always be String, no nil. If it contains elements of donut only it should not count as 1 occurrence; it needs to contain donuts, doesn't have to be in order.
Code
def count_em(str, target)
target.chars.uniq.map { |c| str.count(c)/target.count(c) }.min
end
Examples
count_em "I love donuts!", "donuts" #=> 1
count_em "Squirrels do love nuts", "donuts" #=> 1
count_em "donuts do stun me", "donuts" #=> 2
count_em "donuts and nuts sound too delicious", "donuts" #=> 3
count_em "cats have nine lives", "donuts" #=> 0
count_em "feeding force scout", "coffee" #=> 1
count_em "feeding or scout", "coffee" #=> 0
str = ("free mocha".chars*4).shuffle.join
# => "hhrefemcfeaheomeccrmcre eef oa ofrmoaha "
count_em str, "free mocha"
#=> 4
Explanation
For
str = "feeding force scout"
target = "coffee"
a = target.chars
#=> ["c", "o", "f", "f", "e", "e"]
b = a.uniq
#=> ["c", "o", "f", "e"]
c = b.map { |c| str.count(c)/target.count(c) }
#=> [2, 2, 1, 1]
c.min
#=> 1
In calculating c, consider the first element of b passed to the block and assigned to the block variable c.
c = "c"
Then the block calculation is
d = str.count(c)
#=> 2
e = target.count(c)
#=> 1
d/e
#=> 2
This indicates that str contains enough "c"'s to match "coffee" twice.
The remaining calculations to obtain c are similar.
Addendum
If the characters of str matching characters target must be in the same order as those of target, the following regex could be used.
target = "coffee"
r = /#{ target.chars.join(".*?") }/i
#=> /c.*?o.*?f.*?f.*?e.*?e/i
matches = "xcorr fzefe yecaof tfe erg eeffoc".scan(r)
#=> ["corr fzefe ye", "caof tfe e"]
matches.size
#=> 2
"feeding force scout".scan(r).size
#=> 0
The questions marks in the regex are needed to make the searches non-greedy.
The solution is more or less simple (map(&:dup) is used there to avoid inputs mutating):
pattern = 'donuts'
[str1, str2, str3].map(&:dup).map do |s|
loop.with_index do |_, i|
break i unless pattern.chars.all? { |c| s.sub!(c, '') }
end
end
#⇒ [1, 1, 2]
Here's an approach with two variants, one where the letters must appear in order, and one where order is irrelevant. In both cases the frequency of each letter is respected, so "coffee" must match vs. two 'f' and two 'e' letters, "free mocha" is insufficient to match, lacking a second 'f'.
def sorted_string(string)
string.split('').sort.join
end
def phrase_regexp_sequence(phrase)
Regexp.new(
phrase.downcase.split('').join('.*')
)
end
def phrase_regexp_unordered(phrase)
Regexp.new(
phrase.downcase.gsub(/\W/, '').split('').sort.chunk_while(&:==).map do |bit|
"#{bit[0]}{#{bit.length}}"
end.join('.*')
)
end
def contains_unordered(phrase, string)
!!phrase_regexp_unordered(phrase).match(sorted_string(string.downcase))
end
def contains_sequence(phrase, string)
!!phrase_regexp_sequence(phrase).match(string.downcase)
end
strings = [
"I love donuts!",
"Squirrels do love nuts",
"donuts do stun me",
"no stunned matches",
]
phrase = 'donut'
strings.each do |string|
puts '%-30s %s %s' % [
string,
contains_unordered(phrase, string),
contains_sequence(phrase, string)
]
end
# => I love donuts! true true
# => Squirrels do love nuts true true
# => donuts do stun me true true
# => no stunned matches true false
Simple solution:
criteria = "donuts"
str1 = "I love donuts!"
str2 = "Squirrels do love nuts"
str3 = "donuts do stun me"
def strings_construction(criteria, string)
unique_criteria_array = criteria.split("").uniq
my_hash = {}
# Let's count how many times each character of the string matches a character in the string
unique_criteria_array.each do |char|
my_hash[char] ? my_hash[char] = my_hash[char] + 1 : my_hash[char] = string.count(char)
end
my_hash.values.min
end
puts strings_construction(criteria, str1) #=> 1
puts strings_construction(criteria, str2) #=> 1
puts strings_construction(criteria, str3) #=> 2
Building out a Rot method to solve encryption. I have something that is working but takes out whitespaces and any characters that are included. Was going to use bytes instead of chars then turn it back into a string once I have the byte code but I can't seem to get it working. How would you go about keeping those in place from this code:
code
def rot(x, string, encrypt=true)
alphabet = Array("A".."Z") + Array("a".."z")
results = []
if encrypt == true
key = Hash[alphabet.zip(alphabet.rotate(x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key.fetch(i).upcase
end
end
return results.join
else
key_false = Hash[alphabet.zip(alphabet.rotate(26 - x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key_false.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key_false.fetch(i).upcase
end
end
return results.join
end
end
puts rot(10, "Hello, World")
=> RovvyGybvn
puts rot(10, "Rovvy, Gybvn", false)
=> HelloWorld
Thanks for your help in advance!
Just add to both if blocks an else condition like this:
if ('a'..'z').include? i
# ...
elsif ('A'..'Z').include? i
# ...
else
results << i
end
Which will add all non A-z characters untouched to the output.
I've noticed some issues with your code:
Broken replacement hash
This is the biggest problem - your replacement hash is broken. I'm using a smaller alphabet for demonstration purposes, but this applies to 26 characters as well:
uppercase = Array("A".."C")
lowercase = Array("a".."c")
alphabet = uppercase + lowercase
#=> ["A", "B", "C", "a", "b", "c"]
You build the replacement hash via:
x = 1
key = Hash[alphabet.zip(alphabet.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"a", "a"=>"b", "b"=>"c", "c"=>"A"}
"C"=>"a" and "c"=>"A" are referring to the wrong character case. This happens because you rotate the entire alphabet at once:
alphabet #=> ["A", "B", "C", "a", "b", "c"]
alphabet.rotate(x) #=> ["B", "C", "a", "b", "c", "A"]
Instead. you have to rotate the uppercase and lowercase letter separately:
uppercase #=> ["A", "B", "C"]
uppercase.rotate(x) #=> ["B", "C", "A"]
lowercase #=> ["a", "b", "c"]
lowercase.rotate(x) #=> ["B", "C", "A"]
and concatenate the rotated parts afterwards. Either:
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
or:
key = Hash[(uppercase + lowercase).zip(uppercase.rotate(x) + lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
Replacing the characters
Back to a full alphabet:
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = 10
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
Having a working replacement hash makes replacing the characters almost trivial:
string = "Hello, World!"
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
#=> "Rovvy, Gybvn!"
I've changed result from an array to a string. It also has a << method and you don't have to join it afterwards.
Hash#fetch works almost like Hash#[], but you can pass a default value that is returned if the key is not found in the hash:
key.fetch("H", "H") #=> "R" (replacement value)
key.fetch("!", "!") #=> "!" (default value)
Handling encryption / decryption
You're duplicating a lot of code to handle the decryption part. But there's a much easier way - just reverse the direction:
rot(10, "Hello") #=> "Rovvy"
rot(10, "Rovvy", false) #=> "Hello"
rot(-10, "Rovvy") #=> "Hello"
So within your code, you can write:
x = -x unless encrypt
Putting it all together
def rot(x, string, encrypt = true)
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = -x unless encrypt
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
end
rot(10, "Hello, World!") #=> "Rovvy, Gybvn!"
rot(10, "Rovvy, Gybvn!", false) #=> "Hello, World!"
I originally wrote a method to take a word and find out if its vowels were in alphabetical order. I did it by using the code below:
def ordered_vowel_word?(word)
vowels = ["a", "e", "i", "o", "u"]
letters_arr = word.split("")
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
(0...(vowels_arr.length - 1)).all? do |i|
vowels_arr[i] <= vowels_arr[i + 1]
end
end
However, I decided to try to change it by using an all? method. I tried to do so with the following code:
def ordered_vowel_word?(word)
vowels = ["a","e", "i", "o", "u"]
splitted_word = word.split("")
vowels_in_word = []
vowels_in_word = splitted_word.select {|word| vowels.include?(word)}
vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]}
end
ordered_vowel_word?("word")
Anyone have any ideas why it isnt working? I would have expected this to work.
Also, if anyone has a better solution please feel free to post. Thanks!
Examples are:
it "does not return a word that is not in order" do
ordered_vowel_words("complicated").should == ""
end
it "handle double vowels" do
ordered_vowel_words("afoot").should == "afoot"
end
it "handles a word with a single vowel" do
ordered_vowel_words("ham").should == "ham"
end
it "handles a word with a single letter" do
ordered_vowel_words("o").should == "o"
end
it "ignores the letter y" do
ordered_vowel_words("tamely").should == "tamely"
end
Here is how I would do it:
#!/usr/bin/ruby
def ordered?(word)
vowels = %w(a e i o u)
check = word.each_char.select { |x| vowels.include?(x) }
# Another option thanks to #Michael Papile
# check = word.scan(/[aeiou]/)
puts check.sort == check
end
ordered?("afoot")
ordered?("outaorder")
Output is:
true
false
In your original example, you use the array values (String) as array indices which should be Integers when the all? method fires.
def ordered_vowel_word?(word)
vowels = ["a","e", "i", "o", "u"]
splitted_word = word.split("")
vowels_in_word = []
vowels_in_word = splitted_word.select {|word| vowels.include?(word)}
p vowels_in_word #=> ["o"]
vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]}
end
p ordered_vowel_word?("word")
#=> `[]': no implicit conversion of String into Integer (TypeError)
vowels_in_word contains only 'o', and inside the vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]} the expression vowels_in_word[x] means vowels_in_word["o"], which in-turn throws error as index can never be string.
I want to get the index as well as the results of a scan
"abab".scan(/a/)
I would like to have not only
=> ["a", "a"]
but also the index of those matches
[1, 3]
any suggestion?
Try this:
res = []
"abab".scan(/a/) do |c|
res << [c, $~.offset(0)[0]]
end
res.inspect # => [["a", 0], ["a", 2]]
There's a gotcha to look out for here, depending on the behaviour you expect.
If you search for /dad/ in "dadad" you'd only get [["dad",0]] because scan advances to the end of each match when it finds one (which is wrong to me).
I came up with this alternative:
def scan_str(str, pattern)
res = []
(0..str.length).each do |i|
res << [Regexp.last_match.to_s, i] if str[i..-1] =~ /^#{pattern}/
end
res
end
If you wanted you could also do a similar thing with StringScanner from the standard library, it might be faster for long strings.
Very similar to what #jim has said and works a bit better for longer strings:
def matches str, pattern
arr = []
while (str && (m = str.match pattern))
offset = m.offset(0).first
arr << offset + (arr[-1] ? arr[-1] + 1 : 0)
str = str[(offset + 1)..-1]
end
arr
end
It surprised me that there isn't any method similar to String#scan which would return array of MatchData objects, similar to String#match. So, if you like monkey-patching, you can combine this with Todd's solution (Enumerator is introduced in 1.9):
class Regexp
def scan str
Enumerator.new do |y|
str.scan(self) do
y << Regexp.last_match
end
end
end
end
#=> nil
/a/.scan('abab').map{|m| m.offset(0)[0]}
#=> [0, 2]
In Ruby, what is the most expressive way to map an array in such a way that certain elements are modified and the others left untouched?
This is a straight-forward way to do it:
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
Omitting the "leave-alone" case of course if not enough:
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
What I would like is something like this:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Is there some nice way to do this in Ruby (or maybe Rails has some kind of convenience method that I haven't found yet)?
Thanks everybody for replying. While you collectively convinced me that it's best to just use map with the ternary operator, some of you posted very interesting answers!
Because arrays are pointers, this also works:
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
In the loop, make sure you use a method that alters the object rather than creating a new object. E.g. upcase! compared to upcase.
The exact procedure depends on what exactly you are trying to achieve. It's hard to nail a definite answer with foo-bar examples.
old_a.map! { |a| a == "b" ? a + "!" : a }
gives
=> ["a", "b!", "c"]
map! modifies the receiver in place, so old_a is now that returned array.
I agree that the map statement is good as it is. It's clear and simple,, and would easy
for anyone to maintain.
If you want something more complex, how about this?
module Enumerable
def enum_filter(&filter)
FilteredEnumerator.new(self, &filter)
end
alias :on :enum_filter
class FilteredEnumerator
include Enumerable
def initialize(enum, &filter)
#enum, #filter = enum, filter
if enum.respond_to?(:map!)
def self.map!
#enum.map! { |elt| #filter[elt] ? yield(elt) : elt }
end
end
end
def each
#enum.each { |elt| yield(elt) if #filter[elt] }
end
def each_with_index
#enum.each_with_index { |elt,index| yield(elt, index) if #filter[elt] }
end
def map
#enum.map { |elt| #filter[elt] ? yield(elt) : elt }
end
alias :and :enum_filter
def or
FilteredEnumerator.new(#enum) { |elt| #filter[elt] || yield(elt) }
end
end
end
%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ]
require 'set'
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}>
('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy"
Your map solution is the best one. I'm not sure why you think map_modifying_only_elements_where is somehow better. Using map is cleaner, more concise, and doesn't require multiple blocks.
One liner:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
In the code above, you start with [] "cumulative". As you enumerate through an Enumerator (in our case the array, ["a", "b", "c"]), cumulative as well as "the current" item get passed to our block (|cumulative, i|) and the result of our block's execution is assigned to cumulative. What I do above is keep cumulative unchanged when the item isn't "b" and append "b!" to cumulative array and return it when it is a b.
There is an answer above that uses select, which is the easiest way to do (and remember) it.
You can combine select with map in order to achieve what you're looking for:
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
Inside the select block, you specify the conditions for an element to be "selected". This will return an array. You can call "map" on the resulting array to append the exclamation mark to it.
Ruby 2.7+
As of 2.7 there's a definitive answer.
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Here's a good read on the subject.
Hope that's useful to someone!
If you don't need the old array, I prefer map! in this case because you can use the ! method to represent you are changing the array in place.
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
I prefer this over:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
It's a few lines long, but here's an alternative for the hell of it:
oa = %w| a b c |
na = oa.partition { |a| a == 'b' }
na.first.collect! { |a| a+'!' }
na.flatten! #Add .sort! here if you wish
p na
# >> ["b!", "a", "c"]
The collect with ternary seems best in my opinion.
I've found that the best way to accomplish this is by using tap
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end