Sorting an array of objects in PHP - php

I want to write a static method in a class to generically sort an array of objects.
I am thinking of somthing along the lines of:
class GenUtils {
const ASCENDING = 1;
const DESCENDING = 2;
protected static alphaSort($value1, $value2, $sort_type=self::DESCENDING){
$retval = strcasecmp($value1, $value2);
return ($sort_type == self::DESCENDING) ? $retval : (-1*$retval);
}
protected static numericSort($value1, $value2, $sort_type=self::DESCENDING){
return $value1 < $value2;
}
// Assumption: array is non-empty and homogeneous
public doSort(array& $object_array, $method_name, $sort_type=self::DESCENDING) {
if(!empty($object_array) && method_exists($object_array[0],$method_name)) {
$element = $object_array[0];
$value = $element->$method_name();
if(is_string($value)){
//do string sort (possibly using usort)
}
elseif(is_number($value)){
//do numeric sort (possibly using usort)
}
}
}
}
This is just a quick brain dump -perharps someone can fill in the missing pieces, or suggest a better way of doing this?
[Edit]
Just to clarify, the objects to be sorted (in the array), have methods which return either a string (e.g. getName()) or a numeric value (e.g. getId())
A typical usecase code snippet would therefore be somethimng like this:
GenUtils::doSort($objects,'getName'); // This will do an alphabetic DESC sort using the getName() method
GenUtils::doSort($objects, 'getId', GenUtils::ASCENDING); // This will do a numeric ASC sort using the getId() method

The use cases (numeric and string) in your example are already built in to PHP - Check out the sort function. I would use the built-in function unless I had more specific needs.

Use usort and define your own comparison function to work with your objects.

Related

unsetting objects in an array based on a bool method

I'm trying to filter an array of objects implementing a specific interface (which simply defines the isComplete(): bool method) based on the result of that method. array_filter doesn't work because it can't call a method on each object to determine whether to filter it (or can it?). I've tried writing a function that takes the splatted array as an argument by reference, this doesn't work either:
function skipIncomplete(CompletableObjectInterface &...$objects): array {
$skipped = [];
foreach ($objects as $index => $item) {
if (!$item->isComplete()) {
$skipped[] = $item->id ?? $index;
unset($objects[$index]);
}
}
return $skipped;
}
The original elements passed in simply don't end up getting unset.
I'm looking for a way that doesn't include creating an entirely new Collection class to hold my CompletableObjects for complexity reasons. I really want to keep the type hint so no one can pass in a generic array, causing runtime errors when the function tries to call $item->isComplete.
Is there any way I can achieve this in PHP 7.3.15?
Added a filter, please comment as to what is wrong with this type of approach:
<?php
interface CompletableObjectInterface {
public function isComplete() : bool;
}
class Foo implements CompletableObjectInterface
{
public function isComplete() : bool
{
return false;
}
}
class Bar implements CompletableObjectInterface
{
public function isComplete() : bool
{
return true;
}
}
$foo = new Foo;
$bar = new Bar;
$incomplete = array_filter([$foo, $bar], function($obj) { return !$obj->isComplete();});
var_dump($incomplete);
Output:
array(1) {
[0]=>
object(Foo)#1 (0) {
}
}
Looks like you got a bit hung up on a wrong understanding of the ... syntax for a variable number of arguments.
You are passing in one array, and the $objects parameter will therefore contain that array in the first index, i.e. in $objects[0].
So in theory you could just change your line
unset($objects[$index]);
to
unset($objects[0][$index]);
However, I do not really see why the variable number of arguments syntax is used at all, since you apparently are just expecting one array of values (objects in this case) as an argument to the function. Therefore I'd recommend you just remove the ... from the argument list and your solution does what you wanted.
Alternatively you can of course add an outer foreach-loop and iterate over all passed "arrays of objects", if that is an use case for you.

Can I use a function in PHP's array_search or sort function?

I am using Parse.com APIs, though I wouldnt say this is a Parse question.
I have an array of ParseObjects called $groups and an array of ParseObjects called $inputs.
I already have both of these, so I do not want make any new queries.
All of the input objects are children of one of the group objects. I'd like to get an array of all of the $inputs that belong to each $group.
One way I could do this would be:
$groups= // array of ParseObjects
$inputs= // array of ParseObjects
foreach ($groups as $group)
{
$inputsInGroup=array();
foreach ($inputs as $input)
{
if($input->get('parent')==$group)
{
array_push($inputsInGroup,$input);
}
}
//here I can use $inputsInGroup which will contain all elements that have this group as their parent
}
That would work, but seams really inefficient as it has to search the whole list of inputs for each group, including any that it already determined belonged to a previous group.
Is there a way to supply a function to array_search () or sort() to return objects in the array that pass this check $input->get('parent')==$group?
I know you can do this with jQuery's sort() seems like PHP likely has a similar method.
You had an unneeded nested loop there - that's why
$groups; // array of ParseObjects
$inputs; // array of ParseObjects
$ibg; // inputs by group
foreach($inputs as $input) {
var $group = $input->get('parent')->getObjectId();
if(!isset($ibg[$group])){
$ibg[$group] = array();
}
$ibg[$group][] = $input;
}
What you're asking for is to apply a filter, with a custom callback, to an array, which can be done with array_filter. However, the change is only paradigmatic in nature — in that the asymptotic complexity won't change much.
array_filter($groups, function ($group) use ($inputs) {
foreach($inputs as $input) {
if ($input->get('parent') == $group) {
return true;
}
}
});
Both are still O(n * k) in time complexity, but one uses a more functional, as opposed to procedural, approach.

Comparing 2 objects PHP

i need to compare 2 objects to remove duplicates / find new enteries.
The objects are not identical, but they contain the same username key
Here is the layout
database object
array
[0]db->username
[0]db->something
[1]db->username
[1]db->something
etc
other object
array
[0]ob->username
[0]ob->somethingElse
[1]ob->username
[1]ob->somethingElse
etc
I imagine i can loop one array of objects, and compare the $db[$key]->username with an internal loop of the other object $ob[$key]->username but is there a cleaner way ?
I am looking to remove duplicates
No, there is no cleaner way, you have to loop over the properties. If that are not StdClass objects, I would add a custom compare method to their class:
class Person {
protected $id;
protected $name;
protected $age;
/**
* Compares two persons an returns true if their name
* and age equals.
*/
public function equals(Person $b) {
if($b->name === $this->name && $b->age === $this->age) {
return TRUE;
}
return FALSE;
}
}
Then use it like this:
$personA = DB::getPersonById(1);
$personB = DB::getPersonById(2);
if($personA->equals($personB)) {
echo "They are equal";
}
However, beside from this, why not just removing the duplicates using SQL or even better use unique keys in the DB to avoid duplicates at all?

What is the best/simplest way to do multi-tiered sorting?

I'm working on a function to do multi-leveled sorting (sort within a sort, for a lack of better term) for a client. Say we have a list of objects with different attributes such as:
name - object's name
type - object type
date - some date attribute
Let's say I wanted to sort the list first chronologically, then by object type, then alphabetically. How would I go about doing that?
currently I am using usort() to pass in my own comparing function, which will convert the above attributes to integers with different weight; eg. If the primary sorting is by date, I convert it to some integer, multiply it by 1000, convert the next tier of sorting to an integer (in this case the type), multiply it by 100, and so on, then add it all together to determine whether an object is < or > another.
Is there a much simpler/elegant solution? Thanks
EDIT: to clarify, is there a better way to do multi-level sorting without converting everything to a 'weight'?
Basically, what you want to do is use a series of "short circuit" comparisons. A naive example, given your criteria above, might look something like this (untested):
function mySort($a, $b) {
if ($a->name < $b->name) {
return -1;
}
if ($a->name > $b->name) {
return 1;
}
// If we get this far, then name is equal, so
// move on to checking type:
if ($a->type < $b->type) {
return -1;
}
if ($a->type > $b->type) {
return 1;
}
// If we get this far, then both name and type are equal,
// so move on to checking date:
if ($a->date < $b->date) {
return -1;
}
if ($a->date > $b->date) {
return 1;
}
// If we get this far, then all three criteria are equal,
// so for sorting purposes, these objects are considered equal.
return 0;
}
As I said, though, this is a naive solution, and it's very non-extensible. I'd recommend going with a slightly more robust solution, where your sorts aren't hard-coded into the sort method. Take this approach, for example (untested):
// These are the properties to sort by, and the sort directions.
// They use PHP's native SORT_ASC and SORT_DESC constants.
$this->_sorts = [
'name' => SORT_ASC,
'type' => SORT_ASC,
'date' => SORT_ASC
];
// Implemented as a class method this time.
protected function _mySort($a, $b) {
foreach ($this->_sorts as $property => $direction) {
if ($a->{$property} < $b->{$property}) {
return $direction === SORT_ASC ? -1 : 1;
}
if ($a->{$property} > $b->{$property}) {
return $direction === SORT_ASC ? 1 : -1;
}
}
return 0;
}
Now, adding or removing different sort fields or sort directions is as simple as adding or modifying an array element. No code modification necessary.

php arrays with only one type

I'm looking to create an array or list with elements of a certain type (eg objects the implement a certain interface). I know I can create an object that does the same thing implementing Traversable and Iterator, or override ArrayObject. But maybe there's another way I have missed.
Do you mean something like:
$array=Array();
foreach ($itemsToAdd as $item) {
if ($item instanceof NameOfwantedInterface) {
Array_push($array,$item);
}
}
If you don't, them I'm sorry - it's just that your question isn't too clear.
I would write a custom class that extended ArrayObject and threw an exception if you tried to assign a variable that wasn't the correct type, there's really no better way to do it that I can think of.
PHP as a lanugage is very flexible in terms of type handling and type conversion. You will probably have to put a manual check in if you want any kind of strong type checking, a simple if statement will do.
The array object is designed to be especially flexible (lazy key assignment, automatic increment, string or integer keys, etc.) so you should probably use a custom object of your own.
You could use type hinting:
<?php
interface Shape
{
function draw();
}
class MyArray
{
private $array = array();
function addValue(Shape $shape) //The hinting happens here
{
$array[] = $shape;
}
}
?>
This example is not perfect, but you'll get the idea.
Basically, you are going to want to do a function that checks if the variable you are inserting into the array is an object.
function add($var)
{
if(is_object($var))
{
$this->array[] = $var;
}
}
If you want to make sure it has a specific class name, you would do something like this (for PHP5):
function add(className $var)
{
$this->array[] = $var;
}
or this for previous PHP versions:
function add($var)
{
if($var instanceOf className)
{
$this->array[] = $var
}
}
You might want to look into array_filter() to do this without building an object.
Looking at that page, I've found that you can use array_filter with common functions like is_object. So doing something like this:
$this->array = array_filter($this->array ,'is_object');
Would filter the array to contain only objects.

Categories