I am looping the database to get a list of countries with continents.
array: [
0 => array: [
"country" => "BE"
"continent" => "EU"
1 => array: [
"country" => "BG"
"continent" => "EU"
]
...
]
From that result, I want to create an array that shows all the continents with the countries inside.
array:[
0 => array: [
"continent" => "EU"
"countries" => [
"country" => "BE"
"country" => "BG"
]
]
Suppose this is your array
$arr = [
[
"country" => "BE",
"continent" => "EU",
],
[
"country" => "BG",
"continent" => "EU",
]
];
Then this returns what you expected.
collect($arr)->groupBy('continent')->mapWithKeys(function ($group, $continent) {
return [
'continent' => $continent,
'countries' => $group->pluck('country')->toArray()
];
});
If you need just to group countries by continent, simple way to achieve this by mapToGroups() method for collections
$input = [
['country' => 'BE', 'continent' => 'EU'],
['country' => 'BG', 'continent' => 'EU'],
['country' => 'BU', 'continent' => 'AF'],
['country' => 'BY', 'continent' => 'EU'],
];
$grouped = collect($input)
->mapToGroups(fn ($country) => [$country['continent'] => $country['country']])
->toArray(); // if you need array at the end
This will be resulted in
You will need to some how group your results by the continent. One way to do that is to loop over the initial collection and build a new array keyed by the continent.
However, seeing that you are using Laravel just group the collection by the continent.
$countries = [
['country' => 'A', 'continent' => 'aa'],
['country' => 'B', 'continent' => 'bb'],
['country' => 'C', 'continent' => 'aa'],
['country' => 'D', 'continent' => 'aa'],
['country' => 'E', 'continent' => 'aa'],
['country' => 'F', 'continent' => 'bb'],
];
// Because my data is an array I just turn that into a collection.
// But an Eloquent query builder ->get would already return a collection.
$continents = collect($countries)->groupBy('continent');
foreach ($continents as $continent => $items) {
echo "Countries for " . $continent . "\n";
foreach ($items as $country) {
echo $country['country'] . "\n";
}
}
/**
Output:
Countries for aa
A
C
D
E
Countries for bb
B
F
**/
Related
I have an array that I'm wanting to recursively turn into a collection and to use the collection as object values.
I would like to use the collection object in similar ways that eloquent is used rather than using $contact['name'] and being able to use $collection->contacts->each vs foreach $collection->contacts .....)
$collection->contacts->each(function ($contact) {
// ability to use $contact->name (and not have to use $contact['name'])
});
Collection Macro:
Collection::macro('recursive', function () {
return $this->map(function ($value) {
if (is_array($value)) {
return (object)$value;
}
if (is_object($value)) {
return collect($value)->recursive();
}
return $value;
});
});
Example:
public function test_it_can_recursively_convert_array_to_collection()
{
$data = [
[
'name' => 'Michael Scott',
'emails' => [
'mscott#dundermifflin.com',
'michaelscarn#dundermifflin.com',
],
'contacts' => [
[
'name' => 'Dwight Schrute',
'emails' => [
'dschrute#dundermifflin.com',
],
],
[
'name' => 'Jim Halpert',
'emails' => [
'jhalpert#dundermifflin.com',
],
],
],
],
];
$collection = collect($data)->recursive();
$this->assertInstanceOf(Collection::class, $collection);
$collection->each(function ($item) {
$this->assertEquals('Michael Scott', $item->name);
$item->contacts->each(function ($contact) {
$this->assertNotNull($contact->name);
});
});
}
The original collection map works (e.g. $collection->each .... $item->name) but I can't seem to set convert the nested arrays to objects and get the object values.
Error: Call to a member function each() on array
Illuminate\Support\Collection^ {#632
#items: array:1 [
0 => {#13
+"name": "Michael Scott"
+"emails": array:2 [
0 => "mscott#dundermifflin.com"
1 => "michaelscarn#dundermifflin.com"
]
+"contacts": array:2 [
0 => array:2 [
"name" => "Dwight Schrute"
"emails" => array:1 [
0 => "dschrute#dundermifflin.com"
]
]
1 => array:2 [
"name" => "Jim Halpert"
"emails" => array:1 [
0 => "jhalpert#dundermifflin.com"
]
]
]
}
]
}
This is the rquest body:
{
"arr": [
{
"a": 3,
"b": 3
},
{
"a": 3,
"b": 3
}
]
}
and with this validations rules:
public function rules()
{
return [
// 'arr' => 'array',
'arr.*.a' => 'required'
];
}
The result of dd($request->validated()); is what I want, I don't need b to be included
"arr" => array:2 [
0 => array:1 [
"a" => 3
]
1 => array:1 [
"a" => 3
]
]
But when I want to validate the array itself like this:
public function rules()
{
return [
'arr' => 'array',
'arr.*.a' => 'required'
];
}
b will be added to the output
"arr" => array:2 [
0 => array:1 [
"a" => 3,
"b" => 3
]
1 => array:1 [
"a" => 3,
"b" => 3
]
]
How can I prevent adding b to the dd($request->validated()); while validating the array itself?
I have an array
animals = [
[{"name" => "Alex", "spices" => "dog", "vname" => "colour", "value" => "black"},
{"name" => "Alf", "spices" => "dog", "vname" => "colour", "value" => "white"},
{"name" => "Sonia", "spices" => "dog", "vname" => "colour", "value" => "white"}],
[{"name" => "Alex", "spices" => "dog", "vname" => "health", "value" => "80"},
{"name" => "Alf", "spices" => "dog", "vname" => "health", "value" => "98"}],
[{"name" => "Alex", "spices" => "dog", "vname" => "age", "value" => "12"}]
]
Every animal (Alex, Alf and Sonia) is described by colour, health and age (vname values), but Alf does not have his hash with age in third subarray and Sonia does not have her hashes with health (second subarray) and age (third subarray). I want to check if any subarray does not have "vname" pair for some dog and if doesn't to add hash like this
{"name" => "Alf", "spices" => "dog", "vname" => "age", "value" => "unknown"}
to get an array
animals = [
[{"name" => "Alex", "spices" => "dog", "vname" => "colour", "value" => "black"},
{"name" => "Alf", "spices" => "dog", "vname" => "colour", "value" => "white"},
{"name" => "Sonia", "spices" => "dog", "vname" => "colour", "value" => "white"}],
[{"name" => "Alex", "spices" => "dog", "vname" => "health", "value" => "80"},
{"name" => "Alf", "spices" => "dog", "vname" => "health", "value" => "98"},
{"name" => "Sonia", "spices" => "dog", "vname" => "health", "value" => "unknown"}],
[{"name" => "Alex", "spices" => "dog", "vname" => "age", "value" => "12"},
{"name" => "Alf", "spices" => "dog", "vname" => "age", "value" => "unknown"},
{"name" => "Sonia", "spices" => "dog", "vname" => "age", "value" => "unknown"}]
]
Can You help me with this?
Umm. Set the names in an Array. Loop through your animals array and for each of the array inside, check if has hash for all animal names. Add the default hash for those that are missing.
animal_names = ["Alex", "Alf", "Sonia"]
animals.each do |animals_by_vname|
vname = animals_by_vname.first["vname"]
names_present = animals_by_vname.map {|i| i["name"]}
names_missing = animal_names - names_present
names_missing.each do |name|
animals_by_vname << {
"name" => name,
"spices" => "dog",
"vname" => vname,
"value" => "unknown"
}
end
end
puts animals # should be you wanted it.
That solves your problem.
However, I honestly believe, the data structure needs to be optimised. Maybe have a hash of arrays with animal names as keys. Like:
{
"Alex": { health: "good" }
"Alf": { age: 10, health: "good" }
}
Your data format is very hard to work with.
You could define an Animal class, keep an Animal.all hash and write your data in this structure:
data = [
[{ 'name' => 'Alex', 'spices' => 'dog', 'vname' => 'colour', 'value' => 'black' },
{ 'name' => 'Alf', 'spices' => 'dog', 'vname' => 'colour', 'value' => 'white' },
{ 'name' => 'Sonia', 'spices' => 'dog', 'vname' => 'colour', 'value' => 'white' }],
[{ 'name' => 'Alex', 'spices' => 'dog', 'vname' => 'health', 'value' => '80' },
{ 'name' => 'Alf', 'spices' => 'dog', 'vname' => 'health', 'value' => '98' }],
[{ 'name' => 'Alex', 'spices' => 'dog', 'vname' => 'age', 'value' => '12' }]
]
class Animal
attr_accessor :name, :species, :health, :age, :colour
#all = {}
class << self
attr_reader :all
def find_or_create_by_name(params)
all[params['name']] ||= Animal.new(params)
end
end
def initialize(params)
#name = params['name']
#species = params['spices']
end
def to_h
{
name: name,
species: species,
age: age || 'unknown',
colour: colour || 'unknown',
health: health || 'unknown'
}
end
def to_s
to_h.to_s
end
alias inspect to_s
end
data.each do |info|
info.each do |params|
animal = Animal.find_or_create_by_name(params)
animal.instance_variable_set("##{params['vname']}", params['value'])
end
end
require 'pp'
pp Animal.all
# {"Alex"=>
# {:name=>"Alex", :species=>"dog", :age=>"12", :colour=>"black", :health=>"80"},
# "Alf"=>
# {:name=>"Alf", :species=>"dog", :age=>"unknown", :colour=>"white", :health=>"98"},
# "Sonia"=>
# {:name=>"Sonia", :species=>"dog", :age=>"unknown", :colour=>"white", :health=>"unknown"}}
I have an array A that looks like this:
A = [ { "id" => "1234", "name" => "audi", "isCool" => false },
{ "id" => "5678", "name" => "acura", "isCool" => false },
{ "id" => "9101112", "name" => "bentley", "isCool" => true },
{ "id" => "13141516", "name" => "rollsroyce", "isCool" => true },
{ "id" => "17181920", "name" => "toyota", "isCool" => true } ]
and I have an array B that looks like this:
B = ["1234", "13141516”]
I am trying to select only elements from array A that match array A's ids with Array Bs elements.
So the returned results I would like is:
C = [ { "id" => "1234", "name" => "audi", "isCool" => false },
{ "id" => "13141516", "name" => "rollsroyce", "isCool" => true } ]
Is there an easy way to go about this?
I have currently tried this but obviously not a good idea:
a.select {|x| x['id'] == B.first || B.last}
But obviously this is not dynamic, because what if I had 3 or 4 elements in array B.
A.select { |x| B.include?(x['id']) }
I have an array like this:
[
{:game_id=>546012, :period=>:fg, :stat_name=>:hits, :result=>12, :team_id=>1104},
{:game_id=>546012, :period=>:fg, :stat_name=>:errors, :result=>1, :team_id=>1104},
{:game_id=>546012, :period=>:fg, :stat_name=>:hits, :result=>9, :team_id=>1103},
{:game_id=>546012, :period=>:fg, :stat_name=>:errors, :result=>3, :team_id=>1103}
]
How can turn it into an array of items like this:
{ 546012 => { :hits => { :fg => { 1104 => 12,
1103 => 9 } }
:errors => { :fg => { 1104 => 1,
1103 => 3 } } }
First, create a hash organized by :game_id
{ 546012 => {{
{:period=>:fg, :stat_name=>:hits, :result=>12, :team_id=>1104},
{:period=>:fg, :stat_name=>:errors, :result=>1, :team_id=>1104},
{:period=>:fg, :stat_name=>:hits, :result=>9, :team_id=>1103},
{:period=>:fg, :stat_name=>:errors, :result=>3, :team_id=>1103}
]}
Then group by :stat_name
{ 546012 => [
{:hits => [
{:period=>:fg, :result=>12, :team_id=>1104},
{:period=>:fg, :result=>9, :team_id=>1103}],
:errors => [
{:period=>:fg, :result=>1, :team_id=>1104},
{:period=>:fg, :result=>3, :team_id=>1103}
]}
]}
Then group by period:
{ 546012 => [
{:hits => [
:fg => [{:result=>12, :team_id=>1104},
{:result=>9, :team_id=>1103}]
]}
]},
:errors => [
:fg => [{:result=>1, :team_id=>1104},
{:result=>3, :team_id=>1103}]
]}
]}
Lastly, group by :team_id and associate each one to its :result.
{ 546012 => [
{:hits => [
:fg => [1104 => 12, 1103 => 9]
]},
:errors => [
:fg => [1104 => 1, 1103 => 3]
]}
]}
As for how to create these groupings, I will leave it as an exercise to you.
One possible method is to loop through each item and create a new copy of the object that stores these new mappings. For instance, if we have this object:
foods = {
{:food => lemon, :taste => sour},
{:food => pretzel, :taste => salty},
{:food => pretzel, :taste => sweet}
}
We could group by foods like (pseudocode):
newfoods = {};
foreach item in foods:
newfoods[ item.food ].push( item.taste );
And end up with
newfoods = [
{ lemon => [sour] },
{ pretzel => [sweet, salty] }
]
The best option you can do is flatten it.
Credits go to this question.