PHP Array - Count Partial Duplicates - php

I have an array of objects.
I want to count the total number of array items where two values are an exact match.
Array
(
[0] => stdClass Object
(
[Job] => stdClass Object
(
[ID] => 123
[Line] => Shirt
[Color] => Blue
)
)
[1] => stdClass Object
(
[Job] => stdClass Object
(
[ID] => 456
[Line] => Jeans
[Color] => Blue
)
)
[2] => stdClass Object
(
[Job] => stdClass Object
(
[ID] => 789
[Line] => Jeans
[Color] => Blue
)
)
)
In this simplified example I want to Count that there are 2 array items that have Blue Jeans.

Probably the best approach is to use an index as one would do inside a database:
<?php
$json = <<<JSON
[
{"Job":{"ID":123,"Line":"Shirt","Color":"Blue"}},
{"Job":{"ID":456,"Line":"Jeans","Color":"Blue"}},
{"Job":{"ID":789,"Line":"Jeans","Color":"Blue"}}
]
JSON;
$data = json_decode($json);
$index = [];
$counter = 0;
array_walk($data, function(\stdClass $entry) use (&$index, &$counter) {
$key = $entry->Job->Line . '|' . $entry->Job->Color;
if (!in_array($key, $index)) {
$index[] = $key;
} else {
$counter++;
}
});
print_r($counter);
The output obviously is:
1

Just use a good old loop.
$count = 0; //Initialize the number of matches to 0
//Loop through each item (obviously)
foreach($array as $item) {
//Check if the required properties match.
//If they do, increment $count by 1
if($item->Job->Line=="Jeans" && $item->Job->Color=="Blue") ++$count;
}
//Do whatever is required with the value of $count

Here is another approach using simple recursion. You can set the number of matches desired, and it will find matches for any property (nothing is fixed). I wrapped it in a class to keep everything contained:
<?php
class MatchFind {
// This can be set to any number of matches desired
const ELEMENTS_TO_FIND = 2;
public static function count ($aObjs) {
// Cannot compare elements unless there are 2
if (sizeof($aObjs) <= 1) {
return 0;
}
// Get the top element from the array
$oTop = array_shift($aObjs);
$aTopProps = (array) $oTop->Job;
// Get the number of matches, moving from end to start,
// removing each match from the array
$iMatchObjs = 0;
for ($n = sizeof($aObjs); $n > 0; $n--) {
$oCompare = $aObjs[$n-1];
$iMatch = 0;
foreach ((array) $oCompare->Job as $sKey => $sValue) {
if (isset($aTopProps[$sKey]) && $aTopProps[$sKey] === $sValue) {
++$iMatch;
}
}
if ($iMatch >= self::ELEMENTS_TO_FIND) {
unset($aObjs[$n-1]);
++$iMatchObjs;
}
}
reset($aObjs);
return ($iMatchObjs + self::count($aObjs));
}
}
// Declare the objects
$aAllObjs = [
(object)[ 'Job' => (object)['ID' => 123,
'Line' => 'Shirt',
'Color' => 'Blue'] ],
(object)[ 'Job' => (object)['ID' => 456,
'Line' => 'Jeans',
'Color' => 'Blue'] ],
(object)[ 'Job' => (object)['ID' => 789,
'Line' => 'Jeans',
'Color' => 'Blue'] ],
];
echo MatchFind::count($aAllObjs);

Related

php remove empty 'columns' in multidimensional, associative array

Goal: Generate an array that includes only those 'columns' with data, even though a 'header' may exist.
Example Data:
Array (
[HeaderRow] => Array (
[0] => Employee [1] => LaborHours [2] => 0.1 [3] => 0.25 [4] => 0.5 [5] => 0.8
)
[r0] => Array (
[0] => Joe [1] => 5 [2] => [3] => [4] => 50 [5] =>
)
[r1] => Array (
[0] => Fred [1] => 5 [2] => 10 [3] => [4] => [5] =>
)
)
Desired Output:
Array (
[HeaderRow] => Array (
[0] => Employee [1] => LaborHours [2] => 0.1 [4] => 0.5
)
[r0] => Array (
[0] => Joe [1] => 5 [2] => [4] => 50
)
[r1] => Array (
[0] => Fred [1] => 5 [2] => 10 [4] =>
)
)
So, in this very dumbed down example, the HeaderRow will always have data, but if both c0 and c1 are empty (as is the case for [3] and [5]) then I want to remove. I tried iterating through with for loops like I would in other languages, but that apparently doesn't work with associative arrays. I then tried doing a transpose followed by two foreach loops, but that failed me as well. Here's a sample of my for loop attempt:
Attempt with For Loop
for ($j = 0; $j <= count(reset($array))-1; $j++) {
$empty = true;
for ($i = 1; $i <= count($array)-1; $i++) {
if(!empty($array[$i][$j])) {
$empty = false;
break;
}
}
if ($empty === true)
{
for ($i = 0; $i <= count($array); $i++) {
unset($array[$i][$j]);
}
}
}
return $array;
Attempt with transpose:
$array = transpose($array);
foreach ($array as $row)
{
$empty = true;
foreach ($row as $value)
{
if (!empty($value))
{
$empty = false;
}
}
if ($empty) {
unset($array[$row]);
}
}
$array = transpose($array);
return $array;
function transpose($arr) {
$out = array();
foreach ($arr as $key => $subarr) {
foreach ($subarr as $subkey => $subvalue) {
$out[$subkey][$key] = $subvalue;
}
}
return $out;
}
I know the transpose one isn't terribly fleshed out, but I wanted to demonstrate the attempt.
Thanks for any insight.
We can make this more simpler. Just get all column values using array_column.
Use array_filter with a custom callback to remove all empty string values.
If after filtering, size of array is 0, then that key needs to be unset from all
subarrays.
Note: The arrow syntax in the callback is introduced since PHP 7.4.
Snippet:
<?php
$data = array (
'HeaderRow' => Array (
'0' => 'Employee','1' => 'LaborHours', '2' => 0.1, '3' => 0.25, '4' => 0.5, '5' => 0.8
),
'r0' => Array (
'0' => 'Joe', '1' => 5, '2' => '','3' => '', '4' => 50, '5' => ''
),
'r1' => Array (
'0' => 'Fred', '1' => 5,'2' => 10, '3' => '', '4' => '', '5' => ''
)
);
$cleanup_keys = [];
foreach(array_keys($data['HeaderRow']) as $column_key){
$column_values = array_column($data, $column_key);
array_shift($column_values); // removing header row value
$column_values = array_filter($column_values,fn($val) => strlen($val) != 0);
if(count($column_values) == 0) $cleanup_keys[] = $column_key;
}
foreach($data as &$row){
foreach($cleanup_keys as $ck){
unset($row[ $ck ]);
}
}
print_r($data);
It figures, I work on this for a day and have a moment of clarity right after posting. The answer was that I wasn't leveraging the Keys.:
function array_cleanup($array)
{
$array = transpose($array);
foreach ($array as $key => $value)
{
$empty = true;
foreach ($value as $subkey => $subvalue)
{
if ($subkey != "HeaderRow") {
if (!empty($subvalue))
{
$empty = false;
}
}
}
if ($empty) {
unset($array[$key]);
}
}
$array = transpose($array);
return $array;
}

PHP - combine values from Objectlist with same key

I have an objectlist:
$deliveryOptions =
Array ( [0] => stdClass Object ( [item_id] => 55 [value] => delivery-online )
[1] => stdClass Object ( [item_id] => 55 [value] => delivery-campus )
[2] => stdClass Object ( [item_id] => 56 [value] => delivery-campus )
[3] => stdClass Object ( [item_id] => 81 [value] => delivery-blended )
)
I need to format it to an array:
$combined =
( [item_id] => 55 [course-delivery] => array( "delivery-online","delivery-campus")
( [item_id] => 56 [course-delivery] => delivery-campus )
( [item_id] => 81 [course-delivery] => delivery-blended )
My code so far:
foreach ($deliveryOptions as $row)
{
$temp = array('item_id'=>$row->item_id,
'course-delivery'=>$row->value
);
$course[] = $temp;
}
foreach ($course as $row)
{
$match = array_search($row['item_id'], array_column($combined, 'item_id'));
if(is_numeric($match))
{
$combined[$match]['course-delivery'][] = $row['course-delivery'];
}
else{
array_push($combined, [
'item_id' => $row['item_id'],
'course-delivery' => array($row['course-delivery'])
]);
}
}
The format of $combined might seem odd, but I have three different queries creating different object lists that all need to be combined into one JSON array based on 'item_id' as the key.
I have the part where all three get combined working, this new array configuration comes from a checkbox situation, thus the need to combine the different values off the same item_id.
No need for another foreach, you just create the structure along the way. First, initialize the container for the particular item_id.
When an item_id hits again and is not an array, just overwrite it, use the first value (string) and turn it to an array and finally push the value.
$deliveryOptions = [
(object) ['item_id' => 55, 'value' => 'delivery-online'],
(object) ['item_id' => 55, 'value' => 'delivery-campus'],
(object) ['item_id' => 56, 'value' => 'delivery-campus'],
(object) ['item_id' => 81, 'value' => 'delivery-blended'],
];
$combined = [];
foreach ($deliveryOptions as $row) {
if (!isset($combined[$row->item_id])) { // initialize if it doesn't exist
$combined[$row->item_id] = (array) $row; continue;
}
if (!is_array($combined[$row->item_id]['value'])) { // if another occurence
$temp = $combined[$row->item_id]['value']; // get the string initial value
$combined[$row->item_id]['value'] = []; // turn it into an array
$combined[$row->item_id]['value'][] = $temp; // and reassign and push inside the array
}
$combined[$row->item_id]['value'][] = $row->value; // push the value in the array
}
// $combined = array_values($combined); // array key reindex if needed
Sample output

How to find the position of key value in an array

I have an array with user id and transaction details sorted based on transaction. Now I need to find the position of key value
Array
(
[0] => Array
(
[userid] => 3
[transaction] => 1878
)
[1] => Array
(
[userid] => 2
[transaction] => 416
)
[2] => Array
(
[userid] => 5
[transaction] => 353
)
[3] => Array
(
[userid] => 4
[transaction] => 183
)
)
When I give user id 4 then it should return 3
First, use array_column() to fetch all userid, then use array_search() to retrieve array key.
$searchId = 4;
echo array_search( $searchId, array_column($array, 'userid') ) );
I might just iterate the outer array here and the check the key values:
$pos = -1;
for ($i = 0; $i < count($array); $i++) {
if ($array[$i]['userid'] == 4) {
$pos = $i;
}
}
if ($pos == -1) {
echo "user not found";
}
else {
echo "user found at position " . $pos;
}
I prefer Calos's answer.
The code below imitates JavaScript's Array.prototype.findIndex to achieve what you need. Using this approach you can have more flexibility in searching the array by using a callback function, similar to how JavaScript has done it.
You can also easily reuse it in other parts of your code.
$data = [
[
'userid' => 3,
'transaction' => 1878
],
[
'userid' => 2,
'transaction' => 416
],
[
'userid' => 5,
'transaction' => 353
],
[
'userid' => 4,
'transaction' => 183
]
];
function findIndex($array, $method){
foreach($array as $index => $value){
if ($method($value, $index, $array) === true){
return $index;
}
}
}
echo findIndex($data, function($arr){
return $arr['userid'] == 5;
});
It's trivial to build your own map:
<?php
$items =
[
'foo' =>
[
'id' => 23,
],
'bar' =>
[
'id' => 47
]
];
foreach($items as $k=>$v)
$ids_keys[$v['id']] = $k;
echo $ids_keys[47];
Output:
bar

PHP Counting inside an Array

I want to create a list where if its already in the array to add to the value +1.
Current Output
[1] => Array
(
[source] => 397
[value] => 1
)
[2] => Array
(
[source] => 397
[value] => 1
)
[3] => Array
(
[source] => 1314
[value] => 1
)
What I want to Achieve
[1] => Array
(
[source] => 397
[value] => 2
)
[2] => Array
(
[source] => 1314
[value] => 1
)
My current dulled down PHP
foreach ($submissions as $timefix) {
//Start countng
$data = array(
'source' => $timefix['parent']['id'],
'value' => '1'
);
$dataJson[] = $data;
}
print_r($dataJson);
Simply use an associated array:
$dataJson = array();
foreach ($submissions as $timefix) {
$id = $timefix['parent']['id'];
if (!isset($dataJson[$id])) {
$dataJson[$id] = array('source' => $id, 'value' => 1);
} else {
$dataJson[$id]['value']++;
}
}
$dataJson = array_values($dataJson); // reset the keys - you don't nessesarily need this
This is not exactly your desired output, as the array keys are not preserved, but if it suits you, you could use the item ID as the array key. This would simplify your code to the point of not needing to loop through the already available results:
foreach ($submissions as $timefix) {
$id = $timefix['parent']['id'];
if (array_key_exists($id, $dataJson)) {
$dataJson[$id]["value"]++;
} else {
$dataJson[$id] = [
"source" => $id,
"value" => 1
];
}
}
print_r($dataJson);
You should simplify this for yourself. Something like:
<?
$res = Array();
foreach ($original as $item) {
if (!isset($res[$item['source']])) $res[$item['source']] = $item['value'];
else $res[$item['source']] += $item['value'];
}
?>
After this, you will have array $res which will be something like:
Array(
[397] => 2,
[1314] => 1
)
Then, if you really need the format specified, you can use something like:
<?
$final = Array();
foreach ($res as $source=>$value) $final[] = Array(
'source' => $source,
'value' => $value
);
?>
This code will do the counting and produce a $new array as described in your example.
$data = array(
array('source' => 397, 'value' => 1),
array('source' => 397, 'value' => 1),
array('source' => 1314, 'value' => 1),
);
$new = array();
foreach ($data as $item)
{
$source = $item['source'];
if (isset($new[$source]))
$new[$source]['value'] += $item['value'];
else
$new[$source] = $item;
}
$new = array_values($new);
PHP has a function called array_count_values for that. May be you can use it
Example:
<?php
$array = array(1, "hello", 1, "world", "hello");
print_r(array_count_values($array));
?>
Output:
Array
(
[1] => 2
[hello] => 2
[world] => 1
)

How to output an array based on primary keys and foreign keys

Considering this array
Array
(
[0] => Array
(
[id] => 51
[category_id] => 37
[title] => Sims
)
[1] => Array
(
[id] => 37
[category_id] => 26
[title] => Blackberry
)
[2] => Array
(
[id] => 26
[category_id] => 0
[title] => Mobile Device
)
I would like to be able to print out:
Mobile Device > Blackberry > Sims
Based on the relationship between category_id and id.
Can you use the id as the key into the array? It will make your life a bit simpler. For example, if you define your array:
Array
(
[51] => Array
(
[id] => 51
[category_id] => 37
[title] => Sims
)
[37] => Array
(
[id] => 37
[category_id] => 26
[title] => Blackberry
)
[27] => Array
(
[id] => 26
[category_id] => 0
[title] => Mobile Device
)
Then you can write code like:
//assume $a is your array, defined above
//and that you have used the id for the array key
$id = 51
do {
print $a['title'];
$id = $a['category_id'];
}while($id != 0);
EDIT: array_multisort probably isn't cleanest way to do this.
<?php
$array = Array(
array('id' => 51, 'category_id' => 37, 'title' => 'Sims'),
array('id' => 37, 'category_id' => 26, 'title' => 'Blackberry'),
array('id' => 26, 'category_id' => 0, 'title' => 'Mobile Device'));
// First build an associative array ID->Object
$map = array();
foreach( $array as $value )
{
$map[$value['id']] = $value;
}
// Then build your path
$path = array();
$value = $array[0];
while( true )
{
$path[] = $value['title'];
if( $value['category_id'] == 0 )
{
break;
}
$value = $map[$value['category_id']];
if( !isset($value) )
{
die("Data Inconsistency");
}
}
// Display path
echo implode(array_reverse($path), ' > ');
?>
Try array_multisort()
Does your original array also contains entries that are to be left out?
If not, use this:
$sort_array = array();
foreach ($original_array as $key => $value) {
$sort_array[] = $value['category_id'];
}
array_multisort($sort_array, SORT_ASC, $original_array);
The above will sort $original_array based on the category_id index.
If your array contains entries that have nothing to do with the rest, and you want to leave them out, you have to use something like this:
// remap keys based on category_id
$parts = array();
foreach ($original_array as $array) {
$parts[$array['category_id']] = $array;
}
// build tree list
$category_id = 0;
$result = array();
while (isset($parts[$category_id])) {
$result[] = $parts[$category_id]['title'];
$category_id = $parts[$category_id]['id'];
}
echo implode(' > ', $result);

Categories