Compare array with duplicate values using array_intersect? - php

I'm designing a package engine for my catalog. Here you can add a certain ammount of products to the package and a discount. When you order products the script have to detect which package deals apply to your order.
Here is my code:
// packages
$packages["package1"] = array(1,1,2);
$packages["package2"] = array(1,2);
//orderlist
$orderlist = array(1,1,2,1,2,2);
// put the order list in a temp array
$temp_product_array = $orderlist;
foreach($packages as $pname => $package_array)
{
$no_more_package = 0;
do
{
// put the package products in a temp array
$temp_package_array = $package_array;
$is_packages_array = array_intersect($temp_package_array,$temp_product_array);
// if all package values are present
if(count($is_packages_array) == count($temp_package_array))
{
// add package name
$packages_in_order[] = $pname;
// filter the package out of the product list but keep duplicate values
foreach($temp_product_array as $key1 => $pid1)
{
foreach($temp_package_array as $key2 => $pid2)
{
if($pid1==$pid2)
{
unset($temp_product_array[$key1]);
unset($temp_package_array[$key2]);
break; // after removing go to the next product to prevent double erasing
}
}
}
}
else
{
$no_more_package = 1;
}
}
while($no_more_package<1);
}
print_r($packages_in_order);
print_r($temp_product_array);
The result is:
Array ( [0] => package1 [1] => package1 ) Array ( [5] => 2 )
But I want the result to be:
Array ( [0] => package1 [1] => package2 ) Array ( [5] => 2 )
I tried array_diff, array_intersect but they all do not work well with duplicate values.
Does anyone has a better/working way of solving this?
(PS because of different sources I cannot work with associative arrays)

// packages
$packages["package1"] = array(1,1,2);
$packages["package2"] = array(1,2);
//orderlist
$orderlist = array(1,1,1,2,2,2);
// put the order list in a temp array
$temp_product_array = $orderlist;
$product_count_array = array_count_values($temp_product_array);
foreach($packages as $pname => $temp_package_array)
{
$no_more_package = 0;
do
{
$test_package_array = array();
foreach($temp_package_array as $key => $pid)
{
// check if the product is still in the order totals
if(isset($product_count_array[$pid]) && $product_count_array[$pid]>0)
{
$product_count_array[$pid]--;
$test_package_array[] = $pid;
}
else
{
$no_more_package = 1;
}
}
// check if the found products match the package count
if(count($temp_package_array)==count($test_package_array))
{
$packages_in_order[] = $pname;
}
else
{
// add the extracted products in case of incomplete package
foreach($test_package_array as $pid)
{
$product_count_array[$pid]++;
}
}
}
while($no_more_package<1);
}
print_r($packages_in_order);
print_r($product_count_array);

I would devide the problem. Part of it is to locate the package inside the list. An existing function that does exactly that has been named consecutive_values in a probably related question: Searching for consecutive values in an array.
With it is it possible to locate an array within another array in the exact order. This is probably what you want.
The part left is to search for the packages then and is pretty straight forward. If you understood your question right, you want to return left-overs as well:
list($found, $rest) = find_packages($packages, $orderlist);
var_dump($found, $rest);
function find_packages(array $packages, array $list)
{
$found = array();
foreach($packages as $name => $package) {
# consecutive_values() is #link https://stackoverflow.com/a/6300893/367456
$has = consecutive_values($package, $list);
if ($has === -1) continue;
$found[] = $name;
array_splice($list, $has, count($package));
}
return array($found, $list);
}
Output:
array(2) {
[0] =>
string(8) "package1"
[1] =>
string(8) "package2"
}
array(1) {
[0] =>
int(2)
}
Edit: Searching for the same package multiple times needs a slight modification. Here the an inner while loop is created that needs a break if the current package is not found:
function find_packages(array $packages, array $list)
{
$found = array();
foreach($packages as $name => $package) {
while (true) {
# consecutive_values() is #link https://stackoverflow.com/a/6300893/367456
$has = consecutive_values($package, $list);
if ($has === -1) break;
$found[] = $name;
array_splice($list, $has, count($package));
}
}
return array($found, $list);
}

Related

multidimensional array counting according to the value

$feetypes=[
[30,35,50],
[30,35],
[30,50],
[30,34]
];
i have this code, want to count as per there value like count 30*4, 35*2, 50*2 & 34*1.
i have already tried:
$counts = array();
foreach ($array as $key=>$subarr) {
// Add to the current group count if it exists
if (isset($counts[$subarr['group']]) {
$counts[$subarr['group']]++;
}
// or initialize to 1 if it doesn't exist
else $counts[$subarr['group']] = 1;
// Or the ternary one-liner version
// instead of the preceding if/else block
$counts[$subarr['group']] = isset($counts[$subarr['group']]) ? $counts[$subarr['group']]++ : 1;
}
but my problem still there
You can merge all the inner arrays into one with array_merge and then use array_count_values to get the counts.
$counts = array_count_values(array_merge(...$feetypes));
Merge all the subarray into a single array and apply array_count_values function to get the result
function merageAll($arr) {
$flatArray = array();
foreach($arr as $element) {
if (is_array($element)) {
$flatArray = array_merge($flatArray, merageAll($element));
} else {
$flatArray[] = $element;
}
}
return $flatArray;
}
$res = array_count_values(merageAll($feetypes));
Result
Array
(
[30] => 4
[35] => 2
[50] => 2
[34] => 1
)
function merageAll work if there are values and sub arrays in the array.
Working DEMO LINK
I used recursion in case values are more than two levels deep. note the $count variable is passed by reference to the function due to recursion.
<?php
$feetypes=[
[30,35,50],
[30,35],
[30,50],
[30,34]
];
$counts = array();
function countValues($arr, &$count) {
foreach($arr as $subval) {
if(is_array($subval)) {
countValues($subval,$count);
} else {
if(isset($count[$subval])) {
$count[$subval] += 1;
} else {
$count[$subval] = 1;
}
}
}
}
countValues($feetypes,$counts);
print_r($counts);

Convert PHP array from XML that contains duplicate elements

Up until now, I've been using the snippet below to convert an XML tree to an array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
..however, I'm now working with an XML that has duplicate key values, so the array is breaking when it loops through the XML. For example:
<users>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
Is there a better method to do this that allows for duplicate Keys, or perhaps a way to add an incremented value to each key when it spits out the array, like this:
$array = array(
users => array(
user_1 => x,
user_2 => y,
user_3 => z
)
)
I'm stumped, so any help would be very appreciated.
Here is a complete universal recursive solution.
This class will parse any XML under any structure, with or without tags, from the simplest to the most complex ones.
It retains all proper values and convert them (bool, txt or int), generates adequate array keys for all elements groups including tags, keep duplicates elements etc etc...
Please forgive the statics, it s part of a large XML tools set I used, before rewriting them all for HHVM or pthreads, I havent got time to properly construct this one, but it will work like a charm for straightforward PHP.
For tags, the declared value is '#attr' in this case but can be whatever your needs are.
$xml = "<body>
<users id='group 1'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
<users id='group 2'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
</body>";
$result = xml_utils::xml_to_array($xml);
result:
Array ( [users] => Array ( [0] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 1 ) ) [1] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 2 ) ) ) )
Class:
class xml_utils {
/*object to array mapper */
public static function objectToArray($object) {
if (!is_object($object) && !is_array($object)) {
return $object;
}
if (is_object($object)) {
$object = get_object_vars($object);
}
return array_map('objectToArray', $object);
}
/* xml DOM loader*/
public static function xml_to_array($xmlstr) {
$doc = new DOMDocument();
$doc->loadXML($xmlstr);
return xml_utils::dom_to_array($doc->documentElement);
}
/* recursive XMl to array parser */
public static function dom_to_array($node) {
$output = array();
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
$child = $node->childNodes->item($i);
$v = xml_utils::dom_to_array($child);
if (isset($child->tagName)) {
$t = xml_utils::ConvertTypes($child->tagName);
if (!isset($output[$t])) {
$output[$t] = array();
}
$output[$t][] = $v;
} elseif ($v) {
$output = (string) $v;
}
}
if (is_array($output)) {
if ($node->attributes->length) {
$a = array();
foreach ($node->attributes as $attrName => $attrNode) {
$a[$attrName] = xml_utils::ConvertTypes($attrNode->value);
}
$output['#attr'] = $a;
}
foreach ($output as $t => $v) {
if (is_array($v) && count($v) == 1 && $t != '#attr') {
$output[$t] = $v[0];
}
}
}
break;
}
return $output;
}
/* elements converter */
public static function ConvertTypes($org) {
if (is_numeric($org)) {
$val = floatval($org);
} else {
if ($org === 'true') {
$val = true;
} else if ($org === 'false') {
$val = false;
} else {
if ($org === '') {
$val = null;
} else {
$val = $org;
}
}
}
return $val;
}
}
You can loop through each key in your result and if the value is an array (as it is for user that has 3 elements in your example) then you can add each individual value in that array to the parent array and unset the value:
foreach($a as $user_key => $user_values) {
if(!is_array($user_values))
continue; //not an array nothing to do
unset($a[$user_key]); //it's an array so remove it from parent array
$i = 1; //counter for new key
//add each value to the parent array with numbered keys
foreach($user_values as $user_value) {
$new_key = $user_key . '_' . $i++; //create new key i.e 'user_1'
$a[$new_key] = $user_value; //add it to the parent array
}
}
var_dump($a);
First of all this line of code contains a superfluous cast to array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
^^^^^^^
When you JSON-encode a SimpleXMLElement (which is returned by simplexml_load_string when the parameter could be parsed as XML) this already behaves as-if there would have been an array cast. So it's better to remove it:
$sxml = simplexml_load_string($xml);
$array = json_decode(json_encode($sxml), 1);
Even the result is still the same, this now allows you to create a subtype of SimpleXMLElement implementing the JsonSerialize interface changing the array creation to your needs.
The overall method (as well as the default behaviour) is outlined in a blog-series of mine, on Stackoverflow I have left some more examples already as well:
PHP convert XML to JSON group when there is one child (Jun 2013)
Resolve namespaces with SimpleXML regardless of structure or namespace (Oct 2014)
XML to JSON conversion in PHP SimpleXML (Dec 2014)
Your case I think is similar to what has been asked in the first of those three links.

extract duplicates and how many times they occur in php array

I am developing a user driven eCommerce website and need some help. What I have is a function that will loop through an array remove duplicates and how many times they occur. I then need to run a function on each of those extracted duplicates as many times as they occur. The code I have so far works, but breaks when there are multiple duplicates with the same repetition count. Here is the code I have made so far..
$affiliates = array(11,11,12,12,13,13,13,14,14,14,14); //breaks the code
$affiliates = array(11,11,13,13,13,14,14,14,14,12,12,12,12,12); // works fine
$array = array();
$match_count = array();
foreach($affiliates as $key => $affiliate) {
$array[] = $affiliate;
}
arsort($array); // keeps array index in order
foreach($array as $arrays) {
if(array_value_count($arrays,$array) > 1) {
$match_count[] = array_value_count($arrays,$array);
}
}
$match_count = array_unique($match_count);
$array_unique = arrayDuplicate($array);
$final_array = array_combine($match_count,$array_unique);
foreach($final_array as $key => $value) {
for($i = 0; $i < $key; $i++) {
echo 'addOrder(affiliate_id = ' . $value . ') . '<br>';
}
}
the functions
function unique_array($array) {
return array_unique($array, SORT_NUMERIC);
}
function arrayDuplicate($array) {
return array_unique(array_diff_assoc($array,array_unique($array)));
}
function array_value_count($match, $array) {
$count = 0;
foreach ($array as $key => $value)
{
if ($value == $match)
{
$count++;
}
}
return $count;
}
to fix the duplicates breaking the code I have tried this
if(count($array_unique) - count($match_count_unique) == 1 ) // do something
or
if(count($array_unique) != count($match_count_unique) == 1 ) // do something
How would I know where to add the missing duplicate value count and array items correctly without them getting out of sync? OR Is there a better way of doing this?
Taken from How do I count occurrence of duplicate items in array
$array = array(12,43,66,21,56,43,43,78,78,100,43,43,43,21);
$vals = array_count_values($array);
echo 'No. of NON Duplicate Items: '.count($vals).'<br><br>';
print_r($vals);
Result
No. of NON Duplicate Items: 7
Array
(
[12] => 1
[43] => 6
[66] => 1
[21] => 2
[56] => 1
[78] => 2
[100] => 1
)
Duplicate items = (Array Size) - (Total Number of Unique Values)
<?php
$affiliates = array(11,11,12,12,13,13,13,14,14,14,14);
// get an array whose keys are the aff# and
//the values are how many times they occur
$dupes = array();
foreach ($affiliates as $aff) {
$dupes[$aff]++;
}
// remove the 1's since those aren't dupes
$dupes = preg_grep('~^1$~',$dupes,PREG_GREP_INVERT);
// de-dupe the original array
$affiliates = array_unique($affiliates);
// for each duped affiliate...
foreach ($dupes as $aff => $affCount) {
// for each time it was duped..
for ($c=0;$c<$affCount;$c++) {
// do something. $aff is the aff# like 11
}
}
?>

How to count the total in array from within a multidimensional array

I have an array which comes from a report.
This report has info similar to:
157479877294,OBSOLETE_ORDER,obelisk,19/01/2013 01:42pm
191532426695,WRONG_PERFORMANCE,g3t1,19/01/2013 01:56pm
159523681637,WRONG_PERFORMANCE,g3t1,19/01/2013 01:57pm
176481653889,WRONG_PERFORMANCE,g4t1,19/01/2013 01:57pm
167479810401,WRONG_PERFORMANCE,g4t1,19/01/2013 02:00pm
172485359309,WRONG_PERFORMANCE,g4t2,19/01/2013 02:02pm
125485358802,WRONG_PERFORMANCE,g4t2,19/01/2013 02:02pm
172485359309,DAY_LIMIT_EXCEEDED,obelisk,19/01/2013 02:03pm
125485358802,DAY_LIMIT_EXCEEDED,obelisk,19/01/2013 02:03pm
What I need to do is get the total of each type of error and the location so for the first would be error: 'OBSOLETE_ORDER' and location: 'obelisk'. I have tried to do this a number of ways but the best I can come up with is a multi dimensional array:
$error_handle = fopen("$reportUrl", "r");
while (!feof($error_handle) )
{
$line_of_text = fgetcsv($error_handle, 1024);
$errorName = $line_of_text[1];
$scannerName = $line_of_text[2];
if($errorName != "SCAN_RESULT" && $errorName != "" && $scannerName != "SCAN_LOGIN" && $scannerName != "")
{
$errorsArray["$errorName"]["$scannerName"]++;
}
}
fclose($error_handle);
print_r($errorsArray);
gives me the following:
Array ( [OBSOLETE_ORDER] => Array ( [obelisk] => 1 ) [WRONG_PERFORMANCE] => Array ( [g3t1] => 2 [g4t1] => 2 [g4t2] => 2 ) [DAY_LIMIT_EXCEEDED] => Array ( [obelisk] => 2 ) )
which is great...except how do I then take that apart to add to my sql database?! (I am interested in getting the key and total of that key under the key the array is under)
and then add it to the tables
-errors-
(index)id_errors
id_event
id_access_scanner
id_errors_type
total_errors
-errors_type-
(index)id_errors_type
name_errors_type
-access_scanner-
(index)id_access_scanner
id_portal
name_access_scanner
PLEASE HELP!
Thanks!
A multidimensional array is more than you need. The approach to take is to create your own string ($arrayKey in my example) to use as an array key that combines the scanner name and the error so that you can get a count.
//this is the array containing all the report lines, each as an array
$lines_of_text;
//this is going to be our output array
$errorScannerArray = array();
//this variable holds the array key that we're going to generate from each line
$arrayKey = null;
foreach($lines_of_text as $line_of_text)
{
//the array key is a combination of the scanner name and the error name
//the tilde is included to attempt to prevent possible (rare) collisions
$arrayKey = trim($line_of_text[1]).'~'.trim($line_of_text[2]);
//if the array key exists, increase the count by 1
//if it doesn't exist, set the count to 1
if(array_key_exists($arrayKey, $errorScannerArray))
{
$errorScannerArray[$arrayKey]++;
}
else
{
$errorScannerArray[$arrayKey] = 1;
}
}
//clean up
unset($line_of_text);
unset($arrayKey);
unset($lines_of_text);
//displaying the result
foreach($errorScannerArray as $errorScanner => $count)
{
//we can explode the string hash to get the separate error and scanner names
$names = explode('~', $errorScanner);
$errorName = $names[0];
$scannerName = $names[1];
echo 'Scanner '.$scannerName.' experienced error '.$errorName.' '.$count.' times'."\n";
}
$list = array();
foreach ($lines as $line) {
$values = explode(',' $line);
$error = $values[1];
$scanner = $values[2];
if (!isset($list[$error])) {
$list[$error] = array();
}
if (!isset($list[$error][$scanner])) {
$list[$error][$scanner] = 1;
} else {
$list[$error][$scanner]++;
}
}
To go through each result I just did the following:
foreach ($errorsArray as $errorName=>$info)
{
foreach ($info as $scannerName=>$total)
{
print "$errorName -> $scannerName = $total </br>";
}
}
and now will just connect it to the sql
With your edited question, this much simpler loop will work for you, you just need to then insert the data into your database inside the loop, instead of echoing it out:
$errorsArray = Array (
[OBSOLETE_ORDER] => Array (
[obelisk] => 1
)
[WRONG_PERFORMANCE] => Array (
[g3t1] => 2
[g4t1] => 2
[g4t2] => 2
)
[DAY_LIMIT_EXCEEDED] => Array (
[obelisk] => 2
)
)
foreach($errorsArray as $row => $errors) {
foreach($errors as $error => $count) {
echo $row; // 'OBSOLETE_ORDER'
echo $error; // 'obelisk'
echo $count; // 1
// insert into database here
}
}
OLD ANSWER
You just need a new array to hold the information you need, ideally a count.
Im assuming that the correct data format is:
$report = [
['157479877294','OBSOLETE_ORDER','obelisk','19/01/2013 01:42pm'],
['191532426695','WRONG_PERFORMANCE','g3t1','19/01/2013 01:56pm'],
['159523681637','WRONG_PERFORMANCE','g3t1','19/01/2013 01:57pm'],
['176481653889','WRONG_PERFORMANCE','g4t1','19/01/2013 01:57pm'],
.....
];
foreach($report as $array) {
$errorName = $array[1];
$scannerName = $array[2];
if(exists($errorsArray[$errorName][$scannerName])) {
$errorsArray[$errorName][$scannerName] = $errorsArray[$errorName][$scannerName] + 1;
}
else {
$errorsArray[$errorName][$scannerName] = 1;
}
}

How do I redistribute an array into another array of a certain "shape". PHP

I have an array of my inventory (ITEMS A & B)
Items A & B are sold as sets of 1 x A & 2 x B.
The items also have various properties which don't affect how they are distributed into sets.
For example:
$inventory=array(
array("A","PINK"),
array("A","MAUVE"),
array("A","ORANGE"),
array("A","GREY"),
array("B","RED"),
array("B","BLUE"),
array("B","YELLOW"),
array("B","GREEN"),
array("B","BLACK")
);
I want to redistribute the array $inventory to create $set(s) such that
$set[0] => Array
(
[0] => array(A,PINK)
[1] => array(B,RED)
[2] => array(B,BLUE)
)
$set[1] => Array
(
[0] => array(A,MAUVE)
[1] => array(B,YELLOW)
[2] => array(B,GREEN)
)
$set[2] => Array
(
[0] => array(A,ORANGE)
[1] => array(B,BLACK)
[2] => NULL
)
$set[3] => Array
(
[0] => array(A,GREY)
[1] => NULL
[2] => NULL
)
As you can see. The items are redistributed in the order in which they appear in the inventory to create a set of 1 x A & 2 x B. The colour doesn't matter when creating the set. But I need to be able to find out what colour went into which set after the $set array is created. Sets are created until all inventory is exhausted. Where an inventory item doesn't exist to go into a set, a NULL value is inserted.
Thanks in advance!
I've assumed that all A's come before all B's:
$inventory=array(
array("A","PINK"),
array("A","MAUVE"),
array("A","ORANGE"),
array("A","GREY"),
array("B","RED"),
array("B","BLUE"),
array("B","YELLOW"),
array("B","GREEN"),
array("B","BLACK")
);
for($b_start_index = 0;$b_start_index<count($inventory);$b_start_index++) {
if($inventory[$b_start_index][0] == 'B') {
break;
}
}
$set = array();
for($i=0,$j=$b_start_index;$i!=$b_start_index;$i++,$j+=2) {
isset($inventory[$j])?$temp1=$inventory[$j]:$temp1 = null;
isset($inventory[$j+1])?$temp2=$inventory[$j+1]:$temp2 = null;
$set[] = array( $inventory[$i], $temp1, $temp2);
}
To make it easier to use your array, you should make it something like this
$inv['A'] = array(
'PINK',
'MAUVE',
'ORANGE',
'GREY'
);
$inv['B'] = array(
'RED',
'BLUE',
'YELLOW',
'GREEN',
'BLACK'
);
This way you can loop through them separately.
$createdSets = $setsRecord = $bTemp = array();
$bMarker = 1;
$aIndex = $bIndex = 0;
foreach($inv['A'] as $singles){
$bTemp[] = $singles;
$setsRecord[$singles][] = $aIndex;
for($i=$bIndex; $i < ($bMarker*2); ++$i) {
//echo $bIndex.' - '.($bMarker*2).'<br/>';
if(empty($inv['B'][$i])) {
$bTemp[] = 'null';
} else {
$bTemp[] = $inv['B'][$i];
$setsRecord[$inv['B'][$i]][] = $aIndex;
}
}
$createdSets[] = $bTemp;
$bTemp = array();
++$bMarker;
++$aIndex;
$bIndex = $bIndex + 2;
}
echo '<pre>';
print_r($createdSets);
print_r($setsRecord);
echo '</pre>';
To turn your array into an associative array, something like this can be done
<?php
$inventory=array(
array("A","PINK"),
array("A","MAUVE"),
array("A","ORANGE"),
array("A","GREY"),
array("B","RED"),
array("B","BLUE"),
array("B","YELLOW"),
array("B","GREEN"),
array("B","BLACK")
);
$inv = array();
foreach($inventory as $item){
$inv[$item[0]][] = $item[1];
}
echo '<pre>';
print_r($inv);
echo '</pre>';
Maybe you can use this function, assuming that:
... $inventory is already sorted (all A come before B)
... $inventory is a numeric array staring at index zero
// $set is the collection to which the generated sets are appended
// $inventory is your inventory, see the assumptions above
// $aCount - the number of A elements in a set
// $bCount - the number of B elements in a set
function makeSets(array &$sets, array $inventory, $aCount, $bCount) {
// extract $aItems from $inventory and shorten $inventory by $aCount
$aItems = array_splice($inventory, 0, $aCount);
$bItems = array();
// iterate over $inventory until a B item is found
foreach($inventory as $index => $item) {
if($item[0] == 'B') {
// extract $bItems from $inventory and shorten $inventory by $bCount
// break out of foreach loop after that
$bItems = array_splice($inventory, $index, $bCount);
break;
}
}
// append $aItems and $bItems to $sets, padd this array with null if
// less then $aCount + $bCount added
$sets[] = array_pad(array_merge($aItems, $bItems), $aCount + $bCount, null);
// if there are still values left in $inventory, call 'makeSets' again
if(count($inventory) > 0) makeSets($sets, $inventory, $aCount, $bCount);
}
$sets = array();
makeSets($sets, $inventory, 1, 2);
print_r($sets);
Since you mentioned that you dont have that much experience with arrays, here are the links to the php documentation for the functions I used in the above code:
array_splice — Remove a portion of the array and replace it with something else
array_merge — Merge one or more arrays
array_pad — Pad array to the specified length with a value
This code sorts inventory without any assumption on inventory ordering. You can specify pattern (in $aPattern), and order is obeyed. It also fills lacking entries with given default value.
<?php
# config
$aInventory=array(
array("A","PINK"),
array("A","MAUVE"),
array("A","ORANGE"),
array("A","GREY"),
array("B","RED"),
array("B","BLUE"),
array("B","YELLOW"),
array("B","GREEN"),
array("B","BLACK"),
array("C","cRED"),
array("C","cBLUE"),
array("C","cYELLOW"),
array("C","cGREEN"),
array("C","cBLACK")
);
$aPattern = array('A','B','A','C');
$mDefault = null;
# preparation
$aCounter = array_count_values($aPattern);
$aCurrentCounter = $aCurrentIndex = array_fill_keys(array_unique($aPattern),0);
$aPositions = array();
$aFill = array();
foreach ($aPattern as $nPosition=>$sElement){
$aPositions[$sElement] = array_keys($aPattern, $sElement);
$aFill[$sElement] = array_fill_keys($aPositions[$sElement], $mDefault);
} // foreach
$nTotalLine = count ($aPattern);
$aResult = array();
# main loop
foreach ($aInventory as $aItem){
$sElement = $aItem[0];
$nNeed = $aCounter[$sElement];
$nHas = $aCurrentCounter[$sElement];
if ($nHas == $nNeed){
$aCurrentIndex[$sElement]++;
$aCurrentCounter[$sElement] = 1;
} else {
$aCurrentCounter[$sElement]++;
} // if
$nCurrentIndex = $aCurrentIndex[$sElement];
if (!isset($aResult[$nCurrentIndex])){
$aResult[$nCurrentIndex] = array();
} // if
$nCurrentPosition = $aPositions[$sElement][$aCurrentCounter[$sElement]-1];
$aResult[$nCurrentIndex][$nCurrentPosition] = $aItem;
} // foreach
foreach ($aResult as &$aLine){
if (count($aLine)<$nTotalLine){
foreach ($aPositions as $sElement=>$aElementPositions){
$nCurrentElements = count(array_keys($aLine,$sElement));
if ($aCounter[$sElement] != $nCurrentElements){
$aLine = $aLine + $aFill[$sElement];
} // if
} // foreach
} // if
ksort($aLine);
# add empty items here
} // foreach
# output
var_dump($aResult);
Generic solution that requires you to specify a pattern of the form
$pattern = array('A','B','B');
The output will be in
$result = array();
The code :
// Convert to associative array
$inv = array();
foreach($inventory as $item)
$inv[$item[0]][] = $item[1];
// Position counters : int -> int
$count = array_fill(0, count($pattern),0);
$out = 0; // Number of counters that are "out" == "too far"
// Progression
while($out < count($count))
{
$elem = array();
// Select and increment corresponding counter
foreach($pattern as $i => $pat)
{
$elem[] = $inv[ $pat ][ $count[$i]++ ];
if($count[$i] == count($inv[$pat]))
$out++;
}
$result[] = $elem;
}

Categories