Comparing two arrays or objects with a function in PHP

Comparing two arrays or objects with a function in PHP

Created:17 Aug 2017 16:07:40 , in  Maths

In this article I look how to compare two ordinary arrays (or objects based on array). The problem can be solved fairly easily using a comparison function. It accepts two items, one from each the arrays and returns a result of the comparison. You combine these results and on the basis of their total infer which of the two arrays is larger.

Comparing two arrays with a function based on a spaceship operator

The so called spaceship operator available since PHP 7 is a perfect tool for any sort of comparisons, including comparisons of two objects, or even arrays of objects:


[(object)1,(object)2] <=> [(object)2,(object)1]
=> -1

The above tells you, that the array on the left hand side is smaller than the one on the right hand side.

The spaceship operator can, and should really, be used as the engine of your comparison function.


function compareTwoObjects($objA,$objB){
  return $objA ->  propertyX <=> $objB -> propertyX;
}

A function like that can be used to compare arrays of objects using their common propertyX:

Assuming, both arrayA and arrayB hold the same count of objects a comparison can be done as follows:


$totalA = 0;
$totalB = 0;
$arrayA = [obj1,obj2,...,objN];
$arrayB = [obj1,obj2,...,objN];

foreach(range(0,count($arrayA) -1) as $index){ 
  if( ($compResult = compareTwoObjects($arrayA[$index],$arrayB[$index])) === 0 ){
    continue;
  } else {
    if($compResult === -1){
      $totalA--;
    }else{
      $totalB--;
    }
  }
}

print_r($totalA <=> $totalB);
// -1 => arrayA is smaller than arrayB, 
// 0 both arrays are equal, 
// 1 arrayA is greater than arrayB 

Here is a concrete example of this:


function compareTwoObjects($objA,$objB){
  return $objA -> scalar <=> $objB -> scalar;
}

$totalA = 0;
$totalB = 0;
$arrayA = [(object)100,(object)101];
$arrayB = [(object)1,(object)11];

foreach(range(0,count($arrayA) -1) as $index){ 
  if( ($compResult = compareTwoObjects($arrayA[$index],$arrayB[$index])) === 0 ){
    continue;
  } else {
    if($compResult === -1){
      $totalA--;
    }else{
      $totalB--;
    }
  }
}

print_r($totalA <=> $totalB);
// => 1 or arrayA is greater than arrayB

Comparing arrays with a function not based on a spaceship operator

This part of this text consists of a PHP class called SWWWArrayCompare, which implements the idea as well as use examples for two arrays and two objects.

SWWWArrayCompare

Here is SWWWArrayCompare:


/*
  Class: SWWWArrayCompare 
  Description: Compare two arrays or objects using a comparing function
  Author: Sylwester Wojnowski
  WWW: wojnowski.net.pl

  Methods:
    instance - public static - compare two arrays or objects
      return - bool 
*/
class SWWWArrayCompare{
  public $result = true;
  private $opts = null;
  private $defaults = array(   
    'a' => null,
    'b' => null,
    // default reltion is greater than
    'relation' => '>',
    // name of length function if two objects are used 
    'len_func' => null,
    // name of value at the given index function 
    'index_func' => null,
    // objects / arrays to compare agains one another
    'a1' => null,
    'a2' => null
  );

  public static function instance($conf){
    $instance = new SWWWArrayCompare($conf);
    return $instance -> result;
  }

  public function __construct($conf){
    // configure
    $this -> opts = (object)array_merge($this -> defaults,$conf);
    // set interval of values to be compared
    $this -> setInterval(); 
    // get comparison result
    $this -> result = $this -> compare($this -> opts -> a1,$this -> opts -> a2);
  }

  // figure out what part of underlying array this comparison should work on
  private function setInterval(){
    // find lenghts of underlying arrays
    if($this -> opts -> len_func !== null){
      $a1_len = call_user_func(array($this -> opts -> a1,$this -> opts -> len_func));
      $a2_len = call_user_func(array($this -> opts -> a2,$this -> opts -> len_func));
    }else{
      $a1_len = count($this -> opts -> a1);
      $a2_len = count($this -> opts -> a2);
    } 
    // set number of items to iterate through 
    $this -> opts -> n = $n = $a1_len < $a2_len ? $a1_len : $a2_len;
    $a = $this -> opts -> a;
    $b = $this -> opts -> b;
    $this -> opts -> a = ($a !== null && $a > 0 && $a <= $n) ? $a : 1;
    $this -> opts -> b = ($b !== null && $b > 0 && $b <= $n) ? $b : $n;
  }

  // compare arrays / objects a1 agains a2  
  private function compare($a1,$a2){
    // set relation - provide comparing func unless one has been provided 
    $relation = is_string($this -> opts -> relation) ? Comparison::func($this -> opts -> relation) : $this -> opts -> relation;
  
    $n = $this -> opts -> b - $this -> opts -> a + 1;

    // empty arrays are equal to one another 
    if ( $n < 1 ) { return true; } 
     
    // compare two objects
    if( is_object($a1) and is_object($a2) ){
      while( $n > 0 ){
        $i = $this -> opts -> a + $n - 2;
        // break if relation is false 
        if( ! $relation(
          call_user_func(array($a1,$this -> opts -> index_func),$i),
          call_user_func(array($a2,$this -> opts -> index_func),$i)
          ) 
        ) { return false; }
        $n--;  
      }
    }

    // compare two arrays
    if( is_array($a1) and is_array($a2) ) {
      while( $n > 0 ){
        $i = $this -> opts -> a + $n - 2; 
        // break if relation is false 
        if( ! $relation($a1[$i],$a2[$i]) ){ return false; }
        $n--;
      }
    }
    return true;
  }  
}

Remarks

Before I move on to configuration and examples of use, I'm going to highlight some important facts about SWWWArrayCompare.

If two objects have to be compared using SWWWArrayCompare, the objects have to have public methods which return length and value at given index of underlying array.

If only a slice of the arrays has to be compared, its boundaries are specified using a and b configuration options. These values correspond to (present of not) indices in the shorter of two arrays. For array with indices from 0 through n - 1, their count starts from 1 and ends at n. Overshooting with either a or b causes no errors.

Default relation used for comparison is grater than. Different relation can be specified using a (anonymous) function.

If "relation" configuration option is not specified, it defaults to Comparison::func(), which returns greater of two numbers. Here is Comparison class:


// Number relations class
class Comparison{
  public static function func($relation = '>'){
    switch($relation){
      case '<':
        return function($a,$b){ return $a < $b; };
      case '>=':
        return function($a,$b){ return $a >= $b; };
      case '<=':
        return function($a,$b){ return $a <= $b; };
      default:
        return function($a,$b){ return $a > $b; };  
    }
  }
}

Comparison is a prerequisite for SWWWArrayCompare, hence must be included before the latter is used.

See examples section for concrete examples of these.

Configuration

SWWWArrayCompare accepts some configuration options. They are:

a - int - low index of slice of array to use for comparison

b - int - high index of slice of array to use for comparison

relation - string / function - default comparison relation defaults to '>' (greater than)

len_func - string - if two objects are being compared, this option specifies the name of public method which returns length of array to be compared.

index_func - string - if two objects are being compared, this option specifies the name of public method which returns value at given index in array to be compared.

a1,a2 - array / object - arrays / objects to compare against one another

Use examples

Here are some use examples.

Comparing two arrays using default comparing function:


SWWWArrayCompare::instance(
  array(
    'a1' => array(-1,3,5,10,30),
    'a2' => array(-2,2,4,9,16)
  )  
)
=> true

Return value in the above example means, that each item in array a1 is greater than item under the same index in a2 (default comparing function uses greater than relation to determine which item of the two passed to is greater). Hence a1 is greater than a2.

The next example is somewhat more involved example. In it two objects get compared.


class Custom{
  private $items = array();
      
  public function __construct($values){
    $items = $values;
  } 
      
  public function len(){
    return count($this -> items);
  }
      
  public function value($i){
    return $this -> items[$i];
  }
      
  public static function compare(){
    return function($a,$b){
      return ord($a) <= ord($b);
    };
  }  
}

The class above provides blueprint for objects to compare. Conveniently, it also provides a comparing function in the guise of a static method "compare". The comparing function specifies that ASCII value of string $a should be less or equal to ASCII value of string $b if it is to return true.

Here is how SWWWArrayCompare can be used to compare two objects of class Custom:


SWWWArrayCompare::instance(
  array(
    'a1' => new Custom(array('a','b','c','d')),
    'a2' => new Custom(array('a','z')),
    'len_func' => 'len',
    'index_func' => 'value',
    'relation' =>  Custom::compare()
  )  
);
=> true

Note, that in the above example the objects were given arrays of size 4 and 2 respectively. In such cases SWWWArrayCompare uses shorter of the two arrays to carry out comparison. Hence, ord('a') was compared against ord('a') and ord('b') was compared with ord('z').

Next example shows how to compare two arrays using just a slice of their values.


SWWWArrayCompare::instance(
  array(
    'a' => 2,
    'b' => 7,
    'relation' => '<', 
    'a1' => array(6,2,4,-1),
    'a2' => array(-2,6,16,7)
  )  
) 
=> true

In the above, slice length was specified using a and b configuration options. Values 2 and 7 mean, that only values under indices 1 and 6 were intended to be compared. However, since the arrays passed for comparison both have length 4, the real length of slice used by SWWWArrayCompare was 3 and it spanned indices 1 through 3 inclusive. Hence, 2 was compared, against 6, 4 against 16 and -1 against 7. Values at index 0 were not taken into account.

I hope these basic examples give enough insight into how SWWWArrayCompare works and what can be achieved with it.

Final thoughts

As you can see the spaceship operator is extremely useful for doing comparisons and should always be your first choice for doing them. If you want something more complicated, you might find a class like SWWWArrayCompare useful. The class allows for effective comparisons of arrays and objects and can be used for sorting and ultimately searching by array of values ( I hope to shed more light on these topics in the future articles).Finally, there is nothing that stops you from using the spaceship operator based comparison functions with SWWWArrayCompare classe either.

As always, I hope you'll find the code provided useful and examples simple enough to understand.

This post was updated on 09 Jul 2020 21:45:36

Tags:  php 


Author, Copyright and citation

Author

Sylwester Wojnowski

Author of the above article, Sylwester Wojnowski, enjoys sWWW writing computer code in PHP, JavaScript and BASH, and some other things he wrote more on on the About page of this website.

Copyrights

©Copyright, 2021 Sylwester Wojnowski. This article may not be reproduced or published as a whole or in parts without permission from the author. If you share it, please give author credit and do not remove embedded links.

Computer code, if present in the article, is excluded from the above and licensed under GPLv3.

Citation

Cite this article as:

Wojnowski, Sylwester. "Comparing two arrays or objects with a function in PHP." From sWWW - Code For The Web . https://wojnowski.net.pl//main/index/comparing-two-arrays-or-objects-with-a-function-in-php

Add Comment

Allowed BB Code - style tags: [b][/b], [i][/i], [code=text][/code],[code=javascript][/code],[code=php][/code],[code=bash][/code],[code=css][/code],[code=html][/code]


I constent to processing my data given through this form for purposes of a reply by the administrator of this website.

Recent Comments

Nobody has commented on this post yet. Be first!