Ruby hash of arrays remove first element from array - arrays

I have an array of arrays arr = [["U6342", "2015-01-12", "Account"],other similar arrays]
then i group it by first element
arr.group_by(&:first)
=> {"U6342"=>[["U6342", "2015-01-12", "Account"],other similar arrays]]
But i want to remove first element from all of arrays and get
=> {"U6342"=>[[ "2015-01-12", "Account"],other similar arrays]]
Is it possible?

One might shift a required element inplace.
arr.group_by &:shift
Whether you do not want to modify an original array:
arr.map(&:dup).group_by &:shift

I think some of the other answerers are overthinking it:
arr = [ ["U6342", "2015-01-12", "Account"],
["U1234", "2015-02-12", "Foo"],
["U6342", "2015-03-12", "Bar"],
["U1234", "2015-04-12", "Qux"] ]
arr.group_by(&:shift)
# => { "U6342" => [ [ "2015-01-12", "Account" ],
# [ "2015-03-12", "Bar" ] ],
# "U1234" => [ [ "2015-02-12", "Foo" ],
# [ "2015-04-12", "Qux" ] ]
# }
Array#shift removes the first element from the array and returns it. arr.group_by(&:shift) removes the first element from each element in arr and groups by it.

You can use this code
arr = [["U6342", "2015-01-12", "Account"], ["U6343", "2015-02-12", "Bank"]]
Hash[arr.map{|ar| [ar.first, ar[1..-1]]}]
# => {"U6342"=>["2015-01-12", "Account"], "U6343"=>["2015-02-12", "Bank"]}
or with "modify" the orginal arr variable
Hash[arr.map{|ar| [ar.shift, ar]}]
I hope this helps

You don't have to use group_by to group-by:
arr = [["dog", "named", "Bert"],
["cat", "named", "Boots"],
["dog", "named", "Hank"],
["pig", "named", "Porky"],
["cat", "named", "Tiger"]]
arr.each_with_object(Hash.new { |h,k| h[k]=[] }) { |(f,*a),h| h[f] << a }
# => {"dog"=>[["named", "Bert"], ["named", "Hank"]],
# "cat"=>[["named", "Boots"], ["named", "Tiger"]],
# "pig"=>[["named", "Porky"]]}
Note this doesn't mutate arr.

Related

How to merge two arrays of hashes

I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }

How do I strip off nil or empty elements from the end of my array?

Using Ruby 2.4. I have an arry with a numbegr of string elements. I would like to strip off elemetns that are either nil or empty from teh end of the array so I have
row_data.pop until row_data.last || row_data.empty?
But this is only stripping the nil elements off the end of the array. How do I adjust the above so that it also includes thte empty elements?
row_data = [ "", "hi", "", nil ] # => ["", "hi", "", nil]
row_data.pop while !row_data.empty? && (!row_data.last || row_data.last.empty?) # => nil
row_data # => ["", "hi"]
Very similar to your solution but instead we flip from while to unless cause instead of looking for the PRESENCE of an object we are looking for nil explicitly. Then we check if that string is empty. You could check if the last element has the method empty? if you expect other object types to be present in your array. (But I assume you are parsing CSV so it's most likely a moot point)
If you have active_support or are inside of rails this gets easier.
# Ignore the require if in a rails project
require 'active_support/core_ext/object/blank' # => true
row_data = [ "", "hi", "", nil ] # => ["", "hi", "", nil]
row_data.pop while !row_data.empty? && row_data.last.blank? # => nil
row_data # => ["", "hi"]
Below would remove nil or empty string from ANY position of an array
row_data = [ "hi", "", nil ]
row_data.compact.reject(&:empty?) # => ["hi"]
Given an array of strings, and assuming no active_support this will remove any nil value compact and then reject any string that is empty reject(&:empty?) This does create a new array, and does not make any changes in place. If you need to mutate the original object use the following code
row_data = [ "hi", "", nil ]
row_data.compact!.reject!(&:empty?) # => ["hi"]
row_data # => ["hi"]
If you have active support this could be reduced to
row_data = [ "hi", "", nil ]
row_data.reject(&:blank?) # => ["hi"]
If the array contains only strings and nils, one could write:
row_data = ["cat", nil, '', nil]
row_data.tap { |rd| pop while rd.last.to_s.empty? }
#=> ["cat"]
Note nil.to_s #=> ''.
If array may contain other objects as well, one may write:
row_data = ['', 1, :cat, {}, [], nil]
row_data.tap { |rd| rd.pop while (rd.last.nil? ||
(row_data.last.respond_to?(:empty?) && row_data.last.empty?)) }
#=> ["", 1, :cat]

Converting array to json in Ruby (Puppet, facter)

I'm writing a fact for Puppet in Ruby. I have an array
array = [[["User", "Username"], ["Date", "16.12.2014"]], [["User1", "Username1"], ["Date1", "17.12.2014"]]]
I want to convert it to json. I tried to convert it to hash first, but doing like this in Linux
array.each do |userarr|
winusers = Hash[userarr.map! { |pair| [pair[0], pair[1]] } ]
end
I get only the this one [["User1", "Username1"], ["Date1", "17.12.2014"]] pair converted. Doing like this:
array.each do |userarr|
winusers = Hash[userarr.map! { |pair| [pair[0], pair[1]] } ]
winusersa << winusers
end
I get an array of hashes. Coverting it to json winusersa.to_json on Linux I get an array of json format text, on Puppet (facter in fact) I get only the first pair converted. Why in Puppet fact it doesn't work? How to convert that array to get all pairs well formated?
Try this one
array.flatten(1).each_slice(2).map(&:to_h)
=> [{"User"=>"Username", "Date"=>"16.12.2014"}, {"User1"=>"Username1", "Date1"=>"17.12.2014"}]
And then, as an hash, you can easily call to_json
You've already got your Array in the form that the ruby Hash#[] method can consume. I think all you need is this:
% pry
[1] pry(main)> require 'json'
[2] pry(main)> a = [[["User", "Username"], ["Date", "16.12.2014"]], [["User1", "Username1"], ["Date1", "17.12.2014"]]]
[3] pry(main)> puts JSON.pretty_generate(a.map { |e| Hash[e] })
[
{
"User": "Username",
"Date": "16.12.2014"
},
{
"User1": "Username1",
"Date1": "17.12.2014"
}
]
Require 'facter' #if you have facter as gem to test locally require 'json'
array = [
[
["User", "Username"],
["Date", "16.12.2014"]
],
[
["User1", "Username1"],
["Date1", "17.12.2014"]
]
]
put JSON.pretty_generate(JSON.parse(array.to_json))

ruby sorting hashes inside array

I have an array of hashes that I would like to sort based on the :reference values. But some of the elements within the array do not have a :reference key and therefore cannot be sorted. Is there a way to ignore this field and just sort the hash elements that contain this key?
I've tried the following approach but I'm getting an argument error
ArgumentError: comparison of NilClass with String failed
sort_by at org/jruby/RubyEnumerable.java:503
arr1 = [{:reference=> "F123",
:name=> "test4"
},
{
:reference=> "ZA4",
:name=> "test3"
},
{
:reference=> "A43",
:name=> "test2"
},
{
:name=> "test1"
},
{
:name=> "homework1"
}]
arr1 = arr1.sort_by { |hash| hash[:reference] }
puts arr1
The correct output should look like this :
=> arr1= [
{:reference=>"A43", :name=>"test2"},
{:reference=>"F123", :name=>"test4"},
{:reference=>"ZA4", :name=>"test3"},
{:name=> "test1"},
{:name=> "homework1"}
]
You can only sort on values that can be compared, so if you've got values of different types it's best to convert them to the same type first. A simple work-around is to convert to string:
arr1.sort_by { |hash| hash[:reference].to_s }
You can also assign a default:
arr1.sort_by { |hash| hash[:reference] || '' }
Edit: If you want the nil values sorted last:
arr1.sort_by { |hash| [ hash[:reference] ? 0 : 1, hash[:reference].to_s ] }
If you don't mind temporary variables, you could split the array into two arrays, one containing hashes with :reference and the other those without:
with_ref, without_ref = arr1.partition { |h| h[:reference] }
Then sort the first one:
with_ref.sort_by! { |h| h[:reference] }
And finally concatenate both arrays:
arr1 = with_ref + without_ref

How do I iterate through a reference to an array of hashes in Perl?

I have a reference to an array of hases that I pass to a subroutine in my perl script
This is the code:
sub mySub {
(my $resultref) = #_;
my #list = #$resultref;
print Dumper(#list);
foreach my $result (#list) {
print Dumper($result);
}
}
And this is the output:
$VAR1 = [
{
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
},
{
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
},
];
$VAR1 = [
{
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
},
{
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
},
];
The loop is putting the whole array into the $result variable. I have tried dereferencing it as #$result[0] with no success.
How do I loop those hashes individually?
Thanks!
The arguments to Data::Dumper's Dumper function should be references. E.g.:
use Data::Dumper;
my #array = ([1,2,3], [11,22,33]); # Two-dimensional array
print Dumper #array; # print array
print Dumper \#array; # print reference to array
The output:
$VAR1 = [
1,
2,
3
];
$VAR2 = [
11,
22,
33
];
$VAR1 = [
[
1,
2,
3
],
[
11,
22,
33
]
];
The second print gives us the entire structure in one variable. When you print the array directly, it expands into all its elements, so...
print Dumper #array;
Is equivalent to:
print Dumper $array[0], $array[1], ..., $array[$#array];
So, in your case, just do:
sub mySub {
my ($resultref) = #_;
print Dumper $resultref;
}
Accessing the inner variables:
Just take a look at Data::Dumper's output:
$VAR1 = [ # bracket denotes start of an array ref
{ # curly brackets = hash ref
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
}, # hash ref ends, comma = new array element begins
{ # new hash ref
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
}, # end of hash
]; # end of array
Important to note here is that all elements of an array, and all the values of a hash are scalars. Therefore, all hashes and arrays can easily be broken up into a list of scalars.
for my $aref (#$resultref) { # starting array ref
for my $aref2 (#$aref) { # second level array ref
for my $href (#$aref2) # here begins the hash
local $\ = "\n"; # add newline to print for simplicity
print $href->{portName}; # printing a scalar
print for #{$href_>{ips}}; # printing an array ref w post-script loop
print $href->{switchIp};
print for #{$href->{macs}};
}
}
}
Note the use of the arrow operator to dereference a reference. If you have a hash or array you would do $array[0] or $hash{$key}, but by using a reference, you "point" to the address contained in the reference instead: $array->[0] or $hash->{$key}.
The parameter passed to mySub is a reference to an array of arrayrefs. To iterate over the nested arrays you could do:
sub mySub {
my ($resultref) = #_;
for my $result (#$resultref) {
my #list = #$result; # array of hashrefs
...
}
}
I have a reference to an array of hases
No, you've passed in a reference to an array of references to an array of hashes.
If you remove that outer level of indirection then your code works as desired.

Resources