PHP - 对大小为XXS-XXL和其他值的数组进行排序

I have these types of values in array $product_sizes which is dynamically created:

Type 1 (numeric value with characters): 45 cm, 120 cm, 50x70 cm, 100 x 160 cm, 10 mm x 30 cm

Type 2 (clothes size): XXS, XS, S, M, L, XL, XXL

Type 3 (text, not currently in array, would be nice to include in solution to be future proof): big, little, ...


I have this code which works for me to sort Type 1 values

function sort_numeric($a, $b) {
  return $a - $b;
}
usort($product_sizes, 'sort_numeric');

And this code to sort Type 2 XXS-XXL sizes (found on stackoverflow)

function sort_size($a, $b) {
  static $sizes = array('XXS', 'XS', 'S', 'M', 'L', 'XL', 'XXL');

  $asize = 100;
  $apos = -1;
  $bsize = 100;
  $bpos = -1;
  foreach ($sizes AS $val => $str) {
    if (($pos = strpos($a, $str)) !== FALSE && ($apos < 0 || $pos < $apos)) {
      $asize = $val;
      $apos = $pos;
    }
    if (($pos = strpos($b, $str)) !== FALSE && ($bpos < 0 || $pos < $bpos)) {
      $bsize = $val;
      $bpos = $pos;
    }
  }

  return ($asize == $bsize ? 0 : ($asize > $bsize ? 1 : -1));
}
usort($product_sizes, 'sort_size');

I can't figure out how to combine this code to work for both (or all 3) types of values.

The array is created dynamically based on product selected and there are values of only one type at time.

Type 1 values should be sorted by its numeric value (omit the cm, mm or other units), eg.:

  1. 10 mm x 30 cm
  2. 45 cm
  3. 50x70 cm
  4. 100 x 160 cm
  5. 120 cm

Assuming that your sorting functions actually works and assuming that I'm really really really bad with regex, I've implemented a class that does the job for you:

<?php
class sorter {

    private $_array = array();

    function __construct($array) {
        $this->_array = $array;
    }

    public function elaborate() {
        if (preg_match("~([0-9]+|[0-9])~", $this->_array[0])) {
            usort ($this->_array, array('sorter','sort_numeric'));
        }
        else {
            if (preg_match("~(?=^(X)(?=(L)$))|(?=^(L)$)|(?=^(M)$)~", $this->_array[0])) {
                usort ($this->_array, array('sorter','sort_size'));
            }
            else {
                usort ($this->_array, array('sorter','sort_text'));
            }
        }
        return $this->_array;
    }

    protected static function sort_numeric($a, $b) {
      return $a - $b;
    }

    protected static function sort_size($a, $b) {
      static $sizes = array('XXS', 'XS', 'S', 'M', 'L', 'XL', 'XXL');

      $asize = 100;
      $apos = -1;
      $bsize = 100;
      $bpos = -1;
      foreach ($sizes AS $val => $str) {
        if (($pos = strpos($a, $str)) !== FALSE && ($apos < 0 || $pos < $apos)) {
          $asize = $val;
          $apos = $pos;
        }
        if (($pos = strpos($b, $str)) !== FALSE && ($bpos < 0 || $pos < $bpos)) {
          $bsize = $val;
          $bpos = $pos;
        }
      }

      return ($asize == $bsize ? 0 : ($asize > $bsize ? 1 : -1));
    }

    protected static function sort_text($a, $b) {
        static $sizes = array("extra small","small","quite small?","something a bit too small","you surely don't fit there.","medium","big","very big","huge","enormous!");

        $asize = 100;
        $apos = -1;
        $bsize = 100;
        $bpos = -1;
          foreach ($sizes AS $val => $str) {
            if (($pos = strpos($a, $str)) !== FALSE && ($apos < 0 || $pos < $apos)) {
              $asize = $val;
              $apos = $pos;
            }
            if (($pos = strpos($b, $str)) !== FALSE && ($bpos < 0 || $pos < $bpos)) {
              $bsize = $val;
              $bpos = $pos;
            }
          }

        return ($asize == $bsize ? 0 : ($asize > $bsize ? 1 : -1));
    }
}
?>

For the "type 3" you are talking about, it's basically the EXACT same as the type 2, you just need to implement an array containing the elements the text should contain.

Usage of the code above, following 3 examples:

<?php
$type1 = array("120 cm","100 x 160 cm","10 mm x 30 cm");
$type2 = array("XL","XS","XXL","M");
$type3 = array("very big","small","extra small","something a bit too small");

$sorter = new sorter($type1);
echo "<pre>";
print_r($sorter->elaborate());
echo "</pre>";

$sorter = new sorter($type2);
echo "<pre>";
print_r($sorter->elaborate());
echo "</pre>";

$sorter = new sorter($type3);
echo "<pre>";
print_r($sorter->elaborate());
echo "</pre>";
?>

Output :

Array
(
    [0] => 10 mm x 30 cm
    [1] => 100 x 160 cm
    [2] => 120 cm
)
Array
(
    [0] => XS
    [1] => M
    [2] => XL
    [3] => XXL
)
Array
(
    [0] => extra small
    [1] => small
    [2] => something a bit too small
    [3] => very big
)

The logic behind this is quite easy:

The first regex checks if any number is inside the first element of the array. If so, it sorts it using the sort_numeric function.

The second regex checks whether the first element of the array starts either with X and finish with L or starts and end with M or L (which should cover the majority of the cases if I'm not wrong): if it does, it uses the sort_size function, else sort_text.

Working sandbox :

http://sandbox.onlinephpfunctions.com/code/9362377a481b0f4f2d33ccdfa4347f4e7c005e92

With hint from briosheje's comment I found a solution.

Not sure if the preg_grep searching in array is correct, but it works...

$product_sizes = array();

function sort_numeric($a, $b) {
  return $a - $b;
}

function sort_size($a, $b) {
  static $sizes = array('XXS', 'XS', 'S', 'M', 'L', 'XL', 'XXL');

  $asize = 100;
  $apos = -1;
  $bsize = 100;
  $bpos = -1;
  foreach ($sizes AS $val => $str) {
    if (($pos = strpos($a, $str)) !== FALSE && ($apos < 0 || $pos < $apos)) {
      $asize = $val;
      $apos = $pos;
    }
    if (($pos = strpos($b, $str)) !== FALSE && ($bpos < 0 || $pos < $bpos)) {
      $bsize = $val;
      $bpos = $pos;
    }
  }

  return ($asize == $bsize ? 0 : ($asize > $bsize ? 1 : -1));
}

if (preg_grep('~([0-9]+|[0-9])~', $product_sizes)) {
  usort($product_sizes, 'sort_numeric');
} else if (preg_grep('~(?=^(X+)(?=(L|S)$))|(?=^(L)$)|(?=^(M)$)|(?=^(S)$)~', $product_sizes)) {
  usort($product_sizes, 'sort_size');
} else {
  setlocale (LC_COLLATE, 'cs_CZ.utf8');
  usort($product_sizes, 'strcoll');
}

foreach ($product_sizes as $product_size_filter) {
  echo '<p>' . $product_size_filter . '</p>';
}