0

I want to sort an array in a complicated way and am not sure how to go about it. Here is a rough idea of the data I'm working with:

[
  { target: random.text.cpu-pct-0, otherData[...] },
  { target: random.text.cpu-pct-1, otherData[...] },
  { target: random.text.cpu-pct-2, otherData[...] },
  { target: random.text.example-0, otherData[...] },
  { target: random.text.example-1, otherData[...] },
  { target: random.text.memory, otherData[...] },
  ...
]

I want all the objects with a target that includes the string cpu-pct to come first, then the objects with a target that includes the string memory, then example. This array could have any number of items, so re-sorting by index won't work. There could be 1 object with a target that includes cpu-pct, or there could be 50+. Same goes for the other strings I'm sorting by.

I thought about looping over the original array, checking if the desired string exists, saving the matching objects to new arrays for each target I'm looking for, then merging the arrays at the end. I think that would work, but I'd imagine there is a better, more efficient solution, probably using usort, but I'm at a loss. Any ideas?

3
  • So should this cpu-pct-0 appear before this cpu-pct-1? And will the target always contain one of cpu-pct, memery or example? Commented Apr 16, 2018 at 23:04
  • Create a custom sort comparison function using php.net/manual/en/function.usort.php You can define your rules easily inside one function. This function should return -1, 0, or 1 as its response when comparing two items. The examples in that page will help. Try this first, then feel free to post a second question with samples of your code attempts. Commented Apr 16, 2018 at 23:09
  • @fubar Yes, cpu-pct-0 should appear first and go in ascending order according to the number at the end. Forgot to mention, but there will also be a cpu-pct-avg, which should appear after the other cpu-pct values Commented Apr 17, 2018 at 16:07

2 Answers 2

2

usort is definitely the way to go. Here's a fairly generalised way of doing it, you can expand the $ranks array to include other terms to sort against if you like.

$a = json_decode('[
  { "target": "random.text.cpu-pct-0", "otherData": [1, 2, 3] },
  { "target": "random.text.cpu-pct-1", "otherData": [2, 2, 3] },
  { "target": "random.text.cpu-pct-2", "otherData": [3, 2, 3] },
  { "target": "random.text.example-0", "otherData": [4, 2, 3] },
  { "target": "random.text.example-1", "otherData": [5, 2, 3] },
  { "target": "random.text.memory", "otherData": [6, 2, 3] } ]');

$ranks = array('example' => 0, 'memory' => 1, 'cpu-pct' => 2);

function rank($obj) {
    global $ranks;

    foreach ($ranks as $key => $value) {
        if (strpos($obj->target, $key) !== false) return $value;
    }
    // sort anything that doesn't match last
    return -1;
}

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

usort($a, "cmp");

print_r($a);

Output:

Array
(
    [0] => stdClass Object
        (
            [target] => random.text.cpu-pct-0
            [otherData] => Array
                (
                    [0] => 1
                    [1] => 2
                    [2] => 3
                )

        )

    [1] => stdClass Object
        (
            [target] => random.text.cpu-pct-1
            [otherData] => Array
                (
                    [0] => 2
                    [1] => 2
                    [2] => 3
                )

        )

    [2] => stdClass Object
        (
            [target] => random.text.cpu-pct-2
            [otherData] => Array
                (
                    [0] => 3
                    [1] => 2
                    [2] => 3
                )

        )

    [3] => stdClass Object
        (
            [target] => random.text.memory
            [otherData] => Array
                (
                    [0] => 6
                    [1] => 2
                    [2] => 3
                )

        )

    [4] => stdClass Object
        (
            [target] => random.text.example-0
            [otherData] => Array
                (
                    [0] => 4
                    [1] => 2
                    [2] => 3
                )

        )

    [5] => stdClass Object
        (
            [target] => random.text.example-1
            [otherData] => Array
                (
                    [0] => 5
                    [1] => 2
                    [2] => 3
                )

        )

)

If you want to further sort on the value after the keyword, you could append that to the ranking value and then sort on the combined value e.g.

$ranks = array('cpu-pct', 'memory', 'example');

function rank($obj) {
    global $ranks;

    foreach ($ranks as $key => $value) {
        if (preg_match("/$value(.*)$/", $obj->target, $matches))
            return $key . $matches[1];
    }
    // sort anything that doesn't match last
    return 'z';
}

function cmp($a, $b) {
    return strcmp(rank($a), rank($b));
}

usort($a, "cmp");

If you had more than 10 strings to sort by, you would need to change the return value from rank to sprintf('%02d', $key) . $matches[1] to ensure sorting worked properly (replace 02 with as many digits as necessary to ensure you can represent all the sort strings in that many digits).

Sign up to request clarification or add additional context in comments.

2 Comments

This is a good solution and is working for me for the most part. Is there a way to ensure that all of the cpu-pct values go in ascending order (cpu-pct-0, cpu-pct-1, cpu-pct-2, etc.) and then have cpu-pct-avg come right after all of those? Right now those are all grouped together, but not in the order I want. The other items after the cpu-pct group are sorted correctly.
Hi Brett, I have updated the answer with a function that will sort on the trailing part of the string as well. Note that this is really dependent on your data format, if you wanted to get any more coimplicated you would need to do something on the lines of @Scuzzy 's answer.
1

Here's a usort approach, you have to define your conditions for every possible combination you wish to sort. Hopefully my code comments will give you a hint as to the approach.

$array = json_decode('[
  { "target": "random.text.cpu-pct-0" },
  { "target": "random.text.cpu-pct-1" },
  { "target": "random.text.cpu-pct-2"},
  { "target": "random.text.example-0" },
  { "target": "random.text.example-1" },
  { "target": "random.text.memory" }
]');

function mySortFunction( $one, $two )
{

  $pattern = '/\.(?<label>cpu-pct|example|memory)(?:-(?<value>\d+))?/';
  preg_match( $pattern, $one->target, $targetOne );
  preg_match( $pattern, $two->target, $targetTwo );

  // Both have CPU-PCT? then sort on CPU-PCT-VALUE
  if( $targetOne['label'] === 'cpu-pct' and $targetTwo['label'] === 'cpu-pct' )
  {
    return strcmp( $targetOne['value'], $targetTwo['value'] );
  }
  // Both have MEMORY? they are the same
  if( $targetOne['label'] === 'memory' and $targetTwo['label'] === 'memory' )
  {
    return 0;
  }
  // 1 has CPU but 2 has Memory, prefer CPU
  if( $targetOne['label'] === 'cpu-pct' and $targetTwo['label'] === 'memory' )
  {
    return -1;
  }
  // 1 has MEMORY but 2 has CPI, prefer CPU
  if( $targetOne['label'] === 'memory' and $targetTwo['label'] === 'cpu-pct' )
  {
    return 1;
  }
  // 1 is MEMORY or CPU, but 2 is Neither
  if( $targetOne['label'] === 'cpu-pct' or $targetOne['label'] === 'memory' )
  {
    if( $targetTwo['label'] !== 'cpu-pct' and $targetTwo['label'] !== 'memory' )
    {
      return -1;
    }
  }
  // 2 is MEMORY or CPU, but 1 is Neither
  if( $targetTwo['label'] === 'cpu-pct' or $targetTwo['label'] === 'memory' )
  {
    if( $targetOne['label'] !== 'cpu-pct' and $targetOne['label'] !== 'memory' )
    {
      return 1;
    }
  }
  // ETC
  // ETC
  // ETC
}

usort( $array, 'mySortFunction' );

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.