php multi array - arrays

What if I have a multidimensional array, in which I want to make combinations. So for example if I have 3 arrays:
Array(1,2)
Array(7,3)
Array(3,9,8,2)
Then I want a function which make combinations like: array( array(1,7) array(1,3) array( 3,9) etcetera but also array(7,3) array(7,9)
Plus it must check the availability so if the numbers are not available then do not make combinations with them.
Now I got this, and further I cant get:
$xCombinations =
Array(
Array(1,2)
Array(7,3)
Array(3,9,8,2)
);
$available = Array([1] => 2,[2] => 1, [7]=>3,[3]=>4, [8]=>2, [2]=>4);
$combination = Array();
recursiveArray($xCombinations);
function recursiveArray($tmpArr){
if($tmpArr){
foreach ($tmpArr)as $value) {
if (is_array($value)) {
displayArrayRecursively($value);
} else {
//here 1st time $value would be 1,
//so this must now be combined with 7 , 3 , 9, 8 ,2 >
//so you will get an array of array(1,7) array, (1,3) array (3,9)
//etcetera.. there arrays must be also in a multidimensional array
}
}
}
}

This issue consists ow two parts. Actually, it can be implemented with one functional block, but that would be hard to read. So, I'll split it to two operations.
Retrieving combinations
First task is to get all combinations. By definition, those combinations are Cartesian product of your array members. That could be produced without recursion, like:
$result = array_reduce(array_slice($input, 1), function($c, $x)
{
return call_user_func_array('array_merge',
array_map(function($y) use ($c)
{
return array_map(function($z) use ($y)
{
return array_merge((array)$z, (array)$y);
}, $c);
}, $x)
);
}, current($input));
Here we're accumulating our tuples one be one. Result is just all possible combinations as it should be for Cartesian product.
Filtering result
This is another part of the issue. Assuming you have $available as a set of key=>value pairs, where key corresponds to specific number in a tuple and value corresponds to how many times can that number appear in that tuple at most, here's simple filtering code:
$result = array_filter($result, function($tuple) use ($available)
{
foreach(array_count_values($tuple) as $value=>$count)
{
if(isset($available[$value]) && $available[$value]<$count)
{
return false;
}
}
return true;
});
For instance, if we have $input as
$input = [
[1,3],
[1,6]
];
and restrictions on it as
$available = [6=>0, 1=>1];
Then result would be only
array(1) {
[1]=>
array(2) {
[0]=>
int(3)
[1]=>
int(1)
}
}
since only this tuple fulfills all requirements (so it has not 6 and 1 appeared once in it)

Related

Compare two 2D array to match the string

I want to compare string in two 2D array but the size are not the same. So, I want to shift the element in one of the array to match with all of the element in another array but i run out of idea on how the looping should be.
This is my first time using perl language. I learned c language before.
#!/usr/intel/pkgs/perl/5.14.1/bin/perl
use Data::Dumper qw(Dumper);
#clk = (
'prescc_ux_aux_clk',
'prescc_ux_prim_clk',
'usb2_phy_side_clk',
'usb3_phy_side_clk',
'ux_prim_clk',
'ux_side_clk',
'ux_xtal_frm_refclk',
'uxd_aux_clk',
'uxd_pgcb_clk',
'uxd_prescc_aux_clk',
'uxd_prim_clk',
'uxd_side_clk',
'uxd_suspend_clk');
#clkack = (
'ccu_ux_xtal_frm_refclk_ack',
'ibbs_ux_prim_clkack',
'sbr_ux_side_clkack',
'uxd_aux_clkack',
'uxd_pgcb_clkack',
'uxd_prim_clkack',
'uxd_side_clkack');
foreach(#clk){
#clkline = map {[split /_/,$_]} #clk;
}
foreach(#clkack){
#clkackline = map{[split /_/,$_]} #clkack;
}
#print Dumper #clkline;
$match = 0;
$clkack_row = #clkackline; #no. of row in clkackline
$clk_row = #clkline;
for ($i=0; $i<$clkack_row; $i++){
$clkackcolumn = #{$clkackline[$i]};
for ($j=0; $j<$clkackcolumn; $j++){
for ($m=0; $m<$clk_row; $m++){
$clkcolumn = #{$clkline[$m]};
for ($n=0; $n<$clkcolumn; $n++){
if ($clkline[$i][$j] eq $clkacline[$m][$n]){
$match = $match + 1;
print "$match\n";
}
}
}
}
}
I expect it to loop the #clkackline array and compare it with the #clkline array. If it's matching then it will give how many match it have, hence the $match variable.
Edited:
I need to split it by '_' so that i can get the element divided by only the word inside another array.
Eg:
$clk[0] = ux_prim_clk will result in;
$clkline[0][0] = ux, $clkline[0][1] = prim, $clkline[0][2] = clk.
Then i need to compare with the #clkackline array element by element but in sequential order.
Eg:
$clkline[0][0] = $clkackline[0][0],
$clkline[0][1] = $clkackline[0][1],
$clkline[0][2] = $clkackline[0][2].
But another problem is i need to compare #clkline with all of the element in clkackline. But since the size will be the constraint, then i need to shift the #clkackline to match with #clkline or vice versa.
Eg:
First check:
$clkline[1][0] = $clkackline[0][0],
$clkline[1][1] = $clkackline[0][1],
$clkline[1][2] = $clkackline[0][2].
Second check:
$clkline[1][0] = $clkackline[0][1],
$clkline[1][1] = $clkackline[0][2],
$clkline[1][2] = $clkackline[0][3].
This is just an example of course, but you can see that i need it to delete the first column in $clkackline[0].
Then i need to calculate the percentage of how much it will match.
Eg:
ux_prim_clk vs uxd_prim_clkack will return 33.33%.
Then store the element with highest match inside another array (eg: #clknew)
I think you may be over complicating your problem, to answer your original question of comparing two lists I have a script that will compare two lists and tell you what doesn't exist in each. If this inst exactly what you need let me know and we can change it up to fit your use. As with just about anything in Perl there is probably a module that will do all this for you.
#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw(any);
my #list1 = ('prescc_ux_aux_clk',
'prescc_ux_prim_clk',
'usb2_phy_side_clk',
'usb3_phy_side_clk',
'ux_prim_clk',
'ux_side_clk',
'ux_xtal_frm_refclk',
'uxd_aux_clk',
'uxd_pgcb_clk',
'uxd_prescc_aux_clk',
'uxd_prim_clk',
'uxd_side_clk',
'uxd_suspend_clk'
);
my #list2 = ('ccu_ux_xtal_frm_refclk_ack',
'ibbs_ux_prim_clkack',
'sbr_ux_side_clkack',
'uxd_aux_clkack',
'uxd_pgcb_clkack',
'uxd_prim_clkack',
'uxd_side_clkack'
);
print "\n==== LIST 1 TO LIST 2 COMPARISON, Does not exist in list 2 ====\n";
foreach my $first (#list1) {
if ( any { $_ eq $first} #list2) { next; }
else { print $first . "\n"; }
}
print "\n==== LIST 2 TO LIST 1 COMPARISON, Does not exist in list 1 ====\n";
foreach my $first (#list2) {
if ( any {$_ eq $first} #list1) { next; }
else { print $first . "\n"; }
}

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.

How to compare two hashes of different levels in perl without using sub routines or modules?

my arrays are
my #arr = ('mars','earth','jupiter');
my #arr1 = ('mercury','mars');
my #arr2 = ('planet','earth','star','sun','planet2','mars');
%space = ( 'earth'=>{
'planet'=> {
'1' =>'US',
'2' =>'UK'
},
'planet2'=>{
'1' =>'AFRICA',
'2' =>'AUS'
}
},
'sun'=>{
'star' =>{
'1' =>'US',
'2' =>'UK'
}
},
'mars' =>{
'planet2' =>{
'1' =>'US',
'2' =>'UK'
}
}
);
now i am comparing the first two arrays in the following manner
foreach (#arr)
{
$arr_hash{$_} =1;
}
foreach my $name (keys %space)
{
foreach my $key (keys %{$space{$name}})
if ($arr_hash{$name} !=1)
{
#do something
}
now how should i compare the third array? I am trying something like this
else
{
if($arr2_hash{$key}{$name} !=1)
{
#do something else
}
I want to check whether the planet+earth pair(ex. the combination of key1 and key2 should be matched with first and second element in #arr2) is present in %space too?
any help?
I've done this twice now in Perl. Once for Test::More's is_deeply() and again for perl5i's are_equal(). Doing it right is not simple. Doing it without subroutines is just silly. If you want to see how this is done, look at are_equal(), though it can be done better.
But I don't think you actually need to compare two hashes.
What I think is happening is you need to check if the things in the various arrays are present in %space. For example...
my #arr = ('mars','earth','jupiter');
That would be true, true, and false.
my #arr1 = ('mercury','mars');
False, true.
my #arr2 = ('planet','earth','star','sun','planet2','mars');
Assuming these are pairs, they're all true.
I'm going to use better variable names than #arr which describe the contents, not the type of the structure. I'm also going to assume that use strict; use warnings; use v5.10; is present.
The first two are simple, loop through the array and check if there's an entry in %space. And we can do both arrays in one loop.
for my $name in (#names1, #names2) {
print "$name...";
say $space{$name} ? "Yes" : "No";
}
The third set is a little trickier, and how the data is laid out makes it harder. Putting pairs in a list is awkward, that's what hashes are for. This would make more sense...
my %object_types = (
earth => "planet", sun => "star", mars => "planet2"
);
Then it's easy. Check that $space{$name}{$type} is true.
for my $name (keys %object_types) {
my $type = $object_types{$name};
print "$name / $type...";
say $space{$name}{$type} ? "Yes" : "No";
}
Or if you're stuck with the array we can iterate through the list in pairs.
# $i will be 0, 2, 4, etc...
for( my $i = 0; $i < $#stellar_objects; $i+=2 ) {
my($type, $name) = ($stellar_objects[$i], $stellar_objects[$i+1]);
print "$name / $type...";
say $space{$name}{$type} ? "Yes" : "No";
}
What if you had a hash of types with multiple names to check instead?
my %object_types = (
planet =>['earth'],
star =>['sun'],
planet2 =>['earth','mars']
);
Same idea, but we need an inner loop over the names array. Good use of plural variable names helps keep thing straight.
for my $type (keys %object_types) {
my $names = $object_types{$type};
for my $name (#$names) {
print "$name / $type...";
say $space{$name}{$type} ? "Yes" : "No";
}
}
Since these are really a set of pairs to search for, combining them into a big hash is a disservice. A better data structure to feed this search might be a list of pairs.
my #searches = (
[ planet => 'earth' ],
[ star => 'sun' ],
[ planet2 => 'earth' ],
[ planet2 => 'mars' ],
);
for my $search (#searches) {
my($type, $name) = #$search;
print "$name / $type...";
say $space{$name}{$type} ? "Yes" : "No";
}
For the record, %space is poorly designed. The first two levels are fine, name and type, it's the country hashes that are awkward.
'sun'=>{
'star' =>{
# This part
'1' =>'US',
'2' =>'UK'
}
},
This has none of the advantages of a hash, and all of the disadvantages. The advantage of a hash is it's very fast to look up a single key, but this makes it awkward by making the interesting part a value. If the key is trying to impose an order on the hash, use an array.
sun => {
star => [ 'US', 'UK' ]
},
Then you can get a list the countries: $countries = $space{$name}{$type}
If you want fast key lookup and order doesn't matter, use a hash with the keys being the thing stored, and the value being 1 (just a placeholder for "true").
sun => {
star => { 'US' => 1, 'UK' => 1 }
},
This takes advantage of hash key lookup and allows $space{$name}{$type}{$country} to quickly check for existence. The "values" (even though they're stored as keys) are also guaranteed to be unique. This formally known as a set, a collection of unique values.
And you can store further information in the value.

How to get first n values from perl Hash of arrays

Experts,
I have a hash of array in perl which I want to print the first 2 values.
my %dramatis_personae = (
humans => [ 'hamnet', 'shakespeare', 'robyn', ],
faeries => [ 'oberon', 'titania', 'puck', ],
other => [ 'morpheus, lord of dreams' ],
);
foreach my $group (keys %dramatis_personae) {
foreach (#{$dramatis_personae{$group}}[0..1]) { print "\t$_\n";}
}
The output I get is
"hamnet
shakespeare
oberon
titania
morpheus
lord of dreams"
which is basically first two array values for each key. But I am looking to have the output as:
hamnet
shakespeare
Please advise how I can get this result. Thanks!
Keys of hashes are not ordered, so you should specify keys ordering by yourself. Then you can concatenate arrays from each key specified and take first two values from resulting array, is it what you want ?
print "\t$_\n" foreach (map {(#{$dramatis_personae{$_}})} qw/humans faeries other/)[0..1];
Hashes are unordered, so what you requested to achieve is impossible. Unless you have some knowledge about the keys and the order they should be in, the closest you can get is something that can produce any of the following:
'hamnet', 'shakespeare'
'oberon', 'titania'
'morpheus, lord of dreams', 'hamnet'
'morpheus, lord of dreams', 'oberon'
The following is an implementation that does just that:
my $to_fetch = 2;
my #fetched = ( map #$_, values %dramatis_personae )[0..$to_fetch-1];
The following is a more efficient version for larger structures. It also handles insufficient data better:
my $to_fetch = 2;
my #fetched;
for my $group (values(%dramatis_personae)) {
if (#$group > $to_fetch) {
push #fetched, #$group[0..$to_fetch-1];
$to_fetch = 0;
last;
} else {
push #fetched, #$group;
$to_fetch -= #$group;
}
}
die("Insufficient data\n") if $to_fetch;

How to sort an array of hashes in ruby

I have an array, each of whose elements is a hash with three key/value pairs:
:phone => "2130001111", :zip => "12345", :city => "sometown"
I'd like to sort the data by zip so all the phones in the same area are together. Does Ruby have an easy way to do that? Can will_paginate paginate data in an array?
Simples:
array_of_hashes.sort_by { |hsh| hsh[:zip] }
Note:
When using sort_by you need to assign the result to a new variable: array_of_hashes = array_of_hashes.sort_by{} otherwise you can use the "bang" method to modify in place: array_of_hashes.sort_by!{}
sorted = dataarray.sort {|a,b| a[:zip] <=> b[:zip]}
Use the bang to modify in place the array:
array_of_hashes.sort_by!(&:zip)
Or re-assign it:
array_of_hashes = array_of_hashes.sort_by(&:zip)
Note that sort_by method will sort by ascending order.
If you need to sort with descending order you could do something like this:
array_of_hashes.sort_by!(&:zip).reverse!
or
array_of_hashes = array_of_hashes.sort_by(&:zip).reverse
If you have Nested Hash (Hash inside a hash format) as Array elements (a structure like the following) and want to sort it by key (date here)
data = [
{
"2018-11-13": {
"avg_score": 4,
"avg_duration": 29.24
}
},
{
"2017-03-13": {
"avg_score": 4,
"avg_duration": 40.24
}
},
{
"2018-03-13": {
"avg_score": 4,
"avg_duration": 39.24
}
}
]
Use Array 'sort_by' method as
data.sort_by { |element| element.keys.first }
If you want to paginate for data in array you should require 'will_paginate/array' in your controller

Resources