1

I have to sort a multidimensional-array based on price or size but the problem is, it works with an issue, always the first array it sort is wrong, I tried using usort, etc but all of them giving the same result, we have a list of products that need to be sorted based on price or size. I find lots of answers and the problem remains the same. Product should be sort by based on price i.e Low to high or Size will sort with width for example product dimension 120 x 60 so 120 is the width and it should be the last product

Function to Sort the multiDimensional array

function array_sort($array, $on, $order=SORT_ASC){
   $new_array = array();
   $sortable_array = array();
   if (count($array) > 0) {
      foreach ($array as $k => $v) {
         if (is_array($v)) {
            foreach ($v as $k2 => $v2) {
                if ($k2 == $on) {
                    $sortable_array[$k] = $v2;
                }
            }
        } else {
            $sortable_array[$k] = $v;
        }
    }

    switch ($order) {
        case SORT_ASC:
            asort($sortable_array);
            break;
        case SORT_DESC:
            arsort($sortable_array);
            break;
    }

    foreach ($sortable_array as $k => $v) {
        $new_array[$k] = $array[$k];
    }
 }
 return $new_array; }

Here is the array I want to sort

$sortingArray = Array(
    '0' => Array(
       'id' => 1287,
       'data-price' => '£209.95',
       'size' => '120 x 60'
    ),
    '1' => Array(
       'id' => 1263,
       'data-price'=> '£14.95',
       'size' => '18 x 13'
     ),

    '2' => Array(
       'id' => 1264,
       'data-price' => '£15.95',
       'size' => '20 x 15'
    ),

   '3' => Array(
      'id' => 1245,
      'data-price' => '£29.95',
      'size' => '30 x 20'
   ),

   '4' => Array
       (
        'id' => 1315,
        'data-price' => '£39.95',
        'size' => '50 x 13'
      ),
    '5' => Array(
        'id' => 1275,
        'data-price' => '£94.95',
        'size' => '60 x 40'
    ),
   '6' => Array
      (
        'id' => 1281,
        'data-price' => '£119.95',
        'size' => '75 x 50'
      ),

   );

To Output result

$rectangleProductDetails = array_sort($sortingArray, 'size', SORT_ASC);
print_r($rectangleProductDetails);
user2572661
  • 132
  • 1
  • 7
  • Start by properly explaining _how_ you want these items sorted by size. These look like width/height values or something - so it would probably make the most sense to perform the “multiplication” contained in those formulas first of all here, so that the products can then be sorted by surface area …? – CBroe Jan 05 '21 at 08:50
  • @CBroe no product can't be sorted based on the surface area, it should be either price or if we choose size then it should be the width for example product dimension 120 x 60 so 120 is the width and it should be the last product – user2572661 Jan 05 '21 at 09:06

3 Answers3

1

Proposed solution:

function array_sort(array $array, string $on, string $order = 'ASC') {
    $sortableArray = $array;
    switch ($on) {
        case 'size':
            usort($sortableArray, 'sortBySize');
            break;
        case 'price':
            usort($sortableArray, 'sortByPrice');
            break;
        default:
            return $array;
            break;
    }

    if (strtoupper($order) === 'DESC') {
        return array_reverse($sortableArray);
    }

    return $sortableArray;
}

function sortByPrice(array $a, array $b) {
    return (float) mb_substr($a['data-price'], 1) <=> (float) mb_substr($b['data-price'], 1);
}

function sortBySize(array $a, array $b) {
    return (int) $a['size'] <=> (int) $b['size'];
}

How it works:

  • It switches the sorting criteria and selects the proper sorting callback (from the functions that were defined separately) to use with usort. If an unsupported value has been supplied, it will return the original order.
  • The array is always sorted ascending (even if some bogus value is supplied as the order). If descending order is defined, we return the sorted array in reverse order by using array_reverse.
  • Since you've specified that only width should be used for sorting size, it is sorted by casting size values to integer (since the integer value only considers the numbers up to the first non-numeric character).
  • Sorting by price requires extraction of the amount, which is done through mb_substr($priceVariable, 1) (it eliminates the first character). Note that substr wouldn't work here, as the pound sign is a multibyte character so the multibyte version of the substring function has to be used.
  • Both sorting functions utilize the spaceship operator.

Lastly, note that the $on sorting criteria doesn't match the array keys exactly (I use 'price' instead of 'data-price'; they can match - like 'size' does - but don't have to). The benefit of this is that if an index in the array changes, you only need to modify the sorting callbacks, instead of having to do the modification in every single place where you call your custom array_sort function.

El_Vanja
  • 3,660
  • 4
  • 18
  • 21
0

you need to cast size as int

maybe something like

usort($sortingArray, function($a, $b) {
    //you can calculate your size and sort the result
    //or split it into 2 number and then sort it
    return intval($a["size"]) < intval($b["size"]) ? -1 : 1;
});
anon
  • 361
  • 4
  • 8
  • Not sure if simply casting to int makes much sense here. `120 x 60` cast to int, will result in 120. So you would only take into account the first dimension every time, the second one would have no influence on the sort result at all. Now OP has not been specific as to how exactly they want to sort, but my guess would be, that an item with size `10 x 1` should probably be considered smaller than one with size `5 x 500`. – CBroe Jan 05 '21 at 08:44
  • not simply, but need to, i try to give you idea how to do it – anon Jan 05 '21 at 08:45
  • edited my answer, maybe something like that? thanks anyway – anon Jan 05 '21 at 08:59
0

Reading what you are trying to achieve I think the following would be a proper approach on achieving a solution.

Since you require several different ways of sorting the array on the values of its fields, with their own needs, I think a strategy kind of pattern will fit this scenario.

For this I defined several functions, one to sanitize the string containing the price and then compare them using the space ship operator.

For the size, we simply obtain the width and compare the intvals.

And finally the default will just apply regular comparison.

function priceSort($on)
{
    return function ($item, $other) use ($on) {
        $itemPrice = filter_var($item[$on], FILTER_SANITIZE_NUMBER_FLOAT);
        $otherPrice = filter_var($other[$on], FILTER_SANITIZE_NUMBER_FLOAT);

        return floatval($itemPrice) <=> floatval($otherPrice);
    };
}

function widthSort($on)
{
    return function ($item, $other) use ($on) {
        $itemWidth = explode('x', str_replace(' ', '', $item[$on]))[0];
        $otherWidth = explode('x', str_replace(' ', '', $other[$on]))[0];

        return intval($itemWidth) <=> intval($otherWidth);
    };
}

function defaultSort($on)
{
    return function ($item, $other) use ($on) {
        return $item[$on] <=> $other[$on];
    };
}

function determineSortStrategy($on)
{
    switch ($on) {
        case 'size':
            $strategy = widthSort($on);
            break;
        case 'data-price':
            $strategy = priceSort($on);
            break;
        default:
            $strategy = defaultSort($on);
            break;
    }

    return $strategy;
}

function orderBased($strategy, $order)
{
    if ($order === SORT_DESC) {
        $strategy = function ($item, $other) use ($strategy) {
            return -1 * $strategy($item, $other);
        };
    }

    return $strategy;
}

function array_sort($array, $on = 'id', $order = SORT_ASC)
{
    $strategy = determineSortStrategy($on);
    $strategy = orderBased($strategy, $order);

    usort($array, $strategy);

    return $array;
}

$result = array_sort($sortingArray, 'data-price', SORT_DESC);

echo '<pre>';
print_r($result);
echo '</pre>';
Remy
  • 777
  • 2
  • 8
  • 15