0

Looking for a possible way to sort this following array. taking the bytes as sting in consideration.

Array
(
    [0] => 100 MB
    [4] => 10 MB
    [8] => 1GB
    [12] => 20 MB
    [16] => 250 MB
    [20] => 2GB
    [24] => 4 MB
    [28] => 500 MB
    [32] => 50 MB
    [36] => 5GB
    [40] => 8GB
    [44] => 0 MB
)
user1743247
  • 29
  • 1
  • 4

5 Answers5

3

You can sort this array using custom comparison function by converting the string to the value of byte.

$arr = array(
    0 => "100 MB",
    4 => "10 MB",
    8 => "1GB",
    12 => "20 MB",
    16 => "250 MB",
    20 => "2GB",
    24 => "4 MB",
    28 => "500 MB",
    32 => "50 MB",
    36 => "5GB",
    40 => "8GB",
    44 => "0 MB"
);

function toByte($value) {
    $multiple  = (stripos($value, "K") > 0) ? 1024 : 1;
    $multiple *= (stripos($value, "M") > 0) ? 1048576 : 1;
    $multiple *= (stripos($value, "G") > 0) ? 1073741824 : 1;

    return floatval($value) * $multiple;
}

usort($arr, function($v1, $v2) {
    return toByte($v1) - toByte($v2);
});

var_dump($arr);
invisal
  • 11,075
  • 4
  • 33
  • 54
0

One way or another, you need to convert those values into some numbers expressed in the same unit of measurement, so that you can compare them. I can think of two ways to do this:

  1. Walk through the array and convert all the values into this unit of measurement, then sort the new array.
  2. Sort the original array using a custom comparison function that converts the values before comparing them.

The first method would be more efficient, because you're only walking through the array once to perform conversions, while the in the second method you will do those conversions every time you compare numbers, which is almost certainly more expensive. If it is possible to copy or modify the array, you should go with the first method.

In any case, the function to convert the values into a simple number would look something like the following:

$unitMultipliers = array(
    'b' => 1,
    'kb' => 1024,
    'mb' => 1048576,
    'gb' => 1073741824,
);

function toBytes($stringValue) {
    if (!preg_match('^/([0-9]+)\s*([a-z]+)$/i', $matches)) {
        // If for some reason we can't find the pattern, exit early
        return 0;
    }

    $bytes = (int) $matches[1];
    $unit = strtolower($matches[2]);

    return $bytes * $unitMultipliers[$unit];
}

To implement solution 1 and convert the array, use:

$newArray = array_map('toBytes', $originalArray);
$sortedArray = sort($newArray);

To implement solution 2 and use a custom comparison, use:

function compare($a, $b) {
    $aBytes = toBytes($a);
    $bBytes = toBytes($b);

    if ($aBytes === $bBytes) {
        return 0;
    }
    return $aBytes > $bBytes ? 1 : -1;
}

$sortedArray = usort($originalArray, 'compare');
Grampa
  • 1,623
  • 10
  • 25
0

Try this:

$inpArr = array(0 => '100 MB', 4=>'10 MB', 8=>'1GB', 12=>'20 MB', 16=>'250 MB', 20=>'2GB', 24=>'4 MB', 28=>'500 MB', 32=>'50 MB', 36=>'5GB', 40=>'8GB', 44=>'0 MB');
$tempArr = array();
$sortArr = array();
foreach ($inpArr AS $key => $elem) {
    $unit = trim(substr($elem, -2));
    if ($unit == 'GB') {
        $tempArr[$key] = intval(trim(strstr($elem, 'GB', true))) * 1024;
    } else {
        $tempArr[$key] = intval(trim(strstr($elem, 'MB', true)));
    }
    asort($tempArr);
}
foreach ($tempArr AS $key => $elem) {
    $sortArr[] = $inpArr[$key];
}
Sanjay Kumar N S
  • 4,653
  • 4
  • 23
  • 38
0

For best efficiency, iterate the input array just once and populate a new array (of equal length) by parsing each string, looking up the unit factor, then pushing the product of the multiplied integers into the new array.

Using the newly populated array and the original array to array_multisort() will not preserve the original keys.

Parsing the string with sscanf() is very convenient because you can instantly cast substrings as integers or floats (which explode(), preg_match(), etc. cannot do). Also, sscanf() will elegantly ignore the potential space between the amount and the unit.

Code: (Demo) (Same technique with grams)

const UNIT_FACTOR = [
    'B' => 1,
    'KB' => 1024,
    'MB' => 1048576,
    'GB' => 1073741824,
];

$array = [
    0 => "100 MB",
    4 => "10 MB",
    8 => "1GB",
    12 => "20 MB",
    16 => "250 MB",
    20 => "2GB",
    24 => "4 MB",
    28 => "500 MB",
    32 => "50 MB",
    36 => "5GB",
    40 => "8GB",
    44 => "0 MB"
];

$bytes = [];
foreach ($array as $string) {
    sscanf($string, '%f%s', $amount, $unit);
    $bytes[] = $amount * UNIT_FACTOR[$unit];
}

array_multisort($bytes, $array);
var_export($array);

Output:

array (
  0 => '0 MB',
  1 => '4 MB',
  2 => '10 MB',
  3 => '20 MB',
  4 => '50 MB',
  5 => '100 MB',
  6 => '250 MB',
  7 => '500 MB',
  8 => '1GB',
  9 => '2GB',
  10 => '5GB',
  11 => '8GB',
)

A less efficient approach that preserves array keys would be uasort().

Code: (Demo)

const UNIT_FACTOR = [
    'B' => 1,
    'KB' => 1024,
    'MB' => 1048576,
    'GB' => 1073741824,
];

$array = [
    0 => "100 MB",
    4 => "10 MB",
    8 => "1GB",
    12 => "20 MB",
    16 => "250 MB",
    20 => "2GB",
    24 => "4 MB",
    28 => "500 MB",
    32 => "50 MB",
    36 => "5GB",
    40 => "8GB",
    44 => "0 MB"
];

uasort(
    $array,
    function($a, $b) {
        sscanf($a, '%f %s', $amountA, $unitA);
        sscanf($b, '%f %s', $amountB, $unitB);
        return $amountA * UNIT_FACTOR[$unitA]
            <=> $amountB * UNIT_FACTOR[$unitB];
    }
);

var_export($array);

However, the number of iterated function calls can be reduced by establishing a lookup array of calculated byte values before calling uasort().

Code: (Demo)

$bytes = [];
foreach ($array as $string) {
    sscanf($string, '%f %s', $amount, $unit);
    $bytes[$string] = $amount * UNIT_FACTOR[$unit];
}
    
uasort($array, fn($a, $b) => $bytes[$a] <=> $bytes[$b]);

var_export($array);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
-1

you can try natsort

https://php.net/manual/en/function.natsort.php

<?php
    $array = array('10t', '2t', '3t');
    natsort($array);

    echo '<pre>';
    print_r($array);
    echo '</pre>';
?>

// output array ( 2t, 3t, 10t )

  • This doesn't work !!! The output is `Array ( [44] => 0 MB [8] => 1GB [20] => 2GB [24] => 4 MB [36] => 5GB [40] => 8GB [4] => 10 MB [12] => 20 MB [32] => 50 MB [0] => 100 MB [16] => 250 MB [28] => 500 MB )` – Akram Fares Apr 06 '15 at 08:22