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"]}]
Related
What does this bug refer to? I have pasted the code below. Kindly have a look. Can anyone let me know what's wrong with the boundary value here? Thanks in advance
db.match_list.aggregate(
[
{
$bucket: {
groupBy: "$competition_id",
boundaries: ["9dn1m1gh41emoep","9dn1m1ghew6moep", "d23xmvkh4g8qg8n","gy0or5jhj6qwzv3"],
default: "Other",
output: {
"data" : {
$push: {
"season_id": "$season_id",
"status_id": "$status_id",
"venue_id": "$venue_id",
"referee_id": "$referee_id",
"neutral":"$neutral",
"note": "$note",
"home_scores":"$home_scores",
"away_scores": "$away_scores",
}
}
}
}
},
{
$sort : { competition_id : 1 }
},
])
mongodb laravel query using raw. Not sure what's going wrong here.new_array vale also has been mentioned
$contents = $query->orderby('competition_id')->pluck('competition_id')->toArray();
$contents = array_unique($contents);
$new_array = array_values($contents);
$data = $query->raw(function ($collection) use ($new_array) {
return $collection->aggregate([
[
'$bucket' => [
'groupBy' => '$competition_id',
'boundaries' => $new_array,
'default' => 'zzzzzzzzzzzzzzzzzzzzzzzzz',
'output' => [
"data" => [
'$push' => [
"id" => '$id',
"season_id" => '$season_id',
"status_id" => '$status_id',
"venue_id" => '$venue_id',
"referee_id" => '$referee_id',
"neutral" => '$neutral',
"note" => '$note',
"home_scores" => '$home_scores',
]
]
]
]
]
]);
});
You have entered Other in the default parameter of the $bucket parameter which
is in between the min-max boundaries you have provided.
I would suggest you try a value of the greater or lesser string than to that provided in the
boundaries array or better enter a different datatype value such as int of 1.
db.match_list.aggregate( [
{
$bucket: {
groupBy: "$competition_id",
boundaries: ["9dn1m1gh41emoep","9dn1m1ghew6moep", "d23xmvkh4g8qg8n","gy0or5jhj6qwzv3"],
default: 1, // Or `zzzzzzzzzzzzzzzzzzzzzzzzz` (any greater or lesser value than provided in `boundaries`
output: {
"data" :
{
$push: {
"season_id": "$season_id",
"status_id": "$status_id",
"venue_id": "$venue_id",
"referee_id": "$referee_id",
"neutral":"$neutral",
"note": "$note",
"home_scores":"$home_scores",
"away_scores": "$away_scores",
}
}
}
}
},
{ $sort : { competition_id : 1 } },
] )
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
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.
I can't seem to wrap my head around the AWS Ruby SDK documentation for DynamoDB (or more specifically the concepts of the DynamoDB data model).
Specifically I've been reading: http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html#!AWS/DynamoDB.html
Note: I have read through the Data Model documentation as well and it's still not sinking in; I'm hoping a proper example in Ruby with clear up my confusion
In the following code snippet, I create a table called "my_books" which has a primary_key called "item_id" and it's a Hash key (not a Hash/Range combination)...
dyn = AWS::DynamoDB::Client::V20120810.new
# => #<AWS::DynamoDB::Client::V20120810>
dyn.create_table({
:attribute_definitions => [
{ :attribute_name => "item_id", :attribute_type => "N" }
],
:table_name => "my_books",
:key_schema => [
{ :attribute_name => "item_id", :key_type => "HASH" },
],
:provisioned_throughput => {
:read_capacity_units => 10,
:write_capacity_units => 10
}
})
# => {:table_description=>{:attribute_definitions=>[{:attribute_name=>"item_id", :attribute_type=>"N"}], :table_name=>"my_books", :key_schema=>[{:attribute_name=>"item_id", :key_type=>"HASH"}], :table_status=>"ACTIVE", :creation_date_time=>2014-11-24 16:59:47 +0000, :provisioned_throughput=>{:number_of_decreases_today=>0, :read_capacity_units=>10, :write_capacity_units=>10}, :table_size_bytes=>0, :item_count=>0}}
dyn.list_tables
# => {:table_names=>["my_books"]}
dyn.scan :table_name => "my_books"
# => {:member=>[], :count=>0, :scanned_count=>0}
I then try and populate the table with a new item. My understanding is that I should specify the numerical value for item_id (which is the primary key) and then I could specify other attributes for the new item/record/document I'm adding to the table...
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => 1,
"item_title" => "My Book Title",
"item_released" => false
}
)
But that last command returns the following error:
expected hash value for value at key item_id of option item
So although I don't quite understand what the hash will be made of, I try doing that:
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => 1 },
"item_title" => "My Book Title",
"item_released" => false
}
)
But this now returns the following error...
expected string value for key N of value at key item_id of option item
I've tried different variations, but can't seem to figure out how this works?
EDIT/UPDATE: as suggested by Uri Agassi - I changed the value from 1 to "1". I'm not really sure why this has to be quoted as I've defined the type to be a number and not a string, but OK let's just accept this and move on.
I've finally figured out most of what I needed to understand the data model of DynamoDB and using the Ruby SDK.
Below is my example code, which hopefully will help someone else, and I've got a fully fleshed out example here: https://gist.github.com/Integralist/9f9f2215e001b15ac492#file-3-dynamodb-irb-session-rb
# https://github.com/BBC-News/alephant-harness can automate the below set-up when using Spurious
# API Documentation http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html
# Ruby SDK API Documentation http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html#!AWS/DynamoDB/Client/V20120810.html
require "aws-sdk"
require "dotenv"
require "spurious/ruby/awssdk/helper"
Spurious::Ruby::Awssdk::Helper.configure
# => <AWS::Core::Configuration>
Dotenv.load(
File.join(
File.dirname(__FILE__), "config", "development", "env.yaml"
)
)
# => {"AWS_REGION"=>"eu-west-1", "AWS_ACCESS_KEY_ID"=>"development_access", "AWS_SECRET_ACCESS_KEY"=>"development_secret", "DYNAMO_LU"=>"development_lookup", "DYNAMO_SQ"=>"development_sequence", "SQS_QUEUE"=>"development_queue", "S3_BUCKET"=>"development_bucket"}
dyn = AWS::DynamoDB::Client.new :api_version => "2012-08-10"
dyn = AWS::DynamoDB::Client::V20120810.new
# => #<AWS::DynamoDB::Client::V20120810>
dyn.create_table({
# This section requires us to define our primary key
# Which will be called "item_id" and it must be a numerical value
:attribute_definitions => [
{ :attribute_name => "item_id", :attribute_type => "N" }
],
:table_name => "my_books",
# The primary key will be a simple Hash key (not a Hash/Range which requires both key types to be provided)
# The attributes defined above must be included in the :key_schema Array
:key_schema => [
{ :attribute_name => "item_id", :key_type => "HASH" }
],
:provisioned_throughput => {
:read_capacity_units => 10,
:write_capacity_units => 10
}
})
# => {:table_description=>{:attribute_definitions=>[{:attribute_name=>"item_id", :attribute_type=>"N"}], :table_name=>"my_books", :key_schema=>[{:attribute_name=>"item_id", :key_type=>"HASH"}], :table_status=>"ACTIVE", :creation_date_time=>2014-11-24 16:59:47 +0000, :provisioned_throughput=>{:number_of_decreases_today=>0, :read_capacity_units=>10, :write_capacity_units=>10}, :table_size_bytes=>0, :item_count=>0}}
dyn.list_tables
# => {:table_names=>["my_books"]}
dyn.scan :table_name => "my_books"
# => {:member=>[], :count=>0, :scanned_count=>0}
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => "1" }, # oddly this needs to be a String and not a strict Integer?
"item_title" => { "S" => "My Book Title"},
"item_released" => { "B" => "false" }
}
)
# Note: if you use an "item_id" that already exists, then the item will be updated.
# Unless you use the "expected" conditional feature
dyn.put_item(
:table_name => "my_books",
:item => {
"item_id" => { "N" => "1" }, # oddly this needs to be a String and not a strict Integer?
"item_title" => { "S" => "My Book Title"},
"item_released" => { "B" => "false" }
},
# The :expected key specifies the conditions of our "put" operation.
# If "item_id" isn't NULL (i.e. it exists) then our condition has failed.
# This means we only write the value when the key "item_id" hasn't been set.
:expected => {
"item_id" => { :comparison_operator => "NULL" }
}
)
# AWS::DynamoDB::Errors::ConditionalCheckFailedException: The conditional check failed
dyn.scan :table_name => "my_books"
# => {:member=>[{"item_id"=>{:n=>"1"}, "item_title"=>{:s=>"My Book Title"}, "item_released"=>{:b=>"false"}}], :count=>1, :scanned_count=>1}
dyn.query :table_name => "my_books", :consistent_read => true, :key_conditions => {
"item_id" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "n" => "1" }]
},
"item_title" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "s" => "My Book Title" }]
}
}
# => {:member=>[{"item_id"=>{:n=>"1"}, "item_title"=>{:s=>"My Book Title"}, "item_released"=>{:b=>"false"}}], :count=>1, :scanned_count=>1}
dyn.query :table_name => "my_books",
:consistent_read => true,
:select => "SPECIFIC_ATTRIBUTES",
:attributes_to_get => ["item_title"],
:key_conditions => {
"item_id" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "n" => "1" }]
},
"item_title" => {
:comparison_operator => "EQ",
:attribute_value_list => [{ "s" => "My Book Title" }]
}
}
# => {:member=>[{"item_title"=>{:s=>"My Book Title"}}], :count=>1, :scanned_count=>1}
dyn.delete_item(
:table_name => "my_books",
:key => {
"item_id" => { "n" => "1" }
}
)
# => {:member=>[], :count=>0, :scanned_count=>0}
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}]