Grouping array based on the key value similarity - php

Let's say I have an array like that:
$data[0]['name'] = 'product 1 brandX';
$data[0]['id_product'] = '77777777';
$data[1]['name'] = 'brandX product 1';
$data[1]['id_product'] = '77777777';
$data[2]['name'] = 'brandX product 1 RED';
$data[2]['id_product'] = '77777777';
$data[3]['name'] = 'product 1 brandX';
$data[3]['id_product'] = '';
$data[4]['name'] = 'product 2 brandY';
$data[4]['id_product'] = '8888888';
$data[5]['name'] = 'product 2 brandY RED';
$data[5]['id_product'] = '';
I am trying to group them by their similarities (name or id_product).
That would be the expected final array:
$uniques[0]['name'] = 'product 1 brandX'; //The smallest name for the product
$uniques[0]['count'] = 4; //Entry which has all the words of the smallest name or the same id_product
$uniques[0]['name'] = 'product 2 brandY';
$uniques[0]['count'] = 2;
That's what I tried so far:
foreach ($data as $t) {
if (!isset($uniques[$t['id_product']]['name']) || mb_strlen($uniques[$t['id_product']]['name']) > mb_strlen($t['name'])) {
$uniques[$t['id_product']]['name'] = $t['name'];
$uniques[$t['id_product']]['count']++;
}
}
But I can't be based on the id_product because sometimes it will be the same product but one will have the id and the other one no. I have to check the name also, but couldn't manage to get it done.

I'm basing my answer on two assumptions about how products should be grouped:
Although id_product can be missing, where it is present, it is
correct and sufficient to match two products; and
For two product names to match, the longest name (name with the most
words) must contain all of the words in the shortest name (name with
the fewest words).
Given these assumptions, here is a function to determine if two individual products match (products should be grouped together) and a helper function to get words from names:
function productsMatch(array $product1, array $product2)
{
if (
!empty($product1['id_product'])
&& !empty($product2['id_product'])
&& $product1['id_product'] === $product2['id_product']
) {
// match based on id_product
return true;
}
$words1 = getWordsFromProduct($product1);
$words2 = getWordsFromProduct($product2);
$min_word_count = min(count($words1), count($words2));
$match_word_count = count(array_intersect_key($words1, $words2));
if ($min_word_count >= 1 && $match_word_count === $min_word_count) {
// match based on name similarity
return true;
}
// no match
return false;
}
function getWordsFromProduct(array $product)
{
$name = mb_strtolower($product['name']);
preg_match_all('/\S+/', $name, $matches);
$words = array_flip($matches[0]);
return $words;
}
This function can be used to group the products:
function groupProducts(array $data)
{
$groups = array();
foreach ($data as $product1) {
foreach ($groups as $key => $products) {
foreach ($products as $product2) {
if (productsMatch($product1, $product2)) {
$groups[$key][] = $product1;
continue 3; // foreach ($data as $product1)
}
}
}
$groups[] = array($product1);
}
return $groups;
}
And then this function can be used to extract the shortest name and count for each group:
function uniqueProducts(array $groups)
{
$uniques = array();
foreach ($groups as $products) {
$shortest_name = '';
$shortest_length = PHP_INT_MAX;
$count = 0;
foreach ($products as $product) {
$length = mb_strlen($product['name']);
if ($length < $shortest_length) {
$shortest_name = $product['name'];
$shortest_length = $length;
}
$count++;
}
$uniques[] = array(
'name' => $shortest_name,
'count' => $count,
);
}
return $uniques;
}
So combining all 4 functions, you can get the uniques as follows (tested with php 5.6):
$data[0]['name'] = 'product 1 brandX';
$data[0]['id_product'] = '77777777';
$data[1]['name'] = 'brandX product 1';
$data[1]['id_product'] = '77777777';
$data[2]['name'] = 'brandX product 1 RED';
$data[2]['id_product'] = '77777777';
$data[3]['name'] = 'product 1 brandX';
$data[3]['id_product'] = '';
$data[4]['name'] = 'product 2 brandY';
$data[4]['id_product'] = '8888888';
$data[5]['name'] = 'product 2 brandY RED';
$data[5]['id_product'] = '';
$groups = groupProducts($data);
$uniques = uniqueProducts($groups);
var_dump($uniques);
Which gives output:
array(2) {
[0]=>
array(2) {
["name"]=>
string(16) "product 1 brandX"
["count"]=>
int(4)
}
[1]=>
array(2) {
["name"]=>
string(16) "product 2 brandY"
["count"]=>
int(2)
}
}

I don't think this will solve your problem, but it may get you moving forward again
$data = [];
$data[0]['name'] = 'product 1 brandX';
$data[0]['id_product'] = '77777777';
$data[1]['name'] = 'brandX product 1';
$data[1]['id_product'] = '77777777';
$data[2]['name'] = 'brandX product 1 RED';
$data[2]['id_product'] = '77777777';
$data[3]['name'] = 'product 1 brandX';
$data[3]['id_product'] = '';
$data[4]['name'] = 'product 2 brandY';
$data[4]['id_product'] = '8888888';
$data[5]['name'] = 'product 2 brandY RED';
$data[5]['id_product'] = '';
$data = collect($data);
$tallies = [
'brand_x' => 0,
'brand_y' => 0,
'other' => 0
];
$unique = $data->unique(function ($item) use (&$tallies){
switch(true){
case(strpos($item['name'], 'brandX') !== false):
$tallies['brand_x']++;
return 'product X';
break;
case(strpos($item['name'], 'brandY') !== false):
$tallies['brand_y']++;
return 'product Y';
break;
default:
$tallies['other']++;
return 'other';
break;
}
});
print_r($unique);
print_r($tallies);

I think the best way to solve this is to use a unique product_id, but if you want create unique keys by finding similarities in the name field you can use preg_split to transform names to arrays and then use array_diff to find an array of difference. 2 names are considered unique if their difference count is lower than 2. I create this function it returns similar names in $arr or false if not found:
function get_similare_key($arr, $name) {
$names = preg_split("/\s+/", $name);
// get similaire key from $arr
foreach( $arr as $key => $value ) {
$key_names = preg_split("/\s+/", $key);
$diff = array_diff($key_names, $names);
if ( count($diff) <= 1 ) {
return $key;
}
}
return false;
}
Here is a working demo here

Related

Counting rows by array_push

I'm a bit stuck here
I have an array that I'm exporting with laravel excel and I'm basically creating a category row and then all subsequent item rows that belong to that category.
How can I properly add a counter for every array_push to the groupItem array so that I can count every row between the groupItem push and set the row with the category info to bold?
Basically I only want to bold the row that has the data for category_code, category_name and category_desc so I would need to iterate based on the array_push for the category info I believe
I think I would need to set a count, increase count for the categoryItem array_push, store that count in an array and then set those array stored rows to bold?
$allgroupResult= array();
foreach($prices->groups as $group){
$groupItem = array();
$groupItem["category_code"] = $group->category_code;
$groupItem["category_name"] = $group->category_name;
$groupItem["category_desc"] = $group->category_desc;
array_push($allgroupResult, $groupItem);
foreach($group->skus as $sku){
$skuItem = array();
$skuItem["item_code"] = $sku->sku_info->item->item_code;
$skuItem["identifier"] = $sku->sku_info->identifier;
foreach($sku->prices as $price => $amount){
$skuItem[] = $amount;
}
$skuItem[] = strip_tags(html_entity_decode($sku->sku_info->item->desc));
foreach ($sku->sku_info->details as $details) {
$skuItem[] = $details->details1;
$skuItem[] = $details->details2;
$skuItem[] = $details->details3;
}
array_push($allgroupResult, $skuItem);
}
}
$name = 'File Export';
$build = Excel::create($name, function ($excel) use ($allgroupResult) {
$excel->setTitle('File Export');
$excel->sheet('File Export', function ($sheet) use ($allgroupResult) {
$sheet->fromArray($allgroupResult);
// bold the column headers
$sheet->getStyle('A1:'.$sheet->getHighestColumn().'1')->getFont()->setBold(true);
// $count = 2;
// foreach($excelRows as $one){
// $sheet->fromArray($one, null, 'A2');
// $sheet->row($count, function($row) {
// $row->setFontWeight('bold');
// });
// $count += count( $one ) + 1;
// }
// set the width for the columns that are used
$sheet->setWidth('A', 10);
$sheet->setWidth('B', 24);
$sheet->setWidth('C', 20);
$sheet->setWidth('D', 12);
$sheet->setWidth('E', 10);
$sheet->setWidth('F', 16);
$sheet->setWidth('G', 16);
$sheet->setWidth('H', 16);
$sheet->setWidth('I', 16);
$sheet->setWidth('J', 16);
$sheet->setWidth('K', 16);
});
})->download('xlsx');
Why not creating another folded array inside $allgroupResult for each category, so having structure like this:
array(1) {
[0] =>
array(4) {
'category_code' =>
string(13) "category_code"
'category_name' =>
string(13) "category_name"
'category_desc' =>
string(13) "category_desc"
'skus' =>
array(3) {
[0] =>
string(4) "sku1"
[1] =>
string(4) "sku2"
[2] =>
string(4) "sku3"
}
}
}
and then you can just do count($item['skus']) whenever you need to get the number of products in every category. In order to do this , try the following modification to your foreach loop:
foreach($prices->groups as $group){
$groupItem = array();
$groupItem["category_code"] = $group->category_code;
$groupItem["category_name"] = $group->category_name;
$groupItem["category_desc"] = $group->category_desc;
$groupItem["skus"] = array();
foreach($group->skus as $sku){
$skuItem = array();
$skuItem["item_code"] = $sku->sku_info->item->item_code;
$skuItem["identifier"] = $sku->sku_info->identifier;
foreach($sku->prices as $price => $amount){
$skuItem[] = $amount;
}
$skuItem[] = strip_tags(html_entity_decode($sku->sku_info->item->desc));
foreach ($sku->sku_info->details as $details) {
$skuItem[] = $details->details1;
$skuItem[] = $details->details2;
$skuItem[] = $details->details3;
}
$groupItem["skus"][] = $skuItem;
}
$allgroupResult[] = $groupItem;
}

Check if postcode is in multiple arrays?

I have 3 different areas, MyArea1, MyArea2, MyArea3. Each of these have an array of postcodes:
$myArea1=array("AB1","AC2","AD1");
$myArea2=array("BC1","BC2","BC3");
$myArea3=array("CD1","CD2","CD3");
The postcodes I need to check would be like the following "BC2 4YZ".
The following won't work as it checks for a complete match:
if (in_array($row['postcode'], $myArea1)) { $Area = 'Area 1'; }
else if (in_array($row['postcode'], $myArea2)) { $Area = 'Area 2'; }
else if (in_array($row['postcode'], $myArea3)) { $Area = 'Area 3'; }
else { $Area = 'No Match'; }
I've got the following that works:
foreach ($myArea1 as $myArea1pc) {
if (strpos($row['postcode'],$myArea1pc) !== false) {
$Area = 'Area 1';
}
}
However is there a way of looping this for all of my arrays?
Edit: There may sometimes be postcodes such as "BC24" in my arrays.
I would try to use in_array for this.
$input = $row['postcode'];
$areas = array(
'Area 1' => array("AB1","AC2","AD1"),
'Area 2' => array("BC1","BC2","BC3"),
'Area 3' => array("CD1","CD2","CD3"),
);
// Take out the first 3 letter:
$part = substr(trim(str_replace(' ', '', $input)), 0, -3);
$Area = 'No match';
// Find which "area" array contains it
foreach ($areas as $k => $v) {
if (in_array($part, $v)) {
$Area = $k;
break;
}
}

Create multidimensional array from keys

Is there a way to create dynamicaly multidimensional array? I have stored in database "path" for each field=>value like that:
field_name : [slideshow][slide][0][title]
field_value : my title
field_name : [slideshow][slide][0][desc]
field_value : my desc
field_name : [slideshow][slide][1][title]
field value : my other title
field_name : [slideshow][slide][1][desc]
field value : my other desc
field_name : [slideshow][settings][duration]
field value : 300
and now I'm trying to figure out how to make it an array again. Obviously there can be lots of fields and complexity so I wanted to avoid some recursions if possible, cause I'm not sure how it will impact performance.
I was playing around with variable variables and trying something like:
$array_name = 'arr';
${$array_name}[slideshow][slide][1][title] = $field->field_value;
print_r($arr);
but this works only if its literally that, and nothing like this works:
${$array_name}.$field->field_name = $field->field_value;
I basically need to store every field as individual row (e.g. for searches in those fields), values can be diffrent types (even serialized arrays), and contain html.
Any advice appreciate.
The basic idea is to split up your field_name string and loop over the parts backward to build up the array. Some recursion is used to merge the arrays, though any performance impact should be negligible.
Example:
// Set up sample data.
$field = new stdClass();
$field->field_name = '[slideshow][slide][0][title]';
$field->field_value = 'my title';
$fields[] = $field;
$field = new stdClass();
$field->field_name = '[slideshow][slide][0][desc]';
$field->field_value = 'my desc';
$fields[] = $field;
$field = new stdClass();
$field->field_name = '[slideshow][slide][1][title]';
$field->field_value = 'my other title';
$fields[] = $field;
$field = new stdClass();
$field->field_name = '[slideshow][slide][1][desc]';
$field->field_value = 'my other desc';
$fields[] = $field;
$field = new stdClass();
$field->field_name = '[slideshow][settings][duration]';
$field->field_value = '300';
$fields[] = $field;
// End sample data.
// array_merge_recursive() doesn't do what we want with numeric keys, so use this
function merge($base, $array) {
foreach ($array as $key => $value) {
if (isset($base[$key]) && is_array($base[$key]) && is_array($value)) {
$base[$key] = merge($base[$key], $value);
} else {
$base[$key] = $value;
}
}
return $base;
}
$result = [];
foreach ($fields as $field) {
$parts = array_reverse(explode('][', trim($field->field_name, '[]')));
$value = $field->field_value;
foreach ($parts as $part) {
$value = [$part => $value];
}
$result = merge($result, $value);
}
print_r($result);
Output:
Array
(
[slideshow] => Array
(
[slide] => Array
(
[0] => Array
(
[title] => my title
[desc] => my desc
)
[1] => Array
(
[title] => my other title
[desc] => my other desc
)
)
[settings] => Array
(
[duration] => 300
)
)
)
You could try something like this.
$cars = array
(
array("Volvo",22,18),
array("BMW",15,13),
array("Saab",5,2),
array("Land Rover",17,15)
);
<?php
echo $cars[0][0].": In stock: ".$cars[0][1].", sold: ".$cars[0][2].".<br>";
echo $cars[1][0].": In stock: ".$cars[1][1].", sold: ".$cars[1][2].".<br>";
echo $cars[2][0].": In stock: ".$cars[2][1].", sold: ".$cars[2][2].".<br>";
echo $cars[3][0].": In stock: ".$cars[3][1].", sold: ".$cars[3][2].".<br>";
?>
<?php
for ($row = 0; $row < 4; $row++) {
echo "<p><b>Row number $row</b></p>";
echo "<ul>";
for ($col = 0; $col < 3; $col++) {
echo "<li>".$cars[$row][$col]."</li>";
}
echo "</ul>";
}
?>

Create array nested PHP

Hi all' I have a page into PHP where I retrieve XML data from a server and I want to store this data into an array.
This is my code:
foreach ($xml->DATA as $entry){
foreach ($entry->HOTEL_DATA as $entry2){
$id = (string)$entry2->attributes()->HOTEL_CODE;
$hotel_array2 = array();
$hotel_array2['id'] = $entry2->ID;
$hotel_array2['name'] = utf8_decode($entry2->HOTEL_NAME);
$i=0;
foreach($entry2->ROOM_DATA as $room){
$room_array = array();
$room_array['id'] = (string)$room->attributes()->CCHARGES_CODE;
$hotel_array2['rooms'][$i] = array($room_array);
$i++;
}
array_push($hotel_array, $hotel_array2);
}
}
In this mode I have the array hotel_array which all hotel with rooms.
The problem is that: into my XML I can have multiple hotel with same ID (the same hotel) with same information but different rooms.
If I have an hotel that I have already inserted into my hotel_array I don't want to insert a new array inside it but I only want to take its rooms array and insert into the exisiting hotel.
Example now my situation is that:
hotel_array{
[0]{
id = 1,
name = 'test'
rooms{
id = 1
}
}
[0]{
id = 2,
name = 'test2'
rooms{
id = 100
}
}
[0]{
id = 1,
name = 'test'
rooms{
id = 30
}
}
}
I'd like to have this result instead:
hotel_array{
[0]{
id = 1,
name = 'test'
rooms{
[0]{
id = 1
}
[1]{
id = 30
}
}
}
[0]{
id = 2,
name = 'test2'
rooms{
id = 100
}
}
}
How to create an array like this?
Thanks
first thing is it helps to keep the hotel id as the index on hotel_array when your creating it.
foreach ($xml->DATA as $entry){
foreach ($entry->HOTEL_DATA as $entry2){
$id = (string)$entry2->attributes()->HOTEL_CODE;
$hotel_array2 = array();
$hotel_array2['id'] = $entry2->ID;
$hotel_array2['name'] = utf8_decode($entry2->HOTEL_NAME);
$i=0;
foreach($entry2->ROOM_DATA as $room){
$room_array = array();
$room_array['id'] = (string)$room->attributes()->CCHARGES_CODE;
$hotel_array2['rooms'][$i] = array($room_array);
$i++;
}
if (!isset($hotel_array[$hotel_array2['id']])) {
$hotel_array[$hotel_array2['id']] = $hotel_array2;
} else {
$hotel_array[$hotel_array2['id']]['rooms'] = array_merge($hotel_array[$hotel_array2['id']]['rooms'], $hotel_array2['rooms']);
}
}
}
Whilst this is the similar answer to DevZer0 (+1), there is also quite a bit that can be done to simplify your workings... there is no need to use array_merge for one, or be specific about $i within your rooms array.
$hotels = array();
foreach ($xml->DATA as $entry){
foreach ($entry->HOTEL_DATA as $entry2){
$id = (string) $entry2->attributes()->HOTEL_CODE;
if ( empty($hotels[$id]) ) {
$hotels[$id] = array(
'id' => $id,
'name' => utf8_decode($entry2->HOTEL_NAME),
'rooms' => array(),
);
}
foreach($entry2->ROOM_DATA as $room){
$hotels[$id]['rooms'][] = array(
'id' => (string) $room->attributes()->CCHARGES_CODE;
);
}
}
}
Just in case it helps...
And this :)
$hotel_array = array();
foreach ($xml->DATA as $entry)
{
foreach ($entry->HOTEL_DATA as $entry2)
{
$hotel_code = (string) $entry2->attributes()->HOTEL_CODE;
if (false === isset($hotel_array[$hotel_code]))
{
$hotel = array(
'id' => $entry2->ID,
'code' => $hotel_code,
'name' => utf8_decode($entry2->HOTEL_NAME)
);
foreach($entry2->ROOM_DATA as $room)
{
$hotel['rooms'][] = array(
'id' => (string)$room->attributes()->CCHARGES_CODE,
);
}
$hotel_array[$hotel_code] = $hotel;
}
}
}

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