Select items from arrays of an array with certain indexes - arrays

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 } }

Related

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

can't figure out array of hashes ruby

I am trying to turn an array of arrays into an array of hashes. Could someone try to explain what I am exactly doing wrong here? The first array within the array of arrays becomes the keys for the hash. I can get the method to return one hash or even three of the same hashes. But I can't get it return each different new hash within the final array.
table_data = [
["first_name", "last_name", "city", "state"],
["Elisabeth", "Gardenar", "Toledo", "OH"],
["Jamaal", "Du", "Sylvania", "OH"],
["Kathlyn", "Lavoie", "Maumee", "OH"]
]
def convert_table(table_array)
hash = {}
final_array = []
headers_array = table_array.shift
table_array.each_index do |x|
i = 0
until i == headers_array.length
hash[headers_array[i]] = table_array[x][i]
final_array << hash
i += 1
end
end
final_array
end
p convert_table(table_data)
#END GOAL
[ { "first_name" => "Elisabeth", "last_name" => "Gardenar", "city" => "Toledo", "state" => "OH" },
{ "first_name" => "Jamaal", "last_name" => "Du", "city" => "Sylvania", "state" => "OH" },
{ "first_name" => "Kathlyn", "last_name" => "Lavoie", "city" => "Maumee", "state" => "OH" }
Pair up keys (in table_data[0] and values (each other row in table_data) using zip, and map them to a hash:
table_data[1..-1].map { |values| Hash[table_data[0].zip(values)] }
EDIT: The part that doesn't work in your case is having one single hash that you keep reusing. When you do final_array << hash, it doesn't add a snapshot of the hash as it is then; it adds a reference to it. Thus, you don't have an array of three hashes, you have an array with three references to the same hash. You could avoid it by doing final_array << hash.clone to actually take a snapshot; or (much simpler) just make a new hash in each iteration of the loop (move hash = {} into the table_array.each_index loop).
As #Amadan has diagnosed your problem, I will suggest a more "Ruby-like" approach.
keys, *data = table_data
#=> [["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"],
# ["Jamaal", "Du", "Sylvania", "OH"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
keys
#=> ["first_name", "last_name", "city", "state"]
data
#=> [["Elisabeth", "Gardenar", "Toledo", "OH"],
# ["Jamaal", "Du", "Sylvania", "OH"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
[keys].product(data).map { |pair| pair.transpose.to_h }
#=> [{"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo",
# "state"=>"OH"},
# {"first_name"=>"Jamaal", "last_name"=>"Du", "city"=>"Sylvania",
# "state"=>"OH"},
# {"first_name"=>"Kathlyn", "last_name"=>"Lavoie", "city"=>"Maumee",
# "state"=>"OH"}
# ]
The steps are as follows.
a = [keys].product(data)
#=> [[["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"]
# ],
# [["first_name", "last_name", "city", "state"],
# ["Jamaal", "Du", "Sylvania", "OH"]],
# [["first_name", "last_name", "city", "state"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
# ]
The first element of a is passed to map, the block variable pair is assigned and the block calculation is performed.
pair = a.first
#=> [["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"]
# ]
b = pair.transpose
#=> [["first_name", "Elisabeth"],
# ["last_name", "Gardenar"],
# ["city", "Toledo"],
# ["state", "OH"]
# ]
g = b.to_h
#=> {"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo",
# "state"=>"OH"}
Therefore, a.first is mapped to g. The remaining calculations are similar.
Your question has been properly answered by #CarySwoveland and #Amadan.
I'd just like to add that your table basically looks like a CSV table with header.
If your table_data does come from a file, you might as well read it directly with CSV :
csv_table = "first_name,last_name,city,state
Elisabeth,Gardenar,Toledo,OH
Jamaal,Du,Sylvania,OH
Kathlyn,Lavoie,Maumee,OH"
require 'csv'
CSV.parse(csv_table, headers: true).each do |row|
p row
end
It outputs
#<CSV::Row "first_name":"Elisabeth" "last_name":"Gardenar" "city":"Toledo" "state":"OH">
#<CSV::Row "first_name":"Jamaal" "last_name":"Du" "city":"Sylvania" "state":"OH">
#<CSV::Row "first_name":"Kathlyn" "last_name":"Lavoie" "city":"Maumee" "state":"OH">
You can work CSV::Row as with an Hash.
If you really want a Hash, you can use row.to_h :
{"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo", "state"=>"OH"}
{"first_name"=>"Jamaal", "last_name"=>"Du", "city"=>"Sylvania", "state"=>"OH"}
{"first_name"=>"Kathlyn", "last_name"=>"Lavoie", "city"=>"Maumee", "state"=>"OH"}

access / print nth element of sub array, for every array

I have a multidimensional array:
#multarray = ( [ "one", "two", "three" ],
[ 4, 5, 6, ],
[ "alpha", "beta", "gamma" ]
);
I can access #multarray[0]
[
[0] [
[0] "one"
[1] "two"
[2] "three"
]
]
or even #multarray[0][0]
"one"
But how to I access say the 1st sub element of every sub array? something akin to multarray[*][0] so produce:
"one"
4
"alpha"
Thanks!
You can use map and dereference each array:
use warnings;
use strict;
use Data::Dumper;
my #multarray = (
[ "one", "two", "three" ],
[ 4, 5, 6, ],
[ "alpha", "beta", "gamma" ]
);
my #subs = map { $_->[0] } #multarray;
print Dumper(\#subs);
__END__
$VAR1 = [
'one',
4,
'alpha'
];
See also: perldsc
Using a for() loop, you can loop over the outer array, and use any of the inner elements. In this example, I've set $elem_num to 0, which is the first element. For each loop over the outer array, we take each element (which is an array reference), then, using the $elem_num variable, we print out the contents of the inner array's first element:
my $elem_num = 0;
for my $elem (#multarray){
print "$elem->[$elem_num]\n";
}

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]

return keys from array having same values in ruby

How to find out elements of array having same value_entries. As code is in ruby, looking better approach.
Input
"block_device": {
"sda": {
"size": "83886080",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdb": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdc": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
}
}
Sample Code Block:
devicesForRaid = []
deviceHolder = []
node['block_device'].each do |deviceName,deviceProperty|
deviceHolder.push(deviceName,deviceProperty['size']) #['sda'=>'83886080','sdb'=>'16384','sdc'=>'16384']
end
deviceHolder.each do | deviceName,deviceSize|
# how to get deviceName who all having same size
if(deviceSize_match_found){
devicesForRaid.push(deviceName)
}
end
Expected Output:
devicesForRaid = ['sdb','sdc']
Trial way:
using stack,
push 1st element onto stack, and comparing with rest of array element.
if match found, push that element onto stack.
Sample code block completion or better code highly appreciated.
You can do this:
input_hash[:block_device].each_with_object({}) { |(k,g),h|
h.update(g[:size]=>[k]) { |_,o,n| o+n } }
#=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
This uses the form of Hash#update (aka merge!) that employs the block:
{ |_,o,n| o+n }
to determine the values of keys that are present in both hashes being merged.
res = Hash.new { |h, k| h[k] = [] }
node['block_device'].each{|k, v| res[v[:size]]<<k}
gives:
=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
I guess you want to look through res for values with length of 2 or more
res.to_a.select{|k, v| v.size > 1}
You can do it this way (assuming block_device is a key in your input data hash):
hash = input_data[:block_device]
new_hash = Hash.new{ |h, k| h[k] = [] }
hash.each do |k, v|
new_hash[v[:size]] << k
end
p new_hash
# => {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
From this new_hash, you can extract your required data easily.
e.g. if you want to extract the elements that has a size more than 1, you can do this:
p new_hash.select { |k,v| v.length > 1 }.values.flatten
# => [:sdb, :sdc]
How about using group_by?
node[:block_device]
.group_by {|device, attributes| attributes[:size] }
.map {|size, devices| devices.size > 1 ? devices.map(&:first) : nil }
.compact
.flatten
=> [:sdb, :sdc]
I think this way is easy to understand what you are doing.

Resources