How to convert two level array to one level array in laravel - arrays

I have a array of array data which is came from database. And my "array to xml converter" can convert only one level array.
Basicly I want to convert my database table to xml file.
public function downloadXml()
{
$fields = ['created_at', 'updated_at'];
$products = Product::where('user_id', auth()->id())
->exclude($fields)->get()->toArray();// this is returnin array of array like [0 => [], 1 => []]
$products = array_collapse($products);
$result = ArrayToXml::convert($product, 'product');
}
The problem is array_collapse method trim the one level array but give me only last array not all arrays. How can I get all arrays? Any help appreciated..
Edit: when dd(Product::where('user_id', auth()->id())
->exclude($fields)->get()->toArray(););
Output1 = array:2 [▼ 0 => array:18 [▶] 1 => array:18 [▶] ]
When dd(array_collapse(Product::where('user_id', auth()->id())
->exclude($fields)->get()->toArray());)
Output2 = array:18 [▶]
I need something like output2 but the problem is output2 assuming there is only one product but actualy there is two product.

When performing a database query using Eloquent (Laravel ORM) the results are returned inside a Collection (one per row). This is the 'first level' array you are mentioning.
Unfortunately, a 'second level' array is needed to define the attributes (among others) for every row.
So you either have to:
extend your ArrayToXml converter to work with Laravel collections (i.e. print xml tag, loop through the elements and print the closing xml tag)
Split your xml converter into two pieces: a wrapper for the xml tag opening and closing and your current ArrayToXml::convert inside a map function.
Let me illustrate the latter:
public function downloadXml()
{
return Product::where('user_id', auth()->id())
->get()
->map(function (Product $product){
return ArrayToXml::convert($product->only('id', 'user_id'), 'product');
})
}

Related

Identifying elements in one array of hashes that are not in another array of hashes (perl)

I'm a novice perl programmer trying to identify which elements are in one array of hashes but not in another. I'm trying to search through the "new" array, identifying the id, title, and created elements that don't exist from the "old" array.
I believe I have it working with a set of basic for() loops, but I'd like to do it more efficiently. This only came after having tried to use grep() and failed.
These arrays are built from a database as such:
use DBI;
use strict;
use Data::Dumper;
use Array::Utils qw(:all);
sub db_connect_new();
sub db_disconnect_new($);
sub db_connect_old();
sub db_disconnect_old($);
my $dbh_old = db_connect_old();
my $dbh_new = db_connect_new();
# get complete list of articles on each host first (Joomla! system)
my $sql_old = "select id,title,created from mos_content;";
my $sql_new = "select id,title,created from xugc_content;";
my $sth_old = $dbh_old->prepare($sql_old);
my $sth_new = $dbh_new->prepare($sql_new);
$sth_old->execute();
$sth_new->execute();
my $ref_old;
my $ref_new;
while ($ref_old = $sth_old->fetchrow_hashref()) {
push #rv_old, $ref_old;
}
while ($ref_new = $sth_new->fetchrow_hashref()) {
push #rv_new, $ref_new;
}
my #seen = ();
my #notseen = ();
foreach my $i (#rv_old) {
my $id = $i->{id};
my $title = $i->{title};
my $created = $i->{created};
my $seen = 0;
foreach my $j (#rv_new) {
if ($i->{id} == $j->{id}) {
push #seen, $i;
$seen = 1;
}
}
if ($seen == 0) {
print "$i->{id},$i->{title},$i->{state},$i->{catid},$i->{created}\n";
push #notseen, $i;
}
}
The arrays look like this when using Dumper(#rv_old) to print them:
$VAR1 = {
'title' => 'Legal Notice',
'created' => '2004-10-07 00:17:45',
'id' => 14
};
$VAR2 = {
'created' => '2004-11-15 16:04:06',
'id' => 86096,
'title' => 'IRC'
};
$VAR3 = {
'id' => 16,
'created' => '2004-10-07 16:15:29',
'title' => 'About'
};
I tried to use grep() using array references, but I don't think I understand arrays, hashes, and references well enough to do it properly. My failed grep() attempts are below. I'd appreciate any ideas of how to do this properly.
I believe the problem with this is that I don't know how to reference the id field in the second array of hashes. Most of the examples using grep() that I've seen are to just look through an entire array, like you would with regular grep(1). I need to iterate through one array, checking each of the values from the id field with the id field from another array.
my $rv_old_ref = \#rv_old;
my $rv_new_ref = \#rv_new;
for my $i ( 0 .. $#rv_old) {
my $match = grep { $rv_new_ref->$_ == $rv_old_ref->$_ } #rv_new;
push #notseen, $match if !$match;
}
I also tried variations on the grep() above:
1) if (($p) = grep ($hash_ref->{id}, #rv_old)) {
2) if ($hash_ref->{id} ~~ #rv_old) {
There are a number of libraries that compare arrays. However, your comparison involves complex data structures (the arrays have hashrefs as elements) and this at least complicates use of all modules that I am aware of.
So here is a way to do it by hand. I use the shown array and its copy with one value changed.
use warnings;
use strict;
use feature 'say';
use List::Util qw(none); # in List::MoreUtils with older Perls
use Data::Dump qw(dd pp);
sub hr_eq {
my ($e1, $e2) = #_;
return 0 if scalar keys %$e1 != scalar keys %$e2;
foreach my $k1 (keys %$e1) {
return 0 if !exists($e2->{$k1}) or $e1->{$k1} ne $e2->{$k1};
}
return 1
}
my #a1 = (
{ 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
{ 'created' => '2004-11-15 16:04:06', 'id' => 86096, 'title' => 'IRC' },
{ 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);
my #a2 = (
{ 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
{ 'created' => '2004-11-15 16:xxx:06', 'id' => 86096, 'title' => 'IRC' },
{ 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);
my #only_in_two = grep {
my $e2 = $_;
none { hr_eq($e2, $_) } #a1;
} #a2;
dd \#only_in_two;
This correctly identifies the element in #a2 that doesn't exist in #a1 (with xxx in timestamp).
Notes
This finds what elements of one array are not in another, not the full difference between arrays. It is what the question specifically asks for.
The comparison relies on details of your data structure (hashref); there's no escaping that, unless you want to reach for more comprehensive libraries (like Test::More).
This uses string comparison, ne, even for numbers and timestamps. See whether it makes sense for your real data to use more appropriate comparisons for particular elements.
Searching through a whole list for each element of a list is an O(N*M) algorithm. Solutions of such (quadratic) complexity are usable as long as data isn't too big; however, once data gets big enough so that size increases have clear effects they break down rapidly (slow down to the point of being useless). Time it to get a feel for this in your case.
An O(N+M) approach exists here, utilizing hashes, shown in ikegami answer. This is much better algorithmically, once the data is large enough for it to show. However, as your array carries complex data structure (hashrefs) a bit of work is needed to come up with a working program, specially as we don't know data. But if your data is sizable then you surely want to implement this.
Some comments on filtering.
The question correctly observes that for each element of an array, as it's processed in grep, the whole other array need be checked.
This is done in the body of grep using none from List::Util. It returns true if the code in its block evaluates false for all elements of the list; thus, if "none" of the elements satisfy that code. This is the heart of the requirement: an element must not be found in the other array.
Care is needed with the default $_ variable, since it is used by both grep and none.
In grep's block $_ aliases the currently processed element of the list, as grep goes through them one by one; we save it into a named variable ($e2). Then none comes along and in its block "takes possession" of $_, assigning elements of #a1 to it as it processes them. The current element of #a2 is also available since we have copied it into $e2.
The test performed in none is pulled into a a subroutine, which I call hr_eq to emphasize that it is specifically for equality comparison of (elements in) hashrefs.
It is in this sub where the details can be tweaked. Firstly, instead of bluntly using ne for values for each key, you can add custom comparisons for particular keys (numbers must use ==, etc). Then, if your data structures change this is where you'd adjust specifics.
You could use grep.
for my $new_row (#new_rows) {
say "$new_row->{id} not in old"
if !grep { $_->{id} == $new_row->{id} } #old_rows;
}
for my $old_row (#old_rows) {
say "$old_row->{id} not in new"
if !grep { $_->{id} == $old_row->{id} } #new_rows;
}
But that's an O(N*M) solution, while there exists an O(N+M) solution that would be far faster.
my %old_keys; ++$old_keys{ $_->{id} } for #old_rows;
my %new_keys; ++$new_keys{ $_->{id} } for #new_rows;
for my $new_row (#new_rows) {
say "$new_row->{id} not in old"
if !$old_keys{$new_row->{id}};
}
for my $old_row (#old_rows) {
say "$old_row->{id} not in new"
if !$new_keys{$old_row->{id}};
}
If both of your database connections are to the same database, this can be done far more efficiently within the database itself.
Create a temporary table with three fields, id, old_count (DEFAULT 0) and new_count (DEFAULT 0).
INSERT OR UPDATE from the old table into the temporary table, incrementing old_count in the process.
INSERT OR UPDATE from the new table into the temporary table, incrementing new_count in the process.
SELECT the rows of the temporary table which have 0 for old_count or 0 for new_count.
select id,title,created from mos_content
LEFT JOIN xugc_content USING(id)
WHERE xugc_content.id IS NULL;
Gives you the rows that are in mos_content but not in xugc_content.
That's even shorter than the Perl code.

Add value to new collection

I have created a new collection, when I try to add new values with a key in a foreach loop its not working. It only appends the 1st value. I want it to add all.
Here my code:
$new = new Collection();
foreach($messages as $message){
$new['message'] = $message->message;
}
But in the end instead of returning all messages, it only returns the 1st message:
{"message":"first"}
Instead it should have returned
{"message": "first", "message": "second"}
What is the problem?
Also when I do it without giving a key to a collection, it returns all:
foreach($messages as $message){
$new = $message->message;
}
You have an array with the same key, so the for loop overrides the same value again and again.
If you want a collection that contains objects with the same key you can change the following code to your need:
$new = array();
foreach ([1, 2, 3] as $i) {
$new[] = array('message' => $i);
}
$collection = collect($new);
Your making a collection with 1 key: namely "message" and then proceed to overwrite this key for the duration of your loop. PHP thus handles this as shown in your question: a collection with 1 key and 1 value.
This makes perfect sense (for example, how many houses are on your home address? Only one right? What would happen to your mail if there were 16 houses with the exact same address?).
What you want simply isn't possible: how should PHP know what it should return when you ask for the value at "message" if the entire data structure would consist out of the same key?
test this code :
$messages=['ali','reza','sara'];
$new = new Collection();
foreach($messages as $key=>$message){
$new->prepend($message,$key);
}
dd($new);
output :
Collection {#475 ▼
#items: array:3 [▼
2 => "sara"
1 => "reza"
0 => "ali"
]
}

Access an array item that is an anonymous hash in Template Toolkit in Perl

I have this piece of code as part of a foreach loop in my controller:
my $gr = My::Model::Group->new(id => $gra->gr_id);
$gra = My::Model::Group::Admin->new(id => $gra->id);
push(#$groups, {$gr => $gra});
In #$groups array I want to store anonymous hashes where the key element is the group object and the value element is the admin of that group object. Then in the template I want to show the list of different groups that the admin can log in, for that I have this code:
[%- FOREACH gr IN groups -%]
<li><input type="radio" name="group" value="[% gr.id %]">[% gr.name %]</input></li>
[%- END -%]
I know that the p IN partners is not right but is to show you what I want to achieve. Any suggestions on the template code?
duskwuff already explains in their answer that you can't use objects as hash keys as they get serialized and you'll lose the object-ness. My answer builds on that.
Let's say you have an array of arrays instead, where each inner array holds a pair of objects. I've created Moo classes to illustrate.
package My::Model::Group;
use Moo;
has [qw/id name/] => ( is => 'ro' );
package My::Model::Group::Admin;
use Moo;
has [qw/id name/] => ( is => 'ro' );
package main;
my $groups = [
[
My::Model::Group->new( id => 1, name => 'group1' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
[
My::Model::Group->new( id => 2, name => 'group2' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
[
My::Model::Group->new( id => 3, name => 'group3' ) =>
My::Model::Group::Admin->new( id => 1, name => 'bar' )
],
[
My::Model::Group->new( id => 4, name => 'group4' ) =>
My::Model::Group::Admin->new( id => 1, name => 'foo' )
],
];
There are four pairs. Two admins, four groups. Three of the groups belong to the foo admin, and one to bar. Now let's look at the template.
use Template;
my $tt = Template->new();
$tt->process( \*DATA, { groups => $groups }, \my $output )
or die $tt->error;
print $output;
__DATA__
[%- FOREACH item IN groups -%]
[%- DEFAULT by_admin.${item.1.name} = [] -%]
[%- by_admin.${item.1.name}.push(item.0) -%]
[%- END -%]
[%- FOREACH admin IN by_admin.keys.sort -%]
[%- FOREACH group IN by_admin.$admin -%]
[%- admin %] -> [% group.id %]
[%- END -%]
[%- END -%]
The relevant part obviously is the DATA section. We need to reorganize the array data structure into a hash that has the admins, and then each group sorted into one of the admin slots.
We don't need to create the by_admin variable. It will be created globally implicitly. But we do need to set a default value for $by_admin->{$item[0]->name} (I'm using Perl syntax now, to make it easier to understand). It seems like Template Toolkit does not know autovivification, and the DEFAULT keyword is similar to the //= assignment operator in Perl.
We can then push the first element of item into the array ref we just created (if it didn't exist yet) inside the hash ref element with the key item.1.name inside by_name.
Once we're done preparing, the rest is just a simple loop. We iterate the sorted keys of by_admin, and then iterate the array ref that's behind that key.
Here's the output:
bar -> 3
foo -> 1
foo -> 2
foo -> 4
It would make sense to do the preprocessing not in a template, but in your controller instead. As normal Perl code it should be easier to read.
my %by_admin;
for my $group (#$groups) {
push #{ $by_admin{ $group->[1]{name} } }, $group->[0];
}
Note that I have omitted use strict and use warnings for brevity.
You will need to rework your code significantly to make this possible.
Keys in Perl hashes are strings, not scalars. Using anything that isn't a string as a key in a hash (e.g, $gr in the expression { $gr => $gra } will cause it to be stringified, just as if you had interpolated it into a string or printed it. Unless you have explicitly overloaded the "" operator on the My::Model::Group object, the key will end up being stored as a literal string along the lines of:
"My::Model::Group=HASH(0x1234567890)"
This string cannot be converted back to the original object -- in fact, the original object was probably garbage-collected as soon as it went out of scope, so it no longer exists at all.
Consider storing the pair as an array reference instead, e.g.
push #$groups, [$gr, $gra];

Can not access object in laravel and has "0" indexes when appliying toArray()

I have these tables
clientes
id
nombre_rz
ced_rif
telefono
id_usuario
solicitantes
id
nombre
email
telefono
id_cliente
cliente->hasMany('solicitante')<br>
solicitante->belongsTo('cliente')<br>
^ This is well written in the models, just trying not to make a wall of text.
After Authenticating, when i do
$cliente = Cliente::where('usuario_id','=',Auth::id())->with('solicitante')->get();
dd($cliente);
or
$cliente = Cliente::where('usuario_id','=',Auth::id())->with(array('solicitante' => function($query)
{
$query->where('cliente_id', '=', '35');
}))->get();
dd($cliente);
i get this obect
Object from query
And using toArray() i get this
Array from object
And if access index 0 of that array like
$array = $cliente->toArray(); dd($array['0']);
i get
[Index 0 of array][3]
As far as i can see the queries are correct, and the data i need is there, but i don't know why i can't access the object like
$cliente->id; $cliente->telefono, $cliente->solicitante->nombre, $cliente->solicitante->email
It always throws
Undefined property: Illuminate\Database\Eloquent\Collection::$telefono
Can't Understand this behavior.
You are returning an array of objects, because you asked Laravel for a group. You can either do
foreach ($cliente as $client)
{
echo $client->telefono;
foreach ($client->solicitante as $solicitante)
{
$solicitante->nombre;
}
}
OR you can specify that you only want one result
$cliente = Cliente::where('usuario_id','=',Auth::id())->with('solicitante')->first();
echo $cliente->telefono;
foreach ($client->solicitante as $solicitante)
{
$solicitante->nombre;
}

Unsetting elements of a cakephp generated array

I have an array called $all_countries following this structure:
Array
(
[0] => Array
(
[countries] => Array
(
[id] => 1
[countryName] => Afghanistan
)
)
[1] => Array
(
[countries] => Array
(
[id] => 2
[countryName] => Andorra
)
)
)
I want to loop through an array called prohibited_countries and unset the entire [countries] element that has a countryName matching.
foreach($prohibited_countries as $country){
//search the $all_countries array for the prohibited country and remove it...
}
Basically I've tried using an array_search() but I can't get my head around it, and I'm pretty sure I could simplify this array beforehand using Set::extract or something?
I'd be really grateful if someone could suggest the best way of doing this, thanks.
Here's an example using array_filter:
$all_countries = ...
$prohibited_countries = array('USA', 'England'); // As an example
$new_countries = array_filter($all_countries, create_function('$record', 'global $prohibited_countries; return !in_array($record["countries"]["countryName"], $prohibited_countries);'));
$new_countries now contains the filtered array
Well first of all id e teh array in the format:
Array(
'Andorra' => 2,
'Afghanistan' => 1
);
Or if you need to have the named keys then i would do:
Array(
'Andorra' => array('countryName'=> 'Andorra', 'id'=>2),
'Afghanistan' => array('countryName'=> 'Afghanistan', 'id'=>1)
);
then i would jsut use an array_diff_keys:
// assuming the restricted and full list are in the same
// array format as outlined above:
$allowedCountries = array_diff_keys($allCountries, $restrictedCountries);
If your restricted countries are just an array of names or ids then you can use array_flip, array_keys, and/or array_fill as necessary to get the values to be the keys for the array_diff_keys operation.
You could also use array_map to do it.
Try something like this (it's probably not the most efficient way, but it should work):
for ($i = count($all_countries) - 1; $i >= 0; $i--) {
if (in_array($all_countries[$i]['countries']['countryName'], $prohibited_countries) {
unset($all_countries[$i]);
}
}
If you wanted to use the Set class included in CakePHP, you could definitely reduce the simplicity of your country array with Set::combine( array(), key, value ). This will reduce the dimensionality (however, you could do this differently as well. It looks like your country array is being created by a Cake model; you could use Model::find( 'list' ) if you don't want the multiple-dimension resultant array... but YMMV).
Anyway, to solve your core problem you should use PHP's built-in array_filter(...) function. Manual page: http://us3.php.net/manual/en/function.array-filter.php
Iterates over each value in the input
array passing them to the callback
function. If the callback function
returns true, the current value from
input is returned into the result
array. Array keys are preserved.
Basically, pass it your country array. Define a callback function that will return true if the argument passed to the callback is not on the list of banned countries.
Note: array_filter will iterate over your array, and is going to be much faster (execution time-wise) than using a for loop, as array_filter is a wrapper to an underlying C function. Most of the time in PHP, you can find a built-in to massage arrays for what you need; and it's usually a good idea to use them, just because of the speed boost.
HTH,
Travis

Resources