delete array elements by certain criteria - ruby

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"]]

Related

How to use keep_if with string array

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)

How to check if a key exists in an array of arrays?

Is there a straightforward way to do something like the following without excessive looping?
myArray = [["a","b"],["c","d"],["e","f"]]
if myArray.includes?("c")
...
I know this works fine if it's just a normal array of chars... but I would like something equally as elegant for an array of an array of chars (bonus points for helping convert this to an array of tuples).
If you only need a true/false answer you can flatten the array and call include on that:
>> myArray.flatten.include?("c")
=> true
You can use assoc:
my_array = [['a', 'b'], ['c', 'd'], ['e', 'f']]
if my_array.assoc('c')
# ...
It actually returns the whole subarray:
my_array.assoc('c') #=> ["c", "d"]
or nil if there is no match:
my_array.assoc('g') #=> nil
There's also rassoc to search for the second element:
my_array.rassoc('d') #=> ["c", "d"]
my_array = [["a","b"],["c","d"],["e","f"]]
p my_hash = my_array.to_h # => {"a"=>"b", "c"=>"d", "e"=>"f"}
p my_hash.key?("c") # => true
You can use Array#any?
myArray = [["a","b"],["c","d"],["e","f"]]
if myArray.any? { |x| x.includes?("c") }
# some code here
The find_index method works well for this:
myArray = [["a","b"],["c","d"],["e","f"]]
puts "found!" if myArray.find_index {|a| a[0] == "c" }
The return value is the array index of the pair or nil if the pair is not found.
You can capture the pair's value (or nil if not found) this way:
myArray.find_index {|a| a[0] == "c" } || [nil, nil])[1]
# => "d"

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}

Delete contents of array based on a set of indexes

delete_at only takes a single index. What's a good way to achieve this using built-in methods?
Doesn't have to be a set, can be an array of indexes as well.
arr = ["a", "b", "c"]
set = Set.new [1, 2]
arr.delete_at set
# => arr = ["a"]
One-liner:
arr.delete_if.with_index { |_, index| set.include? index }
Re-open the Array class and add a new method for this.
class Array
def delete_at_multi(arr)
arr = arr.sort.reverse # delete highest indexes first.
arr.each do |i|
self.delete_at i
end
self
end
end
arr = ["a", "b", "c"]
set = [1, 2]
arr.delete_at_multi(set)
arr # => ["a"]
This could of course be written as a stand-alone method if you don't want to re-open the class. Making sure the indexes are in reverse order is very important, otherwise you change the position of elements later in the array that are supposed to be deleted.
Try this:
arr.reject { |item| set.include? arr.index(item) } # => [a]
It's a bit ugly, I think ;) Maybe someone suggest a better solution?
Functional approach:
class Array
def except_values_at(*indexes)
([-1] + indexes + [self.size]).sort.each_cons(2).flat_map do |idx1, idx2|
self[idx1+1...idx2] || []
end
end
end
>> ["a", "b", "c", "d", "e"].except_values_at(1, 3)
=> ["a", "c", "e"]

Hash invert in Ruby?

I've got a hash of the format:
{key1 => [a, b, c], key2 => [d, e, f]}
and I want to end up with:
{ a => key1, b => key1, c => key1, d => key2 ... }
What's the easiest way of achieving this?
I'm using Ruby on Rails.
UPDATE
OK I managed to extract the real object from the server log, it is being pushed via AJAX.
Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
first variant
hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]
or
hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
UPD
ok, your hash is:
hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}
Lots of other good answers. Just wanted to toss this one in too for Ruby 2.0 and 1.9.3:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}
Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}
This is leveraging: Hash::[] and Enumerable#flat_map
Also in these new versions there is Enumerable::each_with_object which is very similar to Enumerable::inject/Enumerable::reduce:
hash.each_with_object(Hash.new){ |(k, v), inverse|
v.each{ |e| inverse[e] = k }
}
Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using an original hash with 100 keys, each with 100 distinct values:
Hash::[] w/ Enumerable#flat_map
155.7 (±9.0%) i/s - 780 in 5.066286s
Enumerable#each_with_object w/ Enumerable#each
199.7 (±21.0%) i/s - 940 in 5.068926s
Shows that the each_with_object variant is faster for that data set.
Ok, let's guess. You say you have an array but I agree with Benoit that what you probably have is a hash. A functional approach:
h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
Also:
h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
In the case where a value corresponds to more than one key, like "c" in this example...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}
...some of the other answers will not give the expected result. We will need the reversed hash to store the keys in arrays, like so:
{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }
This should do the trick:
reverse = {}
hash.each{ |k,vs|
vs.each{ |v|
reverse[v] ||= []
reverse[v] << k
}
}
This was my use case, and I would have defined my problem much the same way as the OP (in fact, a search for a similar phrase got me here), so I suspect this answer may help other searchers.
If you're looking to reverse a hash formatted like this, the following may help you:
a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
values.each {|value| memo[value] = key }
memo
end
this returns:
{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}
This gives
new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}
If you want to correctly deal with duplicate values, then you should use the Hash#inverse
from Facets of Ruby
Hash#inverse preserves duplicate values,
e.g. it ensures that hash.inverse.inverse == hash
either:
use Hash#inverse from here: http://www.unixgods.org/Ruby/invert_hash.html
use Hash#inverse from FacetsOfRuby library 'facets'
usage like this:
require 'facets'
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
The code looks like this:
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash
# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h
class Hash
def inverse
i = Hash.new
self.each_pair{ |k,v|
if (v.class == Array)
v.each{ |x|
i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
}
else
i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
end
}
return i
end
end
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
One way to achieve what you're looking for:
arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]
results_arr = []
arr.each do |hsh|
hsh.values.flatten.each do |val|
results_arr << { [val] => hsh.keys.first }···
end
end
Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

Resources