Ruby: Rename First Key of Array - ruby

I have an array like so:
[
[0] {
"Success" => true
},
[1] {
"Success" => true
},
[2] {
"Success" => true
},
[3] {
"Success" => true
}
]
Each number above has a much larger multidimensional array associated with it, but that is not my concern currently. I am wanting to rename the numbers [0..3] with new key values.
So it would look like this
[
["pop"] {
"Success" => true
},
["rock"] {
"Success" => true
},
["country"] {
"Success" => true
},
["soul/r&b"] {
"Success" => true
}
]
Thanks in advance!

if your input is really JSON, you need to first parse the JSON and convert it into a Ruby data structure:
require 'active_support'
json = "[[0,{\"Success\":true}],[1,{\"Success\":true}],[2,{\"Success\":true}],[3,{\"Success\":true}]]"
array = ActiveSupport::JSON.decode( json )
=> [ [0, { "Success" => true }], [1, { "Success" => true }], [2, { "Success" => true }], [3, { "Success" => true }] ]
Now your input is a Ruby Array...
I assume you have the new keys stored in an array somewhere like this:
new_keys = %w{pop rock country soul/r&b}
=> ["pop", "rock", "country", "soul/r&b"]
Next step is to replace the first element of each of the sub-arrays in the JSON array with the new key:
result = Array.new
array.each do |value|
i, rest = value
result << [ new_keys[i], rest ]
end
json = result.to_json
=> "[[\"pop\",{\"Success\":true}],
[\"rock\",{\"Success\":true}],
[\"country\",{\"Success\":true}],
[\"soul/r&b\",{\"Success\":true}]]"

There are two array like data structures in Ruby: the array which stores ordered data accessible by an integer index and the hash, which is a hashmap storing data accessible by some key object (e.g. a string). Although they are both accessed similarly, they are different classes.
From your code (which is not valid ruby btw.) I'm guessing you have the following data structure:
array = [
{ "Success" => true },
{ "Success" => true },
{ "Success" => true },
{ "Success" => true }
]
It can be transferred usiong the following code:
keys = ["pop", "rock", "country", "soul/r&b"]
result = {}
keys.each_with_index {|key, i| result[index] = array[i]}
This results in the following data structure. It is a hash containing the elements of the original array (which are hashes by themselves) as values.
{
"pop" => { "Success" => true },
"rock" => { "Success" => true },
"country" => { "Success" => true },
"soul/r&b" => { "Success" => true }
}

styles = {0: "pop", 1: "rock", 2: "country", 3: "shoul/r&b"}
info2 = Hash[info.map { |index, h| [[styles[index]], h}]

Related

Laravel : How to hide data depend on some condition?

If I have this model :
$result =
[
{
"date":"1/1/2021",
"actions":[
{
"id":1,
"title:"test",
"sub_actions":[
{
"id":1
}
{
"id":2
}
]
}
],
"date":"2/1/2021",
"actions":[
{
"id":2,
"title:"test2",
"sub_actions":[
{
"id":1
}
{
"id":2
}
]
}
]
}
]
I want if in some of these dates the action count be zero for some reason to not display both (date and actions) in the list?
something like this :
if($result->actions->count() == 0)
I'm not sure if that's the output of a query, but for the general case, if you had a json looking like
[
{
"date": "1/1/2021",
"actions": [
{
"id": 1,
"title": "test",
"sub_actions": [
{
"id": 1
},
{
"id": 2
}
]
}
]
},
{
"date": "2/1/2021",
"actions": [
]
}
]
and you decoded into a variable like so
$resultArray = json_decode(
'[{"date":"1/1/2021","actions":[{"id":1,"title":"test","sub_actions":[{"id":1},{"id":2}]}]},{"date":"2/1/2021","actions":[]}]'
);
you could turn that to a collection and then apply filter on it
$recordsWithActions = collect($resultArray)->filter(fn($row)=> count($row->actions))->all();
or
$recordsWithActions = collect($resultArray)->filter(
function($row) {
// it would be $row['actions'] if you did json_decode(...,true)
return count($row->actions)>0;
}
)->all();
I left you the example using arrays instead of objects at Laravel Playground
Edit: Filtering on nested properties
Let's leave the json encoding and decoding aside for a minute. You have an array of soccer fixtures, each of which can have zero or more matches, and each match in turn can have zero or more predects (predictions?).
You want to display only matches with more than zero predects. This will prune matches without said property and if the pruned matches array is empty, then the parent fixture should be pruned from the final result as well.
The Collecttion::filter method evaluates every item for the truthyness of a condition. count($match['predect']) is zero, and zero is a falsy value, same as false, NULL, an empty string or an empty array. A non empty array would satisfy either the truthyness test by itself, or the assertion that count($array) or count($array)>0.
Checking your example, you did the equivalent of (I won't use arrow functions for the sake of clarity):
// show me the items for which the next condition is truthy
$myresults = collect($fixtures)->filter(function ($fixture) {
// matches is a collection of items for which is true that
$matches = collect($fixture['matches'])
->filter(function ($match) {
// their predect property is not empty
return count($match['predect']);
});
// this is the truthyness test to check
return $matches;
})->all();
$matches is a collection, which is not falsy no matter it doesn't have items. However, if you used, instead:
$matches = collect($fixture['matches'])
->filter(function ($match) {
// their predect property is not empty
return count($match['predect']);
})->all();
and $matches was an empty array, it would "bubble up" effectively pruning the parent fixture. If any fixture has at least one match passing the truthyness test, then it will show up in the final result. It's matches key would not be filtered, since you aren't altering their contents on the basis of the nested filter.
If you want to redeclare the matches of each fixture to prune the ones without predect, you would use the Collection::map method like so:
$fixtures_with_passing_matches = collect($fixtures)
->map(function($fixture) {
$fixture['matches'] = collect($fixture['matches'])
->filter(function ($match) {
return count($match['predect']);
})->all();
return $fixture;
})->all();
you can chain your filter method after the mapping, therefore pruning the fixtures that, after redeclaring their matches, have none left
$fixtures_with_passing_matches = collect($fixtures)
->map(function($fixture) {
$fixture['matches'] = collect($fixture['matches'])
->filter(function ($match) {
return count($match['predect']);
})->all();
return $fixture;
})->filter(function($fixture) {
return count($fixture['matches'])>0;
})->all();
So let's look at the following example array:
$fixtures = [
[
"fixture_id" => 1,
"matches" => [
[
"match_id" => 1,
"home" => "Turkey",
"away" => "Italy",
"predect" => [
[
"h_predect" => 1,
"a_predect" => 1
]
]
], [
"match_id" => 3,
"home" => "Denmark",
"away" => "Finland",
"predect" => []
]
]
], [
"fixture_id" => 2,
"matches" => [
[
"match_id" => 4,
"home" => "France",
"away" => "Norway",
"predect" => []
]
]
], [
"fixture_id" => 3,
"matches" => []
]
];
The last fixture doesn't have matches, so it will be filtered out
The second fixture has matches, but none of them has predect, therefore are filtered out, leaving the parent fixture without matchesm therefore filtering it out as well
The first fixture has matches, of which one doesn't have predect so it's filtered out. This leaved the parent fixture with just one match
The final array is
$fixtures = [
[
"fixture_id" => 1,
"matches" => [
[
"match_id" => 1,
"home" => "Turkey",
"away" => "Italy",
"predect" => [
[
"h_predect" => 1,
"a_predect" => 1
]
]
]
]
]
];
See the working example in this Laravel Playground

"fold" an array of hashes, recursion required?

This is a simple example of my starting point :
[
{ 'drink' => [ 'hard', 'soft' ] },
{ 'hard' => [ 'beer', 'wine' ] },
{ 'soft' => [ 'water', 'orange_juice' ] },
{ 'food' => [ 'fruit', 'veg', 'meat' ] },
{ 'fruit' => [ 'apples', 'bananas', 'pears' ] }
{ 'veg' => [ 'cabbages', 'potatoes', 'carrots' ] },
{ 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] }
]
I need to iterate over this, and "fold in" where the key to the values in a hash, is the value in another hash, so I end up with :
[
{ 'drink' => [ { 'hard' => [ 'beer', 'wine' ] }, { 'soft' => [ 'water', 'orange_juice' ] } ] },
{ 'food' => [ { 'fruit' => [ 'apples', 'bananas', 'pears' ] },
{ 'veg' => [ 'cabbages', { 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] }, 'carrots' ] }
]
The nesting could be any level of depth, but the result will always end up being an array of hashes at its outermost ( if that makes sense ), with one or more hash in that array.
Edit: Or as suggested in comment below, the result structure could just be a hash, with 'drink', and 'food' keys pointing to the respective nested values in that hash. ( The items are not necessarily unique, two or more keys could point to the same value/s ).
I can iterate over an array and/or hash with ".each" etc., and apply logic and build the result structure iteratively, but I'm struggling with what I think is the recursive nature of this problem, in terms of making sure that everything that can be nested gets nested to any depth ( if that makes sense ).
I'm not looking for a complete solution, but just a mental nudge in the right direction as to how to go about creating a solution for this, because at the moment I keep venturing down dead-ends..
Edited: this updated solution is recursive as well, as the previous. I added some case in the input to show how can nest at a deepr level (beer for example)
def rec_map(to_map, collection, mapped)
if to_map.empty?
mapped
else
case to_map[0]
when String
current_key = to_map[0]
mapped_key = collection.find(-> {current_key}) { |item_hash| item_hash.has_key?(current_key) }
if current_key == mapped_key
rec_map(to_map[1..-1], collection, mapped << current_key)
else
with_mapped_values = Hash.new
mapped_values = rec_map(mapped_key[current_key], collection, [])
with_mapped_values[current_key] = mapped_values
rec_map(to_map[1..-1], collection, mapped << with_mapped_values)
end
when Hash
key = to_map[0].keys.first
values = to_map[0].values.flatten
with_mapped_values = Hash.new
mapped_values = rec_map(values, collection, [])
with_mapped_values[key] = mapped_values
rec_map(to_map[1..-1], collection, mapped << with_mapped_values)
else
raise "Not supported given #{to_map[0].class} expected String or Hash"
end
end
end
def key_has_match(key, collection)
if collection.empty?
return false
else
case collection[0]
when String
key == collection[0] || key_has_match(key, collection[1..-1])
when Hash
key_has_match(key, collection[0].values.flatten) ||
key_has_match(key, collection[1..-1])
else
raise "Not supported given #{collection[0].class} expected String or Hash"
end
end
end
def rec_fold(to_fold, collection, folded)
if to_fold.empty?
folded
else
key = to_fold[0].keys.first
if key_has_match(key, collection)
rec_fold(to_fold[1..-1], collection, folded)
else
with_mapped_values = Hash.new
values = to_fold[0][key]
with_mapped_values[key] = rec_map(values, collection, [])
rec_fold(to_fold[1..-1], collection, folded << with_mapped_values)
end
end
end
if $PROGRAM_NAME == __FILE__
original = [
{ 'drink' => [ 'hard', 'soft' ] },
{ 'hard' => [ 'beer', 'wine' ] },
{ 'beer' => [ 'lager', 'stout', 'ale' ] },
{ 'lager' => [ 'draught', 'bottle' ] },
{ 'draught' => [ 'pint', 'half' ] },
{ 'soft' => [ {'water' => ['tap', 'bottle']}, 'orange_juice' ] },
{ 'food' => [ 'fruit', 'veg', 'meat' ] },
{ 'fruit' => [ 'apples', 'bananas', {'pears'=> ['anjou', 'bartlett', 'concorde']} ] },
{ 'veg' => [ 'cabbages', 'potatoes', 'carrots' ] },
{ 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] },
{ 'sweet' => [ 'red', 'brown', 'white' ] },
{'bottle' => ['green', 'red', 'blue']},
{'bartlett' => ['Woven baskets', 'Bags', 'Modified atmosphere']}
]
pp rec_fold(original, original, [])
end
# [{"drink"=>
# [{"hard"=>
# [{"beer"=>
# [{"lager"=>
# [{"draught"=>["pint", "half"]},
# {"bottle"=>["green", "red", "blue"]}]},
# "stout",
# "ale"]},
# "wine"]},
# {"soft"=>
# [{"water"=>["tap", {"bottle"=>["green", "red", "blue"]}]},
# "orange_juice"]}]},
# {"food"=>
# [{"fruit"=>
# ["apples",
# "bananas",
# {"pears"=>
# ["anjou",
# {"bartlett"=>["Woven baskets", "Bags", "Modified atmosphere"]},
# "concorde"]}]},
# {"veg"=>
# ["cabbages",
# {"potatoes"=>
# ["king_edward", {"sweet"=>["red", "brown", "white"]}, "russet"]},
# "carrots"]},
# "meat"]}]

Using delete_if to delete single value of hash not the entire hash RUBY

I have a array
array_hash = [
{
"array_value" => 1,
"other_values" => "whatever",
"inner_value" => [
{"iwantthis" => "forFirst"},
{"iwantthis2" => "forFirst2"},
{"iwantthis3" => "forFirst3"}
]
},
{
"array_value" => 2,
"other_values" => "whatever2",
"inner_value" => [
{"iwantthis" => "forSecond"},
{"iwantthis2" => "forSecond2"},
{"iwantthis3" => "forSecond3"}
]
},
]
I want to delete inner value or pop it out (i prefer pop).
So my output should be this:
array_hash = [
{
"array_value" => 1,
"other_values" => "whatever"
},
{
"array_value" => 2,
"other_values" => "whatever2"
},
]
I tried delete_if
array_hash.delete_if{|a| a['inner_value'] }
But it deletes all data in the array. Is there any solution?
try this:
array_hash.map{ |a| {'array_value' => a['array_value'], 'other_values' => a['other_values'] }}
You are telling ruby to delete all hashes that have a key called inner_value. That explains why the array remains empty.
What you should probably do instead is:
array_hash.each { |x| x.delete 'inner_value' }
which means: for each hash in this array, erase the inner_value key.
Well I found it,
array_hash_popped = array_hash.map{ |a| a.delete('inner_value') }
This will pop (because I want pop as stated in the question) the inner_value out and hence inner value will be reduced/deleted from array_hash.

Select a value from a nested hash with unknown keys

I have a hash that looks something like this:
hash = { "data" => {
"Aatrox" => {
"id" => "Aatrox",
"key" => "266",
"name" => "Aatrox"
},
"Ahri" => {
"id" => "Ahri",
"key" => "123",
"name" => "Ahri"
},
"Another name" => {
"id" => "Another name",
"key" => "12",
"name" => "Another name"
},
}
}
I'm trying to get the value from "id" that matches a given key:
def get_champion_name_from_id(key)
filtered = #champion_data["data"].select do | key, champ_data |
Integer(champ_data["key"]) == key
end
end
I'm using select to get the items that match the block, however, the return value is another hash that looks like this:
{
"Aatrox": {
"id" => "Aatrox",
"key" => "266",
"name" => "Aatrox"
}
}
How can I avoid this and get just the last nested hash?
If the key passed was 266, I want to get this hash:
{
"id" => "Aatrox",
"key" => "266",
"name" => "Aatrox"
}
This hash is the result of a parsed JSON file, so there's no way I can do filtered["Aatrox"] to get a given value.
Hash#values returns values only (without keys). And by using Enumerable#find, you will get the first matched item instead of an array that contains a single item.
#champion_data['data'].values.find { |champ_data|
champ_data['key'] == '266'
}
# => {"id"=>"Aatrox", "key"=>"266", "name"=>"Aatrox"}
def get_champion_name_from_id(key)
key = key.to_s
#champion_data['data'].values.find { |champ_data|
champ_data['key'] == key
}
end
You can do it with the select method also:
#champion_data["data"].select do |key, val|
#champion_data["data"][key] if #champion_data["data"][key]["key"] == "266"
end

Compare Multidimensional Hash

I have a huge hash (JSON) that I want to compare to a "master key" by deleting the values that are dissimilar then totaling a value set.
I thought it would be a good way to handle test scoring with complex scoring criterion.
Advice about how to do this? Any gems exist to make my life easier?
{
"A" => 10,
"B" => 7,
etc
....
The hash is constructed like test[answer] => test[point_value] and the question key/value is the answer/point value.
So if I want to compare to a master_key and remove dissimilar items (not remove similar ones like arr1-arr2 does...then total the values, what would be best?
After converting the hashes to ruby hashes i'd do something like this
tester = { :"first" => { :"0" => { :"0" => { :"B" => 10 }, :"1" => { :"B" => 7 }, :"2" => { :"B" => 5 } } }}
master = { :"first" => { :"0" => { :"0" => { :"A" => 10 }, :"1" => { :"B" => 7 }, :"2" => { :"B" => 5 } } }}
tester.reduce(0) do |score, (test, section)|
section.each do |group, questions|
questions.each do |question, answer|
if answer.keys.first == master[test][group][question].keys.first
score += answer.values.first
end
end
end
score
end

Resources