PHP - Count unique in an array of objects - php

I have a PHP array which contains objects like this
Array
(
[0] => stdClass Object
(
[label] => Test 1
[session] => 2
)
[1] => stdClass Object
(
[label] => Test 2
[session] => 2
)
[2] => stdClass Object
(
[label] => Test 3
[session] => 42
)
[3] => stdClass Object
(
[label] => Test 4
[session] => 9
)
)
I am trying to count the number of unique sessions within this array. I can do it when the whole thing is an array, but I am struggling to work it out when the array contains objects.
Do I need to convert the objects into arrays or is there a way of doing it with the data in its current format?

https://www.php.net/manual/en/function.array-column.php :
Version Description
7.0.0 Added the ability for the input parameter to be an array of objects.
Use array_column to generate new keys using the session column values. This effectively removes duplicate keys.
Code: (Demo)
$array = [
(object)['label' => 'Test 1', 'session' => 2],
(object)['label' => 'Test 2', 'session' => 2],
(object)['label' => 'Test 3', 'session' => 42],
(object)['label' => 'Test 4', 'session' => 9],
];
echo sizeof(array_column($array, null, 'session'));
Output:
3
Or in a loop:
foreach ($array as $obj) {
$result[$obj->session] = null;
}
echo sizeof($result);
Both techniques avoid the extra function call of array_unique and leverage the fact that arrays cannot store duplicate keys.

I have tried your code and here created sample data
$comments= array();
$comment = new stdClass;
$comment->label = 'Test 1';
$comment->session = '2';
array_push($comments, $comment);
$comment = new stdClass;
$comment->label = 'Test 2';
$comment->session = '2';
array_push($comments, $comment);
$comment = new stdClass;
$comment->label = 'Test 3';
$comment->session = '42';
array_push($comments, $comment);
$comment = new stdClass;
$comment->label = 'Test 4';
$comment->session = '9';
array_push($comments, $comment);
Here is code I tried to get the unique value. this way you can get any field unique value
$uniques = array();
foreach ($comments as $obj) {
$uniques[$obj->session] = $obj;
}
echo "<pre>";
print_r($uniques);
echo "</pre>";

You could also use array_map to only keep the value of session in the array, then use array_unique to remove the duplicate entries and finally count the unique items.
If for example your array variable is called $array:
$result = array_map(function($x){
return $x->session;
}, $array);
echo count(array_unique($result));
That will result in:
3
Demo

The object within an array can be accessed with $array[0] where the 0 stands for the object. To access the objects property you can do $object->session.
To go throught every objects session property you can do:
foreach ($array as $object) {
echo $object->session . "<br/>";
}

Related

Remove duplicates from array but ignoring one parameter

I have an array comprised of PHP objects as such:
$objects[0] => $object->type => 'President'
$object->name => 'Joe Blogs'
$object->address => '123 Harry Street'
$objects[1] => $object->type => 'Secretary'
$object->name => 'Joe Blogs'
$object->address => '123 Harry Street'
$objects[2] => $object->type => 'Treasurer'
$object->name => 'Jane Doe'
$object->address => '456 Upton Street'
I would like to ignore the type parameter and end up with:
$objects[0] => $object->type => 'President'
$object->name => 'Joe Blogs'
$object->address => '123 Harry Street'
$objects[2] => $object->type => 'Treasurer'
$object->name => 'Jane Doe'
$object->address => '456 Upton Street'
I have tried a few different things, one of which was to unset the parameter type using a foreach loop and then trying to reset it, but I wasn't sure how to tie the two indexes together to reset them. Another was trying to use the union in the select command but that wasn't working 100% correctly either.
I am just not sure how to best manage the type parameter.
EDIT:
I have tried to make the query a little easier, it now returns a list of IDs that I will get the address information later. This is the new array that I would like to filter out any duplicates.
$items['president'][0] = '1'
[1] = '2'
[2] = '3'
$items['secretary'][0] = '1'
[1] = '4'
[2] = '5'
What I would like is
$items['president'][0] = '1'
[1] = '2'
[2] = '3'
$items['secretary'][1] = '4'
[2] = '5'
Is that any better? (Note: I can use both array structures, but the second one would be better)
This should work for you:
Here I just go through each object from $objects. Then I go through all of your unique objects from $unique with array_reduce() and check if there is one object, which has the same values in name and address as the current one of the iteration.
If there in no object with the same values name and address in $unique I add it to it.
<?php
$unique = [];
foreach($objects as $o) {
$duplicate = array_reduce($unique, function($keep, $v)use($o){
if($v->name == $o->name && $v->address == $o->address)
return $keep = TRUE;
}, FALSE);
if(!$duplicate)
$unique[] = $o;
}
print_r($unique);
?>
output:
Array
(
[0] => stdClass Object
(
[type] => President
[name] => Joe Blogs
[address] => 123 Harry Street
)
[1] => stdClass Object
(
[type] => Treasurer
[name] => Jane Doe
[address] => 456 Upton Street
)
)
EDIT:
As from your updated question with the new array structure, something like this should work:
Just like before you have an array with the unique values and also an array to keep track on which values you already have in your $unique array. Then you can just loop through your array an check if the value isn't already in the values array.
<?php
$unique = [];
$values = [];
foreach($items as $keyOne => $arr) {
foreach($arr as $keyTwo =>$v) {
if(!in_array($v, $values)) {
$unique[$keyOne][$keyTwo] = $v;
$values[] = $v;
}
}
}
print_r($unique);
?>
output:
Array
(
[president] => Array
(
[0] => 1
[1] => 2
[2] => 3
)
[secretary] => Array
(
[1] => 4
[2] => 5
)
)
you can just use array_unique, this function compares objects using the toString method:
class obj{
public $type;
public $name;
public $address;
public function __construct($type, $name, $address){
$this->type = $type;
$this->name = $name;
$this->$address = $address;
}
public function __toString(){
return $this->name . $this->address;
}
}
$objects = array(
new obj('President','Joe Bogs','123 Harry Street'),
new obj('Treasurer','Joe Bogs','123 Harry Street'),
new obj('Secretary','Jane Doh','456 Harry Street'),
);
// array_unique compares the toString value if given objects
$unique = array_unique($objects);
Or, if for some Reason you can not use __toString in your Object, write your own unique function that can compare using a custom function that returns a unique_id as you want it:
function custom_array_unique($array, $function){
$res = array();
foreach($array as $o){
if(!isset($res[$function($o)])) {
$res[$function($o)] = $o;
}
}
return array_values($res);
}
$unique2 = custom_array_unique($objects, function($obj){
return $obj->name . $obj->address;
});
var_dump($unique2);
You can take the type as key, so it won't be duplicated, and in case you don't want to override, you can just verify it's existence by using array_key_exists, and after that you can array_values to get the order as required, see example below:
$newArray = array();
foreach($objects as $object){
if(!array_key_exists($object->name, $newArray)){
$newArray[$object->name] = $object;
}
}
$newArray = array_values($newArray);
echo '<pre>';
print_r($newArray);

How get a object by id from arrays of object?

Assuming that I have an array of objects like this:
Array
(
[0] => stdClass Object
(
[id] => 10-423-1176
[qty] => 2
[price] => 12.6
)
[1] => stdClass Object
(
[id] => 26-295-1006
[qty] => 24
[price] => 230.35
)
[2] => stdClass Object
(
[id] => 12-330-1000
[qty] => 2
[price] => 230.35
)
And I have another array of object hat looks like this:
Array
(
[0] => Item Object
(
[internalId] => 14062
[itemVendorCode] => 89-605-1250
)
[1] => Item Object
(
[internalId] => 33806
[itemVendorCode] => 89-575-2354
)
[2] => Item Object
(
[internalId] => 64126
[itemVendorCode] => 26-295-1006
)
)
I want to loop through the 2nd array of objects and get the 'itemVendorCode' and then use it as the 'id' to get the object from the first array of objects. Is there a way to obtain what I want without looping the first array? Looping is very costly in my use-case.
You will have to use loops in any case, even if those loops are hidden within PHP built-in functions.
For instance:
$codes = array_map(function ($item) { return $item->itemVendorCode; }, $array2);
$items = array_filter($array1, function ($item) use ($codes) { return in_array($item->id, $codes); });
// $items contains only elements from $array1 that match on $array2
If this will be more efficient than using regular loops is hard to tell.
Since you are aparently trying to code what is supposed to be a DBMS's job, I recommend you export those tables to a database server such as MySQL instead and let it work its magic on those "JOINs".
Answering your comment, you could merge with something like this:
$result = array();
foreach ($array1 as $item1)
foreach ($array2 as $item2)
if ($item1->id == $item2->itemVendorCode)
$result[] = (object)array_merge((array)$item1, (array)$item2));
$result will contain a new set of objects that merge properties from both $array1 and $array2 where they intersect in id == itemVendorCode.
Do you need first arrays index keys? if not you could iterate throuh first array once and set key to id. Something like:
foreach ($items as $key => $item) {
$items[$item->id] = $item;
unset($items[$key]);
}
Here is another direct approach to solve this problem, even better than the one I proposed earlier:
// you got the $itemVendorCode from looping through the second array, let say :
$itemVendorCode = "89-605-1250";
// I'm assuming that you converted the array of objects in into accessible multidimensional array
// so the $first_array would look like :
$first_array= array (
array (
"id" => "10-423-1176",
"qty" => 2,
"price" => 12.6
),
array (
"id" => "10-423-1176",
"qty" => 5,
"price" => 25
),
array (
"id" => "89-605-1250",
"qty" => 12,
"price" => 30
)
);
// Now you can filter the first array using
$filter = function ($player) use($itemVendorCode) {
return $player ['id'] == $itemVendorCode;
};
$filtered = array_filter ( $first_array, $filter );
// print the price of the matching filtered item
print $filtered[key($filtered)]['price'] ;
You can use the array_map and array_filter() function to achieve that.
Try with this code:
<?php
$first = array();
$first[0] = new stdClass;
$first[0]->id = '89-605-1250';
$first[0]->qty = 2;
$first[0]->price = 12.6;
$first[1] = new stdClass;
$first[1]->id = '89-575-2354';
$first[1]->qty = 24;
$first[1]->price = 230.35;
$last = array();
$last[0] = new stdClass;
$last[0]->internalId = 14062;
$last[0]->itemVendorCode = '89-605-1250';
$last[1] = new stdClass;
$last[1]->internalId = 33806;
$last[1]->itemVendorCode = '89-575-2354';
$ids = array_map(function($element){return $element->itemVendorCode;}, $last);
$to_find = $ids[0];
$object = array_filter($first, function($element){global $to_find; return $element->id == $to_find ? true: false;})[0];
print_r($object);
?>
Output:
stdClass Object
(
[id] => 89-605-1250
[qty] => 2
[price] => 12.6
)
try using array_search:
http://php.net/manual/en/function.array-search.php
foreach($array2 as $key=>$item) {
$firstArrayObjectKey = array_search($item['itemVendorCode'], $array1);
//... do something with the key $firstArrayObjectKey
}
In this case you'll need to loop through the first array to get the itemVendorCode.
Right after that you can use the itemValue you got from the previous process to search in a reduced array of the first object using array_reduce function:
http://php.net/manual/en/function.array-reduce.php

magento array_merge

This array_merge doesn't seem to work for me. I am trying merge all the arrays in one array like this:
foreach ($collection as $result) {
$i++;
if(empty($json)){
$json = $result->getData();
}else{
$json = array_merge($json, $result->getData());
}
}
print_r($json);
I have 3 arrays in the collection. But when I do print_r($json); it only shows me the last array like this.
Array (
[po_id] => 3
[title] => Test3
[geo_address] => M1 2FF
[latitude] => 53.449137
[longitude] => -2.364551
[display_address] => testing
[url] => http://testing.com
[phone] => 0321654987
[status] => 1
[type] => 1
[created_time] => 2012-01-26 11:07:05
[update_time] => 2012-01-26 11:10:13
[distance] => 3708.40724665926
)
I am expecting this to merge all three arrays and print that out.
I'm kinda expecting it like this:
Array (
[po_id] => 1
[title] => Test1
[geo_address] => M1 2FF
[po_id] => 2
[title] => Test2
[geo_address] => M2 2FF
[po_id] => 3
[title] => Test3
[geo_address] => M3 2FF
)
Means all the arrays should be merged in on array.
EDITTED
I have it working. In fact this what I was looking for:
$json = array();
foreach ($collection as $key=>$result) {
$data = $result->getData();
$json[$key]['postorefinder_id'] = $data['postorefinder_id'];
$json[$key]['title'] = $data['title'];
$json[$key]['geo_address'] = $data['geo_address'];
$json[$key]['latitude'] = $data['latitude'];
$json[$key]['latitude'] = $data['latitude'];
$json[$key]['longitude'] = $data['longitude'];
$json[$key]['display_address'] = $data['display_address'];
$json[$key]['url'] = $data['url'];
$json[$key]['phone'] = $data['phone'];
$json[$key]['status'] = $data['status'];
$json[$key]['type'] = $data['type'];
$json[$key]['created_time'] = $data['created_time'];
$json[$key]['update_time'] = $data['update_time'];
$json[$key]['distance'] = $data['distance'];
}
return json_encode($json);
Thanks #cillosis, your example really helped.
According to the array_merge() function on php.net:
If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.
This means, every time you try to merge them, because they are using the same key, it just gets overwritten. That's why you only see the last array.
[EDIT]
So how would I concatenate multiple arrays with the same keys in one array?
I don't see how two elements can share the same key unless that key contained another array like this:
foreach ($collection as $result) {
// Get collection of data
$data = $result->getData();
// Assign each result to multi-dimensional array
$json['po_id'][] = $data['po_id'];
$json['title'][] = $data['title'];
$json['geo_address'][] = $data['geo_address'];
$json['latitude'][] = $data['latitude'];
// ... the rest of them go here ...
}
That is untested, just threw it together real quick. It should output something like:
Array(
"po_id": Array(
"first_po_id",
"second_po_id",
"third_po_id"
),
"title": Array(
"first_title",
"second_title",
"third_title"
)
)
With more data than that of course.
Unfortunately the structure you're hoping for is not possible in PHP. Have a read about the array type; if you try to assign a value to a pre-existing key, it overwrites any existing value:
$array = array('foo' => 'bar');
$array = array_merge($array, array('foo' => 'new'));
var_dump($array['foo']); // new
Depending on how you want to manipulate the resulting data, you can use array_merge_recursive:
$json = array();
foreach ($collection as $result) {
$json = array_merge_recursive($json, $result->getData());
}
or alternatively you might want the collections to group by result:
// make $json a copy of $collection
$json = $collection;
// overwrite $result within $json
foreach ($json as &$result) {
$result = $result->getData();
}
EDIT: See example output for each of these approaches.

How can I insert an item in the middle of an object in PHP?

If I have a stdClass object like the one below, which has three arrays with non-numeric keys, what's the best way to insert another item into the object between my-account and settings?
[menu1] => stdClass Object
(
[my-account] => Array
(
[title] => 'My Account'
)
[settings] => Array
(
[title] => 'Settings'
)
[payments] => Array
(
[title] => 'Payments'
)
)
Create your own class for this with functions to add and remove items and their attributes. Then use some functions such as #genesis provided.
There's no function to do this. You'll have to do your own and rebuild your object
http://sandbox.phpcode.eu/g/fba96/3
<?php
function insert_after($var, $key, $value, $after){
$new_object = array();
foreach((array) $var as $k => $v){
$new_object[$k] = $v;
if ($after == $k){
$new_object[$key] = $value;
}
}
$new_object = (object) $new_object;
return $new_object;
}
$var = insert_after($var, 'new dummy var between', array('title' => 'value'), 'my-account');
Add your item and then remove and re-add the items which ned to come after it:
$menu1->newitem = array('title'=>'Title');
$settings = $menu1->settings;
unset($menu1->settings);
$menu1->settings = $settings;
unset($settings);
unset($menu1->payments);
$menu1->payments = $payments;
unset($payments);

weird php array

my php array looks like this:
Array (
[0] => dummy
[1] => stdClass Object (
[aid] => 1
[atitle] => Ameya R. Kadam )
[2] => stdClass Object (
[aid] => 2
[atitle] => Amritpal Singh )
[3] => stdClass Object (
[aid] => 3
[atitle] => Anwar Syed )
[4] => stdClass Object (
[aid] => 4
[atitle] => Aratrika )
) )
now i want to echo the values inside [atitle].
to be specific i want to implode values of atitle into another variable.
how can i make it happen?
With PHP 5.3:
$result = array_map(function($element) { return $element->atitle; }, $array);
if you don't have 5.3 you have to make the anonymous function a regular one and provide the name as string.
Above I missed the part about the empty element, using this approach this could be solved using array_filter:
$array = array_filter($array, function($element) { return is_object($element); });
$result = array_map(function($element) { return $element->atitle; }, $array);
If you are crazy you could write this in one line ...
Your array is declared a bit like this :
(Well, you're probably, in your real case, getting your data from a database or something like that -- but this should be ok, here, to test)
$arr = array(
'dummy',
(object)array('aid' => 1, 'atitle' => 'Ameya R. Kadam'),
(object)array('aid' => 2, 'atitle' => 'Amritpal Singh'),
(object)array('aid' => 3, 'atitle' => 'Anwar Syed'),
(object)array('aid' => 4, 'atitle' => 'Aratrika'),
);
Which means you can extract all the titles to an array, looping over your initial array (excluding the first element, and using the atitle property of each object) :
$titles = array();
$num = count($arr);
for ($i=1 ; $i<$num ; $i++) {
$titles[] = $arr[$i]->atitle;
}
var_dump($titles);
This will get you an array like this one :
array
0 => string 'Ameya R. Kadam' (length=14)
1 => string 'Amritpal Singh' (length=14)
2 => string 'Anwar Syed' (length=10)
3 => string 'Aratrika' (length=8)
And you can now implode all this to a string :
echo implode(', ', $titles);
And you'll get :
Ameya R. Kadam, Amritpal Singh, Anwar Syed, Aratrika
foreach($array as $item){
if(is_object($item) && isset($item->atitle)){
echo $item->atitle;
}
}
to get them into an Array you'd just need to do:
$resultArray = array();
foreach($array as $item){
if(is_object($item) && isset($item->atitle)){
$resultArray[] = $item->atitle;
}
}
Then resultArray is an array of all the atitles
Then you can output as you'd wish
$output = implode(', ', $resultArray);
What you have there is an object.
You can access [atitle] via
$array[1]->atitle;
If you want to check for the existence of title before output, you could use:
// generates the title string from all found titles
$str = '';
foreach ($array AS $k => $v) {
if (isset($v->title)) {
$str .= $v->title;
}
}
echo $str;
If you wanted these in an array it's just a quick switch of storage methods:
// generates the title string from all found titles
$arr = array();
foreach ($array AS $k => $v) {
if (isset($v->title)) {
$arr[] = $v->title;
}
}
echo implode(', ', $arr);
stdClass requires you to use the pointer notation -> for referencing whereas arrays require you to reference them by index, i.e. [4]. You can reference these like:
$array[0]
$array[1]->aid
$array[1]->atitle
$array[2]->aid
$array[2]->atitle
// etc, etc.
$yourArray = array(); //array from above
$atitleArray = array();
foreach($yourArray as $obj){
if(is_object($obj)){
$atitleArray[] = $obj->aTitle;
}
}
seeing as how not every element of your array is an object, you'll need to check for that.

Categories