Find all elements in an array between two elements - arrays

Is there any method to get the elements in an array between two values?
Example:
arr = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
## After some operation..
arr # => ["AGC", "xxx", "tcv", "car"]
How do I get the elements that are between "AGC" and "car" in a new array?

You could use Ruby's flip-flop operator:
def extract(arr, first, last)
return nil unless ([first, last]-arr).empty? && arr.index(first) <= arr.index(last)
arr.select { |s| s==first..s==last ? true : false }
end
arr = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
extract(arr, 'AGC', 'car')
#=> ["AGC", "xxx", "tcv", "car"]
extract(arr, 'car', 'car')
#=> ["car"]
extract(arr, 'AGC', 'dog')
#=> nil
extract(arr, 'car', 'AGC')
#=> nil

If you're confident they're both present then you should be able to do this:
a = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
a[(a.index('abc')+1)..a.index('car')]
# => ["AGC", "xxx", "tcv", "car"]

You can use Array#drop_while/Array#take_while. Note that this leads to one-off, hence the need for push at the end:
things = ['abc', 'AGC', 'xxx', 'tcv', 'car', 'kiss']
things.
drop_while { |thingy| thingy != 'AGC' }.
take_while { |thingy| thingy != 'car' }.
push('car')
# => ["AGC", "xxx", "tcv", "car"]
Alternatively:
things.
slice_before { |thingy| thingy == 'AGC' }.to_a.last.
slice_after { |thingy| thingy == 'car' }.first
# => ["AGC", "xxx", "tcv", "car"]

Here's a variation of #ndn's answer, with 2 reverses:
def extract(arr, first, last)
arr.reverse.drop_while{ |x| x != last }.reverse.drop_while{ |x| x != first }
end
arr = %w[abc AGC xxx tcv car kiss]
p extract(arr, 'AGC', 'car')
#=> ["AGC", "xxx", "tcv", "car"]
p extract(arr, 'car', 'car')
#=> ["car"]
p extract(arr, 'AGC', 'dog')
#=> []
p extract(arr, 'car', 'AGC')
#=> []
The advantage of using 2 drop_whiles is that no extra push or drop is needed, and if first and last aren't present or well ordered, the method returns an empty array.
Here's a related question : "Ruby split lines from files while iterating each line"

Yet another silly way.
Assuming that the elements are all strings and a certain character, say "#", is not included,
arr.join("#")[/(?<=#)AGC#.+#car(?=#)/].split("#")
# => ["AGC", "xxx", "tcv", "car"]

Related

Ruby group array of arrays to merge them into one single array based on unique key in arrays

I tried other options available in other stack overflow requests but couldn't get the result I was looking for.
I have an array of arrays in it as below:
Input:
[["ABC", "5A2", nil, "88474"],
["ABC", nil, "2", "88474"],
["ABC", nil, nil, "88474"],
["ABC", nil, nil, "88474"],
["Jack", "5A2", nil, "05195"],
["Jack", nil, "2", "05195"],
["Jack", nil, nil, "05195"],
["Jack", nil, nil, "05195"]]
Array index 0 ABC or Jack will be used as group_by condition and I want the output as below where all ABC arrays are merged to show the values and removes the nil if any of the array holds a value in that index position:
Output:
[["ABC", "5A2", "2", "88474"],
["Jack", "5A2", "2", "05195"]]
It won't be always the same format as input where first element second value follows by second element third value. It can be changing but second value wont be set twice in multiple elements for same index 0 with different values and same applies for third or if I add fourth or fifth elements as well.
I have worked with array of hash but not array of arrays so not sure how to do it.
I'm not certain that I understand the question but I expect you may be looking for the following.
arr = [
["ABC", "5A2", nil, "88474"],
["ABC", nil, "2", "88474"],
["ABC", nil, nil, "88474"],
["ABC", nil, nil, "88474"],
["Jack", "5A2", nil, "05195"],
["Jack", nil, "2", "05195"],
["Jack", nil, nil, "05195"],
["Jack", nil, nil, "05195"]
]
arr.each_with_object({}) do |a, h|
h.update(a.first=>a) { |_k, oa, na| oa.zip(na).map { |ov, nv| ov.nil? ? nv : ov } }
end.values
#=> [["ABC", "5A2", "2", "88474"], ["Jack", "5A2", "2", "05195"]]
This uses the form of Hash#update (a.k.a. merge!) that employs the block
{ |_k, oa, na| oa.zip(na).map { |ov, nv| ov.nil? ? nv : ov } }
to determine the values of keys that are present in both the hash being built (h) and the hash being merged ({ a.first=>a }). See the doc for a description of the three block variables, _k, oa and na.1
I can best explain how the calculations procede by salting the code with puts statements and running it with an abbreviated array arr.
arr = [
["ABC", "5A2", nil, "88474"],
["ABC", nil, "2", "88474"],
["Jack", "5A2", nil, "05195"],
["Jack", nil, "2", "05195"],
]
arr.each_with_object({}) do |a, h|
puts "\na = #{a}"
puts "h = #{h}"
puts "a.first=>a = #{a.first}=>#{a}"
h.update(a.first=>a) do |_k, oa, na|
puts "_k = #{_k}"
puts "oa = #{oa}"
puts "na = #{na}"
a = oa.zip(na)
puts "oa.zip(na) = #{a}"
a.map do |ov, nv|
puts " ov = #{ov}, nv = #{nv}"
puts " ov.nil? ? nv : ov = #{ov.nil? ? nv : ov}"
ov.nil? ? nv : ov
end
end
end.tap { |h| puts "h = #{h}" }.values
#=> [["ABC", "5A2", "2", "88474"], ["Jack", "5A2", "2", "05195"]]
The following is displayed.
a = ["ABC", "5A2", nil, "88474"]
h = {}
a.first=>a = ABC=>["ABC", "5A2", nil, "88474"]
(The block is not called here because h does not have a key "ABC")
a = ["ABC", nil, "2", "88474"]
h = {"ABC"=>["ABC", "5A2", nil, "88474"]}
a.first=>a = ABC=>["ABC", nil, "2", "88474"]
_k = ABC
oa = ["ABC", "5A2", nil, "88474"]
na = ["ABC", nil, "2", "88474"]
oa.zip(na) = [["ABC", "ABC"], ["5A2", nil], [nil, "2"], ["88474", "88474"]]
ov = ABC, nv = ABC
ov.nil? ? nv : ov = ABC
ov = 5A2, nv =
ov.nil? ? nv : ov = 5A2
ov = , nv = 2
ov.nil? ? nv : ov = 2
ov = 88474, nv = 88474
ov.nil? ? nv : ov = 88474
a = ["Jack", "5A2", nil, "05195"]
h = {"ABC"=>["ABC", "5A2", "2", "88474"]}
a.first=>a = Jack=>["Jack", "5A2", nil, "05195"]
(The block is not called here because h does not have a key "Jack")
a = ["Jack", nil, "2", "05195"]
h = {"ABC"=>["ABC", "5A2", "2", "88474"], "Jack"=>["Jack", "5A2", nil, "05195"]}
a.first=>a = Jack=>["Jack", nil, "2", "05195"]
_k = Jack
oa = ["Jack", "5A2", nil, "05195"]
na = ["Jack", nil, "2", "05195"]
oa.zip(na) = [["Jack", "Jack"], ["5A2", nil], [nil, "2"], ["05195", "05195"]]
ov = Jack, nv = Jack
ov.nil? ? nv : ov = Jack
ov = 5A2, nv =
ov.nil? ? nv : ov = 5A2
ov = , nv = 2
ov.nil? ? nv : ov = 2
ov = 05195, nv = 05195
ov.nil? ? nv : ov = 05195
h = {"ABC"=>["ABC", "5A2", "2", "88474"], "Jack"=>["Jack", "5A2", "2", "05195"]}
1. As is common practice, I began the name of the common key, _k, with an underscore to signal to the reader that it is not used in the block calculation. Often you will see such a block variable represented by an underscore alone.
Another options is to group the array based on the first element using Enumerable#group_by and Hash#values, then zipping into columns and compact each column taking the first element (better explanation below).
This is the final one liner
arr.group_by(&:first).values.map { |first, *last| first.zip(*last).map { |a| a.compact.first } }
#=> [["ABC", "5A2", "2", "88474"], ["Jack", "5A2", "2", "05195"]]
How it works
Let's say you have those three arrays,that you can consider as rows of a table:
a = [1, nil, nil]
b = [1, 2, nil]
c = [nil, 2, 3]
You can Array#zip to get the columns:
a.zip(b, c) #=> [[1, 1, nil], [nil, 2, 2], [nil, nil, 3]]
Another way to get the columns is to wrap the array in another array:
tmp = [a, b, c]
This shows how to unpack the array:
tmp.then { |first, *last| p first; p *last }
#=> [1, nil, nil]
#=> [1, 2, nil]
#=> [nil, 2, 3]
So, zipping returns:
tmp.then { |first, *last| first.zip *last }
#=> [[1, 1, nil], [nil, 2, 2], [nil, nil, 3]]
Now, once we have the columns we need to compact (Array#compact) each one and take the first element.
For example:
[1, 1, nil].compact.first #=> 1
In the case of tmp array:
tmp.then { |first, *last| first.zip(*last).map { |column| column.compact.first } }
#=> [1, 2, 3]
Putting everything together you get the final one liner shown above.

How to reject an element from an array which also contains hashes, in Ruby?

Eg:
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
reject(hash_var, "1") # should return ["2", {"a" => ["aa", "bb", "cc"]}]
reject(hash_var, "a") # should return ["1", "2"]
reject(hash_var, {"a" => ["aa"]}) # should return ["2", {"a" => ["bb", "cc"]}]
Basically, I should be able to reject the element from the array, be it hash or array element.
you can use delete mehod on hash like
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
hash_var.delete("1") //this will delete 1 from hash then new value of hash will be ["2", {"a"=>["aa", "bb", "cc"]}]
hash_var[1]["a"].delete("aa") //this will give output ["2", {"a"=>["bb", "cc"]}]
and so on according to your use
That should do the job
hash_var.drop_while { |e| e == '1' }
hash_var.reject { |e| e == '1' }
hash_var.find_all { |e| e != '1' }
hash_var.select { |e| e != '1' }
You could define the instance method Enumerable#dig_drop:
arr = ["1", "2", {"a" => ["aa", "bb", "cc"]}, {"b" => ["aa", "bb"]}]
module Enumerable
def dig_drop(*elm)
self.each_with_index {|_, idx|
unless self[idx].respond_to? :dig
self.delete(elm[0])
next
end
last=elm.pop
if elm.empty?
self[idx].delete(last)
else
self[idx].dig(*elm).nil? ? nil : self[idx].dig(*elm).delete(last)
end
}
self.reject!(&:empty?)
end
end
arr.dig_drop "1" # arr equals ["2", {"a" => ["aa", "bb", "cc"]}, {"b" => ["aa", "bb"]}]
arr.dig_drop('a') # arr equals ["2", {"b" => ["aa", "bb"]}]
arr.dig_drop('b', 'bb') # arr equals ["2", {"b" => ["aa"]}
I think this method should work:
def reject(hash_var, ele)
# Find the index of the ele
# If it is found, just remove it and return hash_var
idx = hash_var.index(ele)
if idx
hash_var.delete_at(idx)
return hash_var
end
# Convert the hashes to arrays - 2D arrays
# { "b" => "2" } becomes [["b", "2"]]
(0...hash_var.length).each do |n|
if hash_var[n].class == Hash
hash_var[n] = hash_var[n].to_a
end
end
(0...hash_var.length).each_with_index do |n, i|
# Only operate if hash_var[n] is an array
# this means that this is 2D array i.e. hash
if hash_var[n].class == Array
# If this is just as element, delete the whole hash_key
# i.e. reject(hash_var, "a")
# here ele = "a", so since this is not hash
# we need to delete full element i.e. ["aa", "bb", "cc"]
if hash_var[n][0][0] == ele
hash_var.delete_at(i)
else
next if ele.class == String
ele_a = ele.to_a
if hash_var[n][0][0] == ele_a[0][0]
diff = hash_var[n][0][1] - ele_a[0][1]
hash_var[n][0][1] = diff
end
end
end
end
## Convert the 2D arrays back to hashes
(0...hash_var.length).each do |n|
if hash_var[n].class == Array
hash_var[n] = hash_var[n].to_h
end
end
# return final
return hash_var
end
Tests:
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
p reject(hash_var, "1")
# => ["2", {"a"=>["aa", "bb", "cc"]}]
p reject(hash_var, "a")
# => ["1", "2"]
p reject(hash_var, {"a" => ["aa"]})
# => ["1", "2", {"a"=>["bb", "cc"]}]

How to check if hash keys match value from array

I have:
arr = ['test', 'testing', 'test123']
ht = {"test": "abc", "water": "wet", "testing": "fun"}
How can I select the values in ht whose key matches arr?
ht_new = ht.select {|hashes| arr.include? hashes}
ht_new # => "{"test": "abc", "testing": "fun"}"
Additionally, how can we return values from:
arr = ["abc", "123"]
ht = [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}, {"key": "doremi", "value": "rain"}}]
output # => [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}]
Only a slight change is needed:
ht.select { |k,_| arr.include? k.to_s }
##=> {:test=>"abc", :testing=>"fun"}
See Hash#select.
The block variable _ (a valid local variable), which is the value of key k, signifies to the reader that it is not used in the block calculation. Some prefer writing that |k,_v|, or some-such.
One option is mapping (Enumerable#map) the keys in arr:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun", "test123"=>nil}
If you want to get rid of pairs with nil value:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] if ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun"}
This is an option for the last request:
ht.select{ |h| h if h.values.any? { |v| arr.include? v} }
# or
arr.map { |e| ht.find { |h| h.values.any?{ |v| v == e } } }
#=> [{:key=>"abc", :value=>"test"}, {:key=>"123", :value=>"money"}]
A straightforward way is:
ht.slice(*arr.map(&:to_sym))
# => {:test => "abc", :testing => "fun"}

Select items from arrays of an array with certain indexes

I want to select items from each array of arr from index position 0, 2 and 4
Input array
arr = [
["name", "address", "contact", "company", "state"],
["n1", "add1", "c1", "cp1", "s1"],
["n2", "add2", "c2", "cp2", "s2"]
]
Output array
arr = [
["name", "contact", "company"],
["n1", "c1", "cp1"],
["n2", "c2", "cp2"]
]
as an alternative to deleting unneeded items, you can just select the needed items.
arr.map{|subarray| subarray.values_at(0, 2, 4) }
# => [["name", "contact", "state"], ["n1", "c1", "s1"], ["n2", "c2", "s2"]]
If you want tot take this more generic and only select the even columns you could do it like this
arr.map{|a| a.select.with_index { |e, i| i.even? }}
which gives
[["name", "contact", "state"], ["n1", "c1", "s1"], ["n2", "c2", "s2"]]
Original question:
I want to delete items from each array of arr from index position 1 and 5
We can use delete_if to achieve this. Here:
arr.map { |a| a.delete_if.with_index { |el, index| [1,5].include? index } }
# => [["name", "contact", "company", "state"], ["n1", "c1", "cp1", "s1"], ["n2", "c2", "cp2", "s2"]]
PS: the output in question is incorrect as for arrays at index 1 and 2, example is deleting element at index 4
Ruby has very nice destructuring syntax, so you can extract all your values in a one-liner:
a = 0.upto(5).to_a # => [0, 1, 2, 3, 4, 5]
x, _, y, _, z = a
x # => 0
y # => 2
z # => 4
The underscores are just placeholder for values you don't need.
It can be also performed with each_slice method.
If 0, 2, 4 values can be treated as a list with every second value omitted (_), it can be written like:
arr.map { |a| a.each_slice(2).map { |item, _| item } }

How to create an array from hash replacing all of the keys and values with integers

I have a hash:
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
and I need a function to make an array from it:
a = [[2, 3], 4, nil, 5]
Is there any simple way to do this?
First we make a range from 0 to the maximum key value (as an integer)
Then for each number we fetch the value in "a" at the corresponding key.
If the value is an array, we convert it into an array of integers
if not, convert it into an integer (unless it's false or nil).
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
a = (0..(a.keys.map(&:to_i).max)).map do |v|
x = a[v.to_s]
x.is_a?(Array) ? x.map(&:to_i) : (x && x.to_i)
end
A better version that can handle a minimum key > "0"
a = a.values_at(*Range.new(*a.keys.minmax_by(&:to_i))).map do |v|
v.is_a?(Array) ? v.map(&:to_i) : (v && v.to_i)
end
a
=> [[2, 3], 4, nil, 5]
minmax returns an array that we explode into arguments to Range.new
We then explode that range into arguments for values_at.
[*Range.new(*["2","8"])]
=> ["2", "3", "4", "5", "6", "7", "8"]
min, max = a.keys.minmax_by(&:to_i)
min.upto(max).map do |key|
a[key].respond_to?(:map) ? a[key].map(&:to_i) : a[key].to_i if a[key]
end # => [[2, 3], 4, nil, 5]
I'm not sure whether it really makes sense to have the array that you have as the final output given that now you don't know for sure which element corresponds to which question.
If we simplify the problem that you want to convert the hash keys and values to numeric and add the missing keys, then you can do the following with help of Rails' Active Support Hash extension:
# Below require needed only if code is not being used in Rails app
require "active_support/core_ext/hash"
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
p a.deep_merge(a) {|_,v,_| v.to_i rescue v.map(&:to_i)}
.transform_keys(&:to_i)
.tap { |h| h.reverse_update (h.keys.min..h.keys.max).zip([nil]).to_h }
.sort {|a,b| a <=> b} # Not really needed
.to_h # Not really needed
#=> {0=>[2, 3], 1=>4, 2=>nil, 3=>5}
You're looking for Hash.values though you won't get that nil there:
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
def deep_map(h)
h.map{|i| i.is_a?(String) ? i.to_i : deep_map(i)}
end
deep_map (a.values)
gives:
[[2, 3], 4, 5]
The results are OK with the sample input. I didn't test any other cases:
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
('0'..a.size.to_s).map do |i|
v = ([a[i]].flatten.map { |i| i && i.to_i })
v.size == 1 ? v[0] : v
end
# => [[2, 3], 4, nil, 5]
I'v split out the conversion of strings to a separate method (fixnumify) because it does not seem to be an essential element of the question:
a = {"0" => ["2", "3"], "1" => "4", "3" => "5"}
def fixnumify(obj)
case obj
when Array then obj.map(&:to_i)
when String then obj.to_i
else obj
end
end
from, to = a.keys.minmax_by(&:to_i)
#=> [0, 3]
a.values_at(*from..to).map { |v| fixnumify(v) }
#=> [[2, 3], 4, nil, 5]

Resources