How to remove 3 or more the same character - ruby

During my lessons of Ruby I came across of this exercise. I'm trying to remove 3 or more the same charactes in a row. Test Cases Input: abbbaaccada Output: ccada Input: bbccdddcb Output: (Empty string)
So far I have solution which doesn't return expected results:
def playground("abbbaaccada")
count = string.length
string.chars.each_with_index.map { |v, i| (v * (count - i)).capitalize }.join('')
end
output gives me
==> AaaaaaaaaaaBbbbbbbbbbBbbbbbbbbBbbbbbbbAaaaaaaAaaaaaCccccCcccAaaDdA
instead of
==> ccada
Could you please advise?
Edit:
Forgot to add that regexp isn't allowed

There are two challenges here:
Match and remove any run of thee or more characters in a row
Recurse to test again in case the previous step created a new run of three
Here's one way to do it:
THREE_OR_MORE = /(.)\1{2,}/
def three_is_too_many(str)
if str.match? THREE_OR_MORE
str = three_is_too_many(str.gsub(THREE_OR_MORE, ''))
end
str
end
The regexp finds any character ('.'), followed by itself ('\1'), two or more times ('{2,}').
Then the routine either a) removes three or more and tests again or b) returns the string.

Here's a potential solution. The following method searches for any subsequence of an array with repeats, and returns the range of the repeated values if there are three or more of them.
def find_3_or_more(ary)
ary.each_index do |i|
j = i + 1
while j < ary.length && ary[i] == ary[j]
j += 1
end
return (i...j) if j - i > 2
end
nil
end
This portion breaks the target string into an array of chars, and repeatedly slices out the characters in ranges identified as repeats until there are none, as indicated by a nil range.
def delete_3_or_more(str)
ary = str.chars
while r = find_3_or_more(ary)
ary.slice!(r)
end
return ary.join
end
It seems to do the job for your test cases.

def recursively_remove_runs_of_3_or_more(str)
arr = str.chars
loop do
a = arr.slice_when { |a,b| a.downcase != b.downcase }.to_a
b = a.reject! { |e| e.size > 2 }
arr = a.flatten
break arr.join if b.nil?
end
end
recursively_remove_runs_of_3_or_more "abbbaaccada"
#=> "ccada"
This uses Enumerable#slice_when (new in MRI v2.2). Note that Array#reject! returns nil when no changes are made.
You could alternatively use Enumerable#chunk_while (new in MRI v2.3). Simply replace:
a = arr.slice_when { |a,b| a.downcase != b.downcase }.to_a
with:
a = arr.chunk_while { |a,b| a.downcase == b.downcase }.to_a
chunk_while and slice_when are yin and yang.
If a regular expression could be used and case where not an issue, you could write:
str = "abbbaaccada"
s = str.dup
loop { break(s) if s.gsub!(/(.)\1{2,}/, '').nil? }
#=> "ccada"

(I wanted to comment, but it doesn't allow me to do that yet.)
Assuming that you are trying to learn, I chose to only give you some tips while avoiding a solution.
There might be shorter ways of doing this using regex or/and some String methods. However, you said you can not use regex.
My tip is, try to solve it only using the sections you have covered so far. It may not necessarily be the most elegant solution, but you can revise it as you progress. As others suggested, recursion might be a good option. But, if you are not familiar with that yet, you can try slicing the string and merging the parts you need. This can be combined with an endless loop to check the new string satisfies your condition: but think about when you need to break out of the loop.
Also, in your code:
v * (count - i)
String#* actually gives you count - i copies of v, concatenated together.

Related

Given a string, how do I compare the characters to see if there are duplicates?

I'm trying to compare characters in a given string to see if there are duplicates, and if there are I was to remove the two characters to reduce the string to as small at possible. eg. ("ttyyzx") would equal to ("zx")
I've tried converting the characters in an array and then using an #each_with_index to iterate over the characters.
arr = ("xxyz").split("")
arr.each_with_index do |idx1, idx2|
if idx1[idx2] == idx1[idx2 + 1]
p idx1[idx2]
p idx1[idx2 + 1]
end
end
At this point I just wan to be able to print the next character in the array within the loop so I know I can move on to the next step, but no matter what code I use it will only print out the first character "x".
To only keep the unique characters (ggorlen's answer is "b"): count all characters, find only those that appear once. We rely on Ruby's Hash producing keys in insertion order.
def keep_unique_chars(str)
str.each_char.
with_object(Hash.new(0)) { |element, counts| counts[element] += 1 }.
select { |_, count| count == 1 }.
keys.
join
end
To remove adjacent dupes only (ggorlen's answer is "aba"): a regular expression replacing adjacent repetitions is probably the go-to method.
def remove_adjacent_dupes(str)
str.gsub(/(.)\1+/, '')
end
Without regular expressions, we can use slice_when to cut the array when the character changes, then drop the groups that are too long. One might think a flatten would be required before join, but join doesn't care:
def remove_adjacent_dupes_without_regexp(str)
str.each_char.
slice_when { |prev, curr| prev != curr }.
select { |group| group.size == 1 }.
join
end
While amadan's and user's solution definitely solve the problem I felt like writing a solution closer to the OP's attempt:
def clean(string)
return string if string.length == 1
array = string.split('')
array.select.with_index do |value, index|
array[index - 1] != value && array[index + 1] != value
end.join
end
Here are a few examples:
puts clean("aaaaabccccdeeeeeefgggg")
#-> bdf
puts clean("m")
#-> m
puts clean("ttyyzx")
#-> zx
puts clean("aab")
#-> b
The method makes use of the fact that the characters are sorted and in case there are duplicates, they are either before or after the character that's being checked by the select method. The method is slower than the solutions posted above, but as OP mentioned he does not yet work with hashes yet I though this might be useful.
If speed is not an issue,
require 'set'
...
Set.new(("xxyz").split("")).to_a.join # => xyz
Making it a Set removes duplicates.
The OP does not want to remove duplicates and keep just a single copy, but remove all characters completely from occurring more than once. So here is a new approach, again compact, but not fast:
"xxyz".split('').sort.join.gsub(/(.)\1+/,'')
The idea is to sort the the letters; hence, identical letters will be joined together. The regexp /(.)\1+/ describes a repetition of a letter.

Getting position from multiple characters in a string [duplicate]

I am trying to return the index's to all occurrences of a specific character in a string using Ruby. A example string is "a#asg#sdfg#d##" and the expected return is [1,5,10,12,13] when searching for # characters. The following code does the job but there must be a simpler way of doing this?
def occurances (line)
index = 0
all_index = []
line.each_byte do |x|
if x == '#'[0] then
all_index << index
end
index += 1
end
all_index
end
s = "a#asg#sdfg#d##"
a = (0 ... s.length).find_all { |i| s[i,1] == '#' }
require 'enumerator' # Needed in 1.8.6 only
"1#3#a#".enum_for(:scan,/#/).map { Regexp.last_match.begin(0) }
#=> [1, 3, 5]
ETA: This works by creating an Enumerator that uses scan(/#/) as its each method.
scan yields each occurence of the specified pattern (in this case /#/) and inside the block you can call Regexp.last_match to access the MatchData object for the match.
MatchData#begin(0) returns the index where the match begins and since we used map on the enumerator, we get an array of those indices back.
Here's a less-fancy way:
i = -1
all = []
while i = x.index('#',i+1)
all << i
end
all
In a quick speed test this was about 3.3x faster than FM's find_all method, and about 2.5x faster than sepp2k's enum_for method.
Here's a long method chain:
"a#asg#sdfg#d##".
each_char.
each_with_index.
inject([]) do |indices, (char, idx)|
indices << idx if char == "#"
indices
end
# => [1, 5, 10, 12, 13]
requires 1.8.7+
Another solution derived from FMc's answer:
s = "a#asg#sdfg#d##"
q = []
s.length.times {|i| q << i if s[i,1] == '#'}
I love that Ruby never has only one way of doing something!
Here's a solution for massive strings. I'm doing text finds on 4.5MB text strings and the other solutions grind to a halt. This takes advantage of the fact that ruby .split is very efficient compared to string comparisions.
def indices_of_matches(str, target)
cuts = (str + (target.hash.to_s.gsub(target,''))).split(target)[0..-2]
indicies = []
loc = 0
cuts.each do |cut|
loc = loc + cut.size
indicies << loc
loc = loc + target.size
end
return indicies
end
It's basically using the horsepower behind the .split method, then using the separate parts and the length of the searched string to work out locations. I've gone from 30 seconds using various methods to instantaneous on extremely large strings.
I'm sure there's a better way to do it, but:
(str + (target.hash.to_s.gsub(target,'')))
adds something to the end of the string in case the target is at the end (and the way split works), but have to also make sure that the "random" addition doesn't contain the target itself.
indices_of_matches("a#asg#sdfg#d##","#")
=> [1, 5, 10, 12, 13]

Reversing a Ruby String, without .reverse method

I am working on this coding challenge, and I have found that I am stuck. I thought it was possible to call the .string method on an argument that was passed in, but now I'm not sure. Everything I've found in the Ruby documentation suggests otherwise. I'd really like to figure this out without looking at the solution. Can someone help give me a push in the right direction?
# Write a method that will take a string as input, and return a new
# string with the same letters in reverse order.
# Don't use String's reverse method; that would be too simple.
# Difficulty: easy.
def reverse(string)
string_array = []
string.split()
string_array.push(string)
string_array.sort! { |x,y| y <=> x}
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
This is the simplest one line solution, for reversing a string without using #reverse, that I have come across -
"string".chars.reduce { |x, y| y + x } # => "gnirts"
Additionally, I have never heard of the #string method, I think you might try #to_s.
Easiest way to reverse a string
s = "chetan barawkar"
b = s.length - 1
while b >= 0
print s[b]
b=b-1
end
You need to stop the search for alternative or clever methods, such as altering things so you can .sort them. It is over-thinking the problem, or in some ways avoiding thinking about the core problem you have been asked to solve.
What this test is trying to get you you to do, is understand the internals of a String, and maybe get an appreciation of how String#reverse might be implemented using the most basic string operations.
One of the most basic String operations is to get a specific character from the string. You can get the first character by calling string[0], and in general you can get the nth character (zero-indexed) by calling string[n].
In addition you can combine or build longer strings by adding them together, e.g. if you had a="hell" and b="o", then c = a + b would store "hello" in the variable c.
Using this knowledge, find a way to loop through the original string and use that to build the reverse string, one character at a time. You may also need to look up how to get the length of a string (another basic string method, which you will find in any language's string library), and how to loop through numbers in sequence.
You're on the right track converting it to an array.
def reverse(str)
str.chars.sort_by.with_index { |_, i| -i }.join
end
Here is a solution I used to reverse a string without using .reverse method :
#string = "abcde"
#l = #string.length
#string_reversed = ""
i = #l-1
while i >=0 do
#string_reversed << #string[i]
i = i-1
end
return #string_reversed
Lol, I am going through the same challenge. It may not be the elegant solution, but it works and easy to understand:
puts("Write is a string that you want to print in reverse")
#taking a string from the user
string = gets.to_s #getting input and converting into string
def reverse(string)
i = 0
abc = [] # creating empty array
while i < string.length
abc.unshift(string[i]) #populating empty array in reverse
i = i + 1
end
return abc.join
end
puts ("In reverse: " + reverse(string))
Thought i'd contribute my rookie version.
def string_reverse(string)
new_array = []
formatted_string = string.chars
new_array << formatted_string.pop until formatted_string.empty?
new_array.join
end
def reverse_str(string)
# split a string to create an array
string_arr = string.split('')
result_arr = []
i = string_arr.length - 1
# run the loop in reverse
while i >=0
result_arr.push(string_arr[i])
i -= 1
end
# join the reverse array and return as a string
result_arr.join
end

Ruby modify a piece of a string

Totally new to Ruby. This is a simple homework assignment. The secret_code function needs to take in input string and perform the following actions:
In the first block of letters before a space, capitalize all but the first char
Reverse the string
So if the input were "super duper", the output should be "repud REPUs".
I coded the function as follows:
def secret_code(input)
input.split(" ").first[1..-1].each_char do |i|
input[i] = i.upcase
end
return input.reverse
end
It passes the unit tests, but I am wondering if there is a better way to code it. Is it possible to avoid using the loop? I tried
return input.split(" ").first[1..-1].upcase.reverse
But that didn't quite work. Any thoughts on how to clean this up are appreciated!
"super duper".sub(/(?<=.)\S+/, &:upcase).reverse
How about this:
def secret_code(input)
first_space = input.index(' ')
(input[0] + input[1...first_space].upcase + input[first_space..-1]).reverse
end
Note that in Ruby, the last expression evaluate in a method is always returned, so you can omit the final return.
s = "super duper"
words = s.split(' ')
words.first[1..-1] = words.first[1..-1].upcase
words.each { |word| word.reverse! }
s = words.reverse.join(' ')
puts s # => repud REPUs
Not necessarily any better, but sure, it can be done without a loop...
def f x
(b = [(a = x.split)[0].upcase, *a.drop(1)].join(' ').reverse)[-1] = x[0, 1]
return b
end
You can try the below:
a = "super duper"
p a.gsub(a.split[0...1].join(' '),a.split[0...1].join(' ').capitalize.swapcase).reverse
Output:
"repud REPUs"

How do I make multiple combinations with a string in ruby?

Input should be a string:
"abcd#gmail.com"
Output should be an Array of strings:
["abcd#gmail.com",
"a.bcd#gmail.com",
"ab.cd#gmail.com",
"abc.d#gmail.com",
"a.b.cd#gmail.com",
"a.bc.d#gmail.com",
"a.b.c.d#gmail.com"]
The idea: "Make every possible combination in the first string part ("abcd") with a dot. Consecutive dots are not allowed. There are no dots allowed in the beginning and in the end of the first string part ("abcd")"
This is what I've came up with so far:
text,s = "abcd".split""
i=0
def first_dot(text)
text.insert 1,"."
end
def set_next_dot(text)
i = text.rindex(".")
text.delete_at i
text.insert(i+1,".")
end
My approach was
write a function, that sets the first dot
write a function that sets the next dot
...(magic)
I do not know how to put the pieces together. Any Idea? Or perhaps a better way?
thanx in advance
edit:
I think I found the solution :)
I will post it in about one hour (it's brilliant -> truth tables, binary numbers, transposition)
...and here the solution
s = "abc"
states = s.length
possibilites = 2**states
def set_space_or_dot(value)
value.gsub("0","").gsub("1",".")
end
def fill_with_leading_zeros(val, states)
if val.length < states
"0"*(states-val.length)+val
else
val
end
end
a = Array.new(possibilites,s)
a = a.map{|x| x.split ""}
b = [*0...possibilites].map{|x| x.to_s(2).to_s}
b = b.map{|x| fill_with_leading_zeros x,states}
b = b.map{|x| x.split ""}
c = []
for i in 0 ... a.size
c[i] = (set_space_or_dot (a[i].zip b[i]).join).strip
end
Changing pduersteler answer a little bit:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibility = string.dup
possibilities << possibility.insert(pos+1, '.')
end
How about this (probably needs a bit more fine-tuning to suit your needs):
s = "abcd"
(0..s.size-1).map do |i|
start, rest = [s[0..i], s[(i+1)..-1]]
(0..rest.size-1).map { |j| rest.dup.insert(j, '.') }.map { |s| "#{start}#{s}"}
end.flatten.compact
#=> ["a.bcd", "ab.cd", "abc.d", "ab.cd", "abc.d", "abc.d"]
An option would be to iterate n times through your string moving the dot, where n is the amount of chars minus 1. This is what you're doing right now, but without defining two methods.
Something like this:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibilities << string.dup.insert(pos+1, '.')
end
edit
Now tested. THanks to the comments, you need to call .dup on the string before the insert. Otherwise, the dot gets inserted into the string and will stay there for each iteration causing a mess. Calling .dup onthe string will copy the string and works on the copy instead, leaving the original string untouched.

Resources