2

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
Community
  • 1
  • 1
mirek
  • 93
  • 9
  • 1
    You may want to consider using a regex to detect to which type it belongs. You can check, for example, if the element has at least a number to understand that it surely belongs to the first group while, if it doesn't, it belongs to the second one. Can you please give us an example of the third type? (a simple regex such as `'~([0-9]+|[0-9])~'` is enough) – briosheje May 26 '15 at 16:06
  • look here http://stackoverflow.com/questions/30238109/how-to-sort-an-array-by-specific-filter/30239873#30239873 – splash58 May 26 '15 at 16:56
  • Thanks @briosheje for hint with regex, I have made conditions with regexes and then perform sorting. It seems it works now :) – mirek May 26 '15 at 17:27

2 Answers2

3

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

briosheje
  • 7,356
  • 2
  • 32
  • 54
  • Thanks for exhaustive answer, as I am really a php noob I could't implement it properly :/ However I found a solution with conditions and regexes... – mirek May 26 '15 at 17:30
  • 1
    @Mirek: that's exactly what I did there ;) If you want to mantain your code for further edits (you know, you will never know if someday you will need to sort in other ways) I strongly recommend you to work with classes and objects, it's way easier to work with them and it's easier to mantain and optimize your own code in the future. – briosheje May 26 '15 at 17:35
  • I gave it another try and finally could implement your code except the `sort_text` function with correct czech locale sorting. I have tried `protected static function sort_text($a, $b) { setlocale (LC_ALL, 'czech'); return strcoll($a, $b); }`, but it doesn't work :/ You can see it here: [link](http://sandbox.onlinephpfunctions.com/code/79d04b54ffb051d07a22fae74d6043e45c2c04c7). It should sort exactly in the same order as in the $type3 array in code, but the result is different... – mirek May 27 '15 at 09:37
  • 1
    @mirek: if it doens't sort correctly that's probably because of the setlocale bug in windows. I suggest you to use the Collator PHP class, as suggested here: http://stackoverflow.com/questions/120334/how-to-sort-an-array-of-utf-8-strings . you will need to try that on your server though, because such a package is NOT installed (or enabled) in the php online sandbox, hence you have to try to run it in your own environent. To properly make it work, create a Collator object slightly before executing the usort function that calls sort_text and remove the setlocale from the function ;) – briosheje May 27 '15 at 10:04
  • Ah OK, now I get it why it did't work properly on Windows localhost testing environment. As I can't use the Collator PHP class, I've changed the locale to `cs_CZ.utf8`, moved it before usort, uploaded it to server (linux) and it works as supposed :) **Thanks a lot for your help ;)** Working example on [sandbox](http://sandbox.onlinephpfunctions.com/code/4357560ace89c306bc76d3a0ad9346a3eaa6d1ad) – mirek May 27 '15 at 11:02
  • One little edit to XXS-XXL sizes regex to cover them all. **[Final sandbox](http://sandbox.onlinephpfunctions.com/code/9a36f46f42fbdb66020aeb2c0d3d40a7234ee8b8)**. – mirek May 27 '15 at 11:19
  • @mirek : You're welcome, glad it was helpful! Don't forget to keep track of your server. If you someday, for some reason, will migrate your files you will have to take care of that specific case... Perhaps you can enable the Collator class from the php.ini anyway, it should be there from php 5.3 :P – briosheje May 27 '15 at 13:28
  • Incredible answer! Saved me a lot of time as I was faced with 4000 size arrays with different types of sizes. I have one question though - in a lot of instances, the "XL" appears at the beginning of some arrays. So where an item had "2XL,3XL,4XL,5XL,L,M,S,XL", the array returns "XL,M,S,L,2XL,3XL,4XL, 5XL". I'll keep tweaking to see if it's something I've done. Thanks anyway! – Thomas Harding Oct 05 '17 at 12:36
  • EDIT - reading that back made me realise it's because the data I'm working with uses 2XL and not XXL, which is presumably why it's coming back all weird. I can work around that. Thanks! – Thomas Harding Oct 05 '17 at 12:52
0

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>';
}
mirek
  • 93
  • 9