How to use keep_if with string array - ruby

I'm trying to use keep_if in my Rails 5 app with Ruby 2.3.1.
a = ["a", "b", "c", "d" ]
b = ["b", "d"]
a.keep_if { |v| v=~ /["#{b}"]/ }
#=> ["b", "d"]
Real project:
a = ["apple", "banana", "orange"]
b = ["mangoes", "banana", "pear"]
a.keep_if { |v| v=~ /["#{b}"]/ }
#=> ["mangoes", "banana", "pear"]
What I expected:
#=> ["banana"]
I'm guessing some sort of regex to be used? How to get what I expected?

keep_if() deletes every element of self for which block evaluates to false. See Array#select!
If no block is given, an enumerator is returned instead.
#Cary Swoveland has mentioned in a comment that the following should work if you want to use keep_if():
a.keep_if { |v| b.include?(v) } #=> ["banana"]
The following would work if you wanted to use Array#select! instead for perhaps a different scenario:
c = a+b
c.select { |x| c.count(x) == 2 }.uniq #=> ["banana"]
# (use .uniq > 2 for values that appear more than once)

Related

Looking to convert information from a file into a hash Ruby

Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.

Ruby string char chunking

I have a string "wwwggfffw" and want to break it up into an array as follows:
["www", "gg", "fff", "w"]
Is there a way to do this with regex?
"wwwggfffw".scan(/((.)\2*)/).map(&:first)
scan is a little funny, as it will return either the match or the subgroups depending on whether there are subgroups; we need to use subgroups to ensure repetition of the same character ((.)\1), but we'd prefer it if it returned the whole match and not just the repeated letter. So we need to make the whole match into a subgroup so it will be captured, and in the end we need to extract just the match (without the other subgroup), which we do with .map(&:first).
EDIT to explain the regexp ((.)\2*) itself:
( start group #1, consisting of
( start group #2, consisting of
. any one character
) and nothing else
\2 followed by the content of the group #2
* repeated any number of times (including zero)
) and nothing else.
So in wwwggfffw, (.) captures w into group #2; then \2* captures any additional number of w. This makes group #1 capture www.
You can use back references, something like
'wwwggfffw'.scan(/((.)\2*)/).map{ |s| s[0] }
will work
Here's one that's not using regex but works well:
def chunk(str)
chars = str.chars
chars.inject([chars.shift]) do |arr, char|
if arr[-1].include?(char)
arr[-1] << char
else
arr << char
end
arr
end
end
In my benchmarks it's faster than the regex answers here (with the example string you gave, at least).
Another non-regex solution, this one using Enumerable#slice_when, which made its debut in Ruby v.2.2:
str.each_char.slice_when { |a,b| a!=b }.map(&:join)
#=> ["www", "gg", "fff", "w"]
Another option is:
str.scan(Regexp.new(str.squeeze.each_char.map { |c| "(#{c}+)" }.join)).first
#=> ["www", "gg", "fff", "w"]
Here the steps are as follows
s = str.squeeze
#=> "wgfw"
a = s.each_char
#=> #<Enumerator: "wgfw":each_char>
This enumerator generates the following elements:
a.to_a
#=> ["w", "g", "f", "w"]
Continuing
b = a.map { |c| "(#{c}+)" }
#=> ["(w+)", "(g+)", "(f+)", "(w+)"]
c = b.join
#=> "(w+)(g+)(f+)(w+)"
r = Regexp.new(c)
#=> /(w+)(g+)(f+)(w+)/
d = str.scan(r)
#=> [["www", "gg", "fff", "w"]]
d.first
#=> ["www", "gg", "fff", "w"]
Here's one more way of doing it without a regex:
'wwwggfffw'.chars.chunk(&:itself).map{ |s| s[1].join }
# => ["www", "gg", "fff", "w"]

remove elements from array in ruby

I have an array and I want to remove some elements. I tried this but it doesn't work:
#restaurants.each_with_index do |restaurant, i|
if (restaurant.stars > 3) #restaurants.slice!(i) end
end
How can I do it?
#restaurants.reject!{|restaurant| restaurant.stars > 3}
You can use Array#delete_at(index): see rubydoc
But the best way for you will be to use reject! (rubydoc) or delete_if (rubydoc).
If restaurants is an array you can use pop, e.g.
a = [ "a", "b", "c", "d" ]
a.pop #=> "d"
a.pop(2) #=> ["b", "c"]
a #=> ["a"]
#restaurants.reject! {|restaurant| restaurant.stars > 3}

Eliminate consecutive duplicates of list elements

What is the best solution to eliminate consecutive duplicates of list elements?
list = compress(['a','a','a','a','b','c','c','a','a','d','e','e','e','e']).
p list # => # ['a','b','c','a','d','e']
I have this one:
def compress(list)
list.map.with_index do |element, index|
element unless element.equal? list[index+1]
end.compact
end
Ruby 1.9.2
Nice opportunity to use Enumerable#chunk, as long as your list doesn't contain nil:
list.chunk(&:itself).map(&:first)
For Ruby older than 2.2.x, you can require "backports/2.2.0/kernel/itself" or use {|x| x} instead of (&:itself).
For Ruby older than 1.9.2, you can require "backports/1.9.2/enumerable/chunk" to get a pure Ruby version of it.
Do this (provided that each element is a single character)
list.join.squeeze.split('')
Ruby 1.9+
list.select.with_index{|e,i| e != list[i+1]}
with respect to #sawa, who told me about with_index :)
As #Marc-André Lafortune noticed if there is nil at the end of your list it won't work for you. We can fix it with this ugly structure
list.select.with_index{|e,i| i < (list.size-1) and e != list[i+1]}
# Requires Ruby 1.8.7+ due to Object#tap
def compress(items)
last = nil
[].tap do |result|
items.each{ |o| result << o unless last==o; last=o }
end
end
list = compress(%w[ a a a a b c c a a d e e e e ])
p list
#=> ["a", "b", "c", "a", "d", "e"]
arr = ['a','a','a','a','b','c','c','a','a','d','e','e','e','e']
enum = arr.each
#=> #<Enumerator: ["a", "a", "a", "a", "b", "c", "c", "a", "a", "d",
# "e", "e", "e", "e"]:each>
a = []
loop do
n = enum.next
a << n unless n == enum.peek
end
a #=> ["a", "b", "c", "a", "d"]
Enumerator#peek raises a StopIteration exception when it has already returned the last element of the enumerator. Kernel#loop handles that exception by breaking out of the loop.
See Array#each and Enumerator#next. Kernel#to_enum1 can be used in place of Array#each.
1 to_enum is an Object instance method that is defined in the Kernel module but documented in the Object class. Got that?

delete array elements by certain criteria

What's the best and way to do this:
I have two arrays:
a=[['a','one'],['b','two'],['c','three'],['d','four']]
and b=['two','three']
I want to delete nested arrays inside a that include elements in b,to get this:
[['a','one']['d','four']
Thanks.
a = [['a','one'],['b','two'],['c','three'],['d','four']]
b = ['two','three']
a.delete_if { |x| b.include?(x.last) }
p a
# => [["a", "one"], ["d", "four"]]
rassoc to the rescue!
b.each {|el| a.delete(a.rassoc(el)) }
a=[['a','one'],['b','two'],['c','three'],['d','four']]
b=['two','three']
result=a.reject { |e| b.include?(e.first) or b.include?(e.last) }
# result => [["a", "one"], ["d", "four"]]

Resources