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.
Related
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"
So i need to turn an array of strings into a sentence, capitalize the first word and add a period at the end. I have looked everywhere and found bits and pieces but nothing as specific as my problem.
What i tried so far:
array1 = ["this", "is", "my", "first", "post"]
def sentence_maker (array)
array.join(' ')
end
It makes a sentence but i can't figure out how to make the first word capitalized while keeping the others in lower case and add a "." at the end of the sentence. Any help would be appreciated.
You could do as below :
array1 = ["this", "is", "my", "first", "post"]
def sentence_maker (array)
array.join(' ').capitalize << "."
end
sentence_maker(array1)
# => "This is my first post."
How I would do it:
array1 = ["this", "is", "my", "first", "post"]
def sentence_maker(array)
string = array.join(' ')
string.capitalize!
string << '.'
end
puts sentence_maker(array1)
#=> "This is my first post."
See: http://www.ruby-doc.org/core-1.9.3/String.html
The simple way to do this would be to use the capitalize method, but note that “case conversion is effective only in ASCII region”:
"école".capitalize
# => "école"
If this is likely to be an issue you should look into using something like the Unicode Utils gem:
require 'unicode_utils'
UnicodeUtils.titlecase("école")
# => "École"
So your complete method might look something like:
def sentence_maker (array)
array[0] = UnicodeUtils.titlecase(array[0])
array.join(' ') << '.'
end
(This is a bit different from the other answers because titlecase changes the first letter of each word in the string, which we don’t want in this case. Also note this modifies array which you might not want, so you”d have to structure the code differently if that were the case.)
Sorry to ask this but I really need to get this done. I'd like to be able to pass in a string and strip out the stop_words. I have the following:
class Query
def self.normalize term
stop_words=["a","big","array"]
term.downcase!
legit=[]
if !stop_words.include?(term)
legit << term
end
return legit
end
def self.check_parts term
term_parts=term.split(' ')
tmp_part=[]
term_parts.each do |part|
t=self.normalize part
tmp_part << t
end
return tmp_part
end
end
I would think that this would return only terms that are not in the stop_words list but I'm getting back either an empty array or an array of the terms passed in. Like this:
ruby-1.9.2-p290 :146 > Query.check_parts "here Is my Char"
=> [[], [], [], ["char"]]
ruby-1.9.2-p290 :147 >
What am I doing wrong?
thx in advance
If you just want to filter out the terms and get an array of downcased words, it is simple.
module Query
StopWords = %w[a big array]
def self.check_parts string; string.downcase.split(/\s+/) - StopWords end
end
Query.check_parts("here Is my Char") # => ["here", "is", "my", "char"]
Why do you want the result as an array I don't know but
term_parts=term.split(' ')
term_parts.reject { |part| stop_words.include?(part) }
You could write what you expect.
By the way, you have an array for array because
def self.check_parts term
term_parts=term.split(' ')
tmp_part=[] # creates an array
term_parts.each do |part|
t=self.normalize part # normalize returns an empty array
# or one of only one element (a term).
tmp_part << t # you add an array into the array
end
return tmp_part
end
I am wondering what purpose does the counts variable serve, the one right before the last end?
# Pick axe page 51, chapter 4
# Count frequency method
def count_frequency(word_list)
counts = Hash.new(0)
for word in word_list
counts[word] += 1
end
counts #what does this variable actually do?
end
puts count_frequency(["sparky", "the", "cat", "sat", "on", "the", "mat"])
The last expression in any Ruby method is the return value for that method. If counts were not at the end of the method, the return value would be the result of the for loop; in this case, that's the word_list array itself:
irb(main):001:0> def count(words)
irb(main):002:1> counts = Hash.new(0)
irb(main):003:1> for word in words
irb(main):004:2> counts[word] += 1
irb(main):005:2> end
irb(main):006:1> end
#=> nil
irb(main):007:0> count %w[ sparky the cat sat on the mat ]
#=> ["sparky", "the", "cat", "sat", "on", "the", "mat"]
Another way someone might write the same method in 1.9:
def count_frequency(word_list)
Hash.new(0).tap do |counts|
word_list.each{ |word| counts[word]+=1 }
end
end
Though some people consider using tap like this to be an abuse. :)
And, for fun, here's a slightly-slower-but-purely-functional version:
def count_frequency(word_list)
Hash[ word_list.group_by(&:to_s).map{ |word,array| [word,array.length] } ]
end
Ruby doesn't require you to use the return statement to return a value in a method. The last line evaluated in the method will be returned if an explicit return statement is omitted.
It provides the return value for the function; it's how the result (which is stored in that variable) is transmitted back to the caller (i.e., the line of code at the end.) The last expression evaluated in a Ruby function is used as the return value.
Counts is a dictionary, i.e. it is an associative map of keys to values.
In this case, the words are the keys, the values are the number of occurrences.
The dictionary is returned from the function count_frequency
I have a Ruby array containing some string values. I need to:
Find all elements that match some predicate
Run the matching elements through a transformation
Return the results as an array
Right now my solution looks like this:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Is there an Array or Enumerable method that combines select and map into a single logical statement?
I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}
=> [3, 3, 3, nil, nil, nil]
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]
Ruby 2.7+
There is now!
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!
You can use reduce for this, which requires only one pass:
[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3]
In other words, initialize the state to be what you want (in our case, an empty list to fill: []), then always make sure to return this value with modifications for each element in the original list (in our case, the modified element pushed to the list).
This is the most efficient since it only loops over the list with one pass (map + select or compact requires two passes).
In your case:
def example
results = #lines.reduce([]) do |lines, line|
lines.push( ...(line) ) if ...
lines
end
return results.uniq.sort
end
Another different way of approaching this is using the new (relative to this question) Enumerator::Lazy:
def example
#lines.lazy
.select { |line| line.property == requirement }
.map { |line| transforming_method(line) }
.uniq
.sort
end
The .lazy method returns a lazy enumerator. Calling .select or .map on a lazy enumerator returns another lazy enumerator. Only once you call .uniq does it actually force the enumerator and return an array. So what effectively happens is your .select and .map calls are combined into one - you only iterate over #lines once to do both .select and .map.
My instinct is that Adam's reduce method will be a little faster, but I think this is far more readable.
The primary consequence of this is that no intermediate array objects are created for each subsequent method call. In a normal #lines.select.map situation, select returns an array which is then modified by map, again returning an array. By comparison, the lazy evaluation only creates an array once. This is useful when your initial collection object is large. It also empowers you to work with infinite enumerators - e.g. random_number_generator.lazy.select(&:odd?).take(10).
If you have a select that can use the case operator (===), grep is a good alternative:
p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3]
p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"]
If we need more complex logic we can create lambdas:
my_favourite_numbers = [1,4,6]
is_a_favourite_number = -> x { my_favourite_numbers.include? x }
make_awesome = -> x { "***#{x}***" }
my_data = [1,2,3,4]
p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]
I'm not sure there is one. The Enumerable module, which adds select and map, doesn't show one.
You'd be required to pass in two blocks to the select_and_transform method, which would be a bit unintuitive IMHO.
Obviously, you could just chain them together, which is more readable:
transformed_list = lines.select{|line| ...}.map{|line| ... }
Simple Answer:
If you have n records, and you want to select and map based on condition then
records.map { |record| record.attribute if condition }.compact
Here, attribute is whatever you want from the record and condition you can put any check.
compact is to flush the unnecessary nil's which came out of that if condition
No, but you can do it like this:
lines.map { |line| do_some_action if check_some_property }.reject(&:nil?)
Or even better:
lines.inject([]) { |all, line| all << line if check_some_property; all }
I think that this way is more readable, because splits the filter conditions and mapped value while remaining clear that the actions are connected:
results = #lines.select { |line|
line.should_include?
}.map do |line|
line.value_to_map
end
And, in your specific case, eliminate the result variable all together:
def example
#lines.select { |line|
line.should_include?
}.map { |line|
line.value_to_map
}.uniq.sort
end
def example
#lines.select {|line| ... }.map {|line| ... }.uniq.sort
end
In Ruby 1.9 and 1.8.7, you can also chain and wrap iterators by simply not passing a block to them:
enum.select.map {|bla| ... }
But it's not really possible in this case, since the types of the block return values of select and map don't match up. It makes more sense for something like this:
enum.inject.with_index {|(acc, el), idx| ... }
AFAICS, the best you can do is the first example.
Here's a small example:
%w[a b 1 2 c d].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["a", "b", "c", "d"]
%w[a b 1 2 c d].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["A", "B", false, false, "C", "D"]
But what you really want is ["A", "B", "C", "D"].
You should try using my library Rearmed Ruby in which I have added the method Enumerable#select_map. Heres an example:
items = [{version: "1.1"}, {version: nil}, {version: false}]
items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}
If you want to not create two different arrays, you can use compact! but be careful about it.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!
Interestingly, compact! does an in place removal of nil. The return value of compact! is the same array if there were changes but nil if there were no nils.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }
Would be a one liner.
Your version:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
My version:
def example
results = {}
#lines.each{ |line| results[line] = true if ... }
return results.keys.sort
end
This will do 1 iteration (except the sort), and has the added bonus of keeping uniqueness (if you don't care about uniq, then just make results an array and results.push(line) if ...
Here is a example. It is not the same as your problem, but may be what you want, or can give a clue to your solution:
def example
lines.each do |x|
new_value = do_transform(x)
if new_value == some_thing
return new_value # here jump out example method directly.
else
next # continue next iterate.
end
end
end