Reputation: 105
I am learning Ruby. As part of my HW, I am to find the first occurence of two duplicate characters in a row in a string, and return the duplicated character. This is what I came up with:
require 'set'
def find_a_dup_using_set(arr)
s = Set.new
arr.find { |e| !s.add?(e) }
end
p find_a_dup_using_set(["q", "q", "c", "d", "e"])
Question: was this the best approach? Maybe because I am still learning, but I feel this isn't what they were asking for, but this is what I knew what worked based on research I did. Is there a reason not to use an array for something like this?
Upvotes: 1
Views: 973
Reputation: 11226
Why not just use a simple regex?
str = 'abccdd'
str[/(.)\1/][0]
=> 'c'
The regex here groups each character and find the first consecutive pair. Then we just get the first character by calling 0 index.
In ruby there are several ways to use a Regular Expression on a string. So you could put this a method.
def find_first_dup_in_string(str)
str[/(.)\1/][0]
end
Here's a variation on tadman's answer and I'll include benchmarks to compare UPDATED to use each_char
as per comments.
def find_first_dup_a(str)
d = ''
str.each_char.each_cons(2){|c| d = c[0]; break if c[0] == c[1] }
d
end
alpha=[*'a'..'z']
str = ''
1000.times{ str << alpha.sample}
cycles = 100000
Benchmark.bm do |x|
x.report(:ruby) { cycles.times { find_first_dup_a(str) } }
x.report(:regex) { cycles.times { find_first_dup_in_string(str) } }
end
ruby 0.330000 0.010000 0.340000 ( 0.338940)
regex 0.140000 0.000000 0.140000 ( 0.151719)
=> [
[0] #<Benchmark::Tms:0x00007fb6a0bd4c88 @label="ruby", @real=0.33893999992869794, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.33000000000000007, @total=0.3400000000000001>,
[1] #<Benchmark::Tms:0x00007fb6a2601390 @label="regex", @real=0.1517189999576658, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.14000000000000057, @total=0.14000000000000057>
]
And an funny coincidence, not relevant whatsoever :)
14.0/33.0 * 100
=> 42.42424242424242
Upvotes: 2
Reputation: 211610
In Ruby strings can be turned into arrays of characters, and then you can have all kinds of fun with them:
def duup?(str)
!!str.chars.each_cons(2).find { |a,b| a == b }
end
Where that just uses an each_cons
(each consecutive) iterator and finds the first instance of the two letters being identical.
If that's not exciting enough:
def duup?(str)
!!str.chars.each_cons(2).lazy.map(&:uniq).map(&:length).include?(1)
end
Where this reduces each pair to only the unique elements and looks for those that collapsed into an array of length 1. lazy
is thrown in for good measure.
You could also do something a little obscure like:
def duup?(str)
!!(1...str.length).find { |i| str[i].ord ^ str[i-1].ord == 0 }
end
If you like binary math, XOR will return zero if the two values are identical as they cancel themselves out.
Or for simplicity:
def duup?(str)
!!str.chars.each_cons(2).find { |v| v == v.reverse }
end
Where if the reversed set is the same as the forward set it must be two of the same thing.
Note that some of these can be easily scaled up to N characters as the 2
is completely arbitrary.
As an exercise you may want to benchmark these routines with strings of varying lengths. Some approaches might not be viable on huge strings.
Upvotes: 1