Given a string of digits, I am trying to insert '-' between odd numbers and '*' between even numbers. The solution below:
def DashInsertII(num)
num = num.chars.map(&:to_i)
groups = num.slice_when {|x,y| x.odd? && y.even? || x.even? && y.odd?}.to_a
puts groups.to_s
groups.map! do |array|
if array[0].odd?
array.join(" ").gsub(" ", "-")
else
array.join(" ").gsub(" ", "*")
end
end
d = %w{- *}
puts groups.join.chars.to_s
groups = groups.join.chars
# Have to account for 0 because Coderbyte thinks 0 is neither even nor odd, which is false.
groups.each_with_index do |char,index|
if d.include? char
if (groups[index-1] == "0" || groups[index+1] == "0")
groups.delete_at(index)
end
end
end
groups.join
end
is very convoluted, and I was wondering if I could do something like this:
"99946".gsub(/[13579][13579]/) {|s,x| s+"-"+x}
where s is the first odd, x the second. Usually when I substitute, I replace the matched term, but here I want to keep the matched term and insert a character between the pattern. This would make this problem much simpler.
This will work for you:
"99946".gsub(/[13579]+/) {|s| s.split("").join("-") }
# => "9-9-946"
It's roughly similar to what you tried. It captures multiple consecutive odd digits, and uses the gsub block to split and then join them separated by the "-".
This will include both solutions working together:
"99946".gsub(/[13579]+/) {|s| s.split("").join("-") }.gsub(/[02468]+/) {|s| s.split("").join("*") }
# => "9-9-94*6"
The accepted answer illustrates well the logic required to solve the problem. However, I'd like to suggest that in production code that it be simplified somewhat so that it is easier to read and understand.
In particular, we are doing the same thing twice with different arguments, so it would be helpful to the reader to make that obvious, by writing a method or lambda that both uses call. For example:
do_pair = ->(string, regex, delimiter) do
string.gsub(regex) { |s| s.chars.join(delimiter) }
end
Then, one can call it like this:
do_pair.(do_pair.('999434432', /[13579]+/, '-'), /['02468']+/, '*')
This could be simplified even further:
do_pair = ->(string, odd_or_even) do
regex = (odd_or_even == :odd) ? /[13579]+/ : /['02468']+/
delimiter = (odd_or_even == :odd) ? '-' : '*'
string.gsub(regex) { |s| s.chars.join(delimiter) }
end
One advantage to this approach is that it makes obvious both the fact that we are processing two cases, odd and even, and the values we are using for those two cases. It can then be called like this:
do_pair.(do_pair.('999434432', :odd), :even)
This could also be done in a method, of course, and that would be fine. The reason I suggested a lambda is that it's pretty minimal logic and it is used in only one (albeit compound) expression in a single method.
This is admittedly more verbose, but breaks down the logic for the reader into more easily digestible chunks, reducing the cognitive cost of understanding it.
The ordinary way to do that is:
"99946"
.gsub(/(?<=[13579])(?=[13579])/, "-")
.gsub(/(?<=[2468])(?=[2468])/, "*")
# => "9-9-94*6"
or
"99946".gsub(/(?<=[13579])()(?=[13579])|(?<=[2468])()(?=[2468])/){$1 ? "-" : "*"}
# => "9-9-94*6"
"2899946".each_char.chunk { |c| c.to_i.even? }.map { |even, arr|
arr.join(even ? '*' : '-') }.join
#=> "2*89-9-94*6"
The steps:
enum0 = "2899946".each_char
#=> #<Enumerator: "2899946":each_char>
We can convert enum0 to an array to see the elements it will generate:
enum0.to_a
#=> ["2", "8", "9", "9", "9", "4", "6"]
Continuing,
enum1 = enum0.chunk { |c| c.to_i.even? }
#=> #<Enumerator: #<Enumerator::Generator:0x007fa733024b58>:each>
enum1.to_a
#=> [[true, ["2", "8"]], [false, ["9", "9", "9"]], [true, ["4", "6"]]]
a = enum1.map { |even, arr| arr.join(even ? '*' : '-') }
#=> ["2*8", "9-9-9", "4*6"]
a.join
#=> "2*89-9-94*6"
Related
I'm trying to find all possible prefixes from a list of strings. We can remove "/" or ":" from prefixes to make it more readable.
input = ["item1", "item2", "product1", "product2", "variant:123", "variant:789"]
Expected output
item
product
variant
The key here is to find your delimiter. It looks like your delimiters are numbers and : and /. So you should be able to map through the array, use the delimiter in a regex to return the prefix. You also have the option to check it exists in the array (so you TRULY know that its a prefix) but I didnt include it in my answer.
input = ["item1", "item2", "product1", "product2", "variant:123", "variant:789"]
prefixes = input.map {|word| word.gsub(/:?\/?[0-9]/, '')}.uniq
=> ["item", "product", "variant"]
The more you delimiters you have, you can append it onto the regex pattern. Do a little reading here about wildcards :-)
Hope this answers your question!
I assume the order of the prefixes that is returned is unimportant. Also, I have disregarded the treatment of the characters "/" and ":" because that is straightforward and a distraction to the central problem.
Let's first create a helper method whose sole argument is an array of words that begin with the same letter.
def longest_prefix(arr)
a0, *a = arr
return a0 if a0.size == 1 || arr.size == 1
n = (1..a0.size-1).find do |i|
c = a0[i]
a.any? { |w| w[i] != c }
end
n.nil? ? a0 : a0[0,n]
end
For example,
arr = ["dog1", "doggie", "dog2", "doggy"]
longest_prefix arr
#=> "dog"
We now merely need to group the words by their first letters, then map the resulting key-value pairs to the return value of the helper method when its argument equals the value of the key-value pair.
def prefixes(arr)
arr.group_by { |w| w[0] }.map { |_,a| longest_prefix(a) }
end
Suppose, for example,
arr = ["dog1", "eagles", "eagle", "doggie", "dog2", "catsup",
"cats", "elephant", "cat", "doggy", "caustic"]
Then
prefixes arr
#=> ["dog", "e", "ca"]
Note that
arr.group_by { |w| w[0] }
#=> { "d"=>["dog1", "doggie", "dog2", "doggy"],
# "e"=>["eagles", "eagle", "elephant"],
# "c"=>["catsup", "cats", "cat", "caustic"] }
See Enumerable#group_by.
I'm trying to do something like a string analyzer and I need to retrieve the ending of a word and compare it with the keys of an hash
word = "Test"
ending_hash = {"e" => 1, "st" => 2}
output = 2
I need the output to be 2 in this case, but actually I won't know if the length of the ending is of 1 or 2 characters. Is it possible to do?
Initially, assume that you know that word ends with (at least) one of the keys of ending_hash. You can then write:
word = "Test"
ending_hash = {"e" => 1, "st" => 2}
ending_hash.find { |k,v| word.end_with?(k) }.last
#=> 2
See Enumerable#find, String#end_with? and Array#last.
The intermediate calculation is as follows:
ending_hash.find { |k,v| word.end_with?(k) }
#=> ["st", 2]
If you are unsure if any of the keys may match the end of the string, write:
ending_hash = {"e" => 1, "f" => 2}
arr = ending_hash.find { |k,v| word.end_with?(k) }
#=> nil
arr.nil? ? nil : arr.last
#=> nil
or better:
ending_hash.find { |k,v| word.end_with?(k) }&.last
#=> nil
Here & is the Safe Navigation Operator. In a nutshell, if the expression preceding & returns nil, the SNO immediately returns nil for the entire expression, without executing last.
Even if word must end with one of the keys, you may want to write it this way so that you can check the return value and raise an exception if it is nil.
You could alternatively write:
ending_hash.find { |k,v| word.match? /#{k}\z/ }&.last
The regular expression reads, "match the value of k (#{k}) at the end of the string (the anchor \z)".
Note the following:
{"t"=>1, "st"=>2}.find { |k,v| word.end_with?(k) }&.last
#=> 1
{"st"=>1, "t"=>2}.find { |k,v| word.end_with?(k) }&.last
#=> 1
so the order of the keys may matter.
Lastly, as the block variable v is not used in the block calculation, the block variables would often be written |k,_| or |k,_v|, mainly to signal to the reader that only k is used in the block calculation.
If you know there will be only a small number of lengths of endings, it is much faster to check for all possible lengths, than to check for all endings. (It also makes sense to check them from longest to shortest, in case they overlap, otherwise the shorter will never be matched.)
The lazy one-liner:
(-2..-1).lazy.map { |cut| ending_hash[word[cut..]] }.find(&:itself)
The functional version:
(-2..-1).inject(nil) { |a, e| a || ending_hash[word[e..]] }
The blunt but moist version:
ending_hash[word[-2..]] || ending_hash[word[-1..]]
I am trying to remove punctuation from an array of words without using regular expression. In below eg,
str = ["He,llo!"]
I want:
result # => ["Hello"]
I tried:
alpha_num="abcdefghijklmnopqrstuvwxyz0123456789"
result= str.map do |punc|
punc.chars {|ch|alpha_num.include?(ch)}
end
p result
But it returns ["He,llo!"] without any change. Can't figure out where the problem is.
include? block returns true/false, try use select function to filter illegal characters.
result = str.map {|txt| txt.chars.select {|c| alpha_num.include?(c.downcase)}}
.map {|chars| chars.join('')}
p result
str=["He,llo!"]
alpha_num="abcdefghijklmnopqrstuvwxyz0123456789"
Program
v=[]<<str.map do |x|
x.chars.map do |c|
alpha_num.chars.map.include?(c.downcase) ? c : nil
end
end.flatten.compact.join
p v
Output
["Hello"]
exclusions = ((32..126).map(&:chr) - [*'a'..'z', *'A'..'Z', *'0'..'9']).join
#=> " !\"\#$%&'()*+,-./:;<=>?#[\\]^_`{|}~"
arr = ['He,llo!', 'What Ho!']
arr.map { |word| word.delete(exclusions) }
#=> ["Hello", "WhatHo"]
If you could use a regular expression and truly only wanted to remove punctuation, you could write the following.
arr.map { |word| word.gsub(/[[:punct:]]/, '') }
#=> ["Hello", "WhatHo"]
See String#delete. Note that arr is not modified.
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"
This is what I've been doing instead:
my_array.reject { |elem| elem =~ /regex/ }.each { ... }
I feel like this is a little unwieldy, but I haven't found anything built in that would let me change it to my_array.grepv /regex/ { ... }
Is there such a function?
Ruby 2.3 implements an Enumerable#grep_v method that is exactly what you're after.
https://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-grep_v
You know how Symbol#to_proc helps with chaining? You can do the same with regular expressions:
class Regexp
def to_proc
Proc.new {|string| string =~ self}
end
end
["Ruby", "perl", "Perl", "PERL"].reject(&/perl/i)
=> ["Ruby"]
But you probably shouldn't. Grep doesn't just work with regular expressions - you can use it like the following
[1,2, "three", 4].grep(Fixnum)
and if you wanted to grep -v that, you'd have to implement Class#to_proc, which sounds wrong.
How about this?
arr = ["abc", "def", "aaa", "def"]
arr - arr.grep(/a/) #=> ["def", "def"]
I deliberately included a dup to make sure all of them are returned.
What about inverting the regex?
["ab", "ac", "bd"].grep(/^[^a]/) # => ["bd"]
I don't believe there's anything built-in like this, but it's simple enough to add:
class Array
def grepv(regex, &block)
self.reject { |elem| elem =~ regex }.each(&block)
end
end
Note that you need to use parens around the regex when you call this function, otherwise you get a syntax error:
myarray.grepv(/regex/) { ... }
You can do:
my_array.reject{|e| e[/regex/]}.each { ... }
but really it's hard to be more concise and self-documenting. It could be written using grep(/.../) with some negative-lookahead pattern, but then I think it becomes harder to comprehend the overall action because the pattern itself is harder to understand.
You simply need to negate the result of the regexp match.
Enumerable.module_eval do
def grepv regexp
if block_given?
self.each do |item|
yield item if item !~ regexp
end
else
self.find_all do |item|
item !~ regexp
end
end
end
end
Thank you all for your comments. In the end, I did it this way:
module Enumerable
def grepv(condition)
non_matches = []
self.each do |item|
unless condition === item or condition === item.to_s
non_matches.push(item)
yield item if block_given?
end
end
return non_matches
end
end
Not sure if that's the best way because I'm just getting started with Ruby. It's a bit longer than other people's solutions here, but I like it because it's quite analogous to Enumerable's grep option - it works with anything that can handle the ===, just like grep, and it yields the items it finds if a block was given, and either way returns an Array of those that didn't match.
I added the or to_s part so that any integers, for instance, interspersed in the array could be matched with the same regex, though I could imagine this might jack things sometimes.
Try using Array#collect!
my_array.collect! do |elem|
if elem =~ /regex/
# do stuff
elem
end
end
EDIT: Sorry, then you would have to call Array#compact after. At least that would eliminate the second block. But it's more physical code. It depends on how much "stuff" you do.
Here's another shot at it, with a sprinkling of bltxd's and Hsiu's answers, and hopefully retaining as much of the spirit of the original grep as possible (even if it's wordy):
module Enumerable
def grepv(condition)
if block_given?
each do |item|
yield item if not condition === item
end
else
inject([]) do |memo, item|
memo << item if not condition === item
memo
end
end
end
end
If you supply a block, then everything is lazy like you would expect. If you don't supply a block, there is a little duplicate code. I really wish that Andrew Grimm's answer applied in the general case.
>> (%w(1 2 3) + [4]).cycle(3).grepv(Fixnum)
=> ["1", "2", "3", "1", "2", "3", "1", "2", "3"]
>> (%w(1 2 3) + [4]).cycle(3).grepv(/[12]/)
=> ["3", 4, "3", 4, "3", 4]
In neither case do you pay up to O(n^2) for item comparison, like you would in the worst case if you do array subtraction.