0

Using Laravel 5.6, I am creating a multidimensional array that looks like this:

array:8 [   
    0 => array:2 [
        0 => "ELA-2"
        1 => 7   
    ]   
    1 => array:2 [
        0 => "Science-3"
        1 => 9   
    ]   
    2 => array:2 [
        0 => "ELA-1"
        1 => 5   
    ]   
    3 => array:2 [
        0 => "Science-2"
        1 => 9
    ]   
    4 => array:2 [
        0 => "ELA-4"
        1 => 2   
    ]   
    5 => array:2 [
        0 => "ELA-3"
        1 => 7   
    ]   
    6 => array:2 [
        0 => "Science-4"
        1 => 2   
    ]   
    7 => array:2 [
        0 => "Science-1"
        1 => 1   
    ] 
]

This was created using array_count_values and array_map (if that matters).

What I want is the data to look like this:

array:8 [   
    0 => array:3 [
        "Subject" => "ELA"
        "Level" => 2
        "Count" => 7   
    ]   
    1 => array:3 [
        "Subject" => "Science"
        "Level" => 3
        "Count" => 9 
    ]   
    2 => array:3 [
        "Subject" => "ELA"
        "Level" => 1
        "Count" => 5 
    ]   
    3 => array:3 [
        "Subject" => "Science"
        "Level" => 2
        "Count" => 9
    ]   
    4 => array:3 [
        "Subject" => "ELA"
        "Level" => 4
        "Count" => 2   
    ]   
    5 => array:3 [
        "Subject" => "ELA"
        "Level" => 3
        "Count" => 7  
    ]   
    6 => array:3 [
        "Subject" => "Science"
        "Level" => 4
        "Count" => 2   
    ]   
    7 => array:3 [
        "Subject" => "Science"
        "Level" => 1
        "Count" => 1  
    ] 
]

I am not sure how to:

  1. break apart the values from key[0] within each array and separate them into 2 parts—the first portion for the 'Subject' and the second portion for the 'Level'
  2. relabel the numeric incrementing keys (there will be 3 within each array) to the words "Subject", "Level", and "Count", respectively.

My current code looks like this:

// create an empty array
$array_holder = [];

// loop through each student
foreach($class_students as $student) {

    // loop through each subject
    foreach($class_subjects as $subject) {

        // count each child at his/her level (rounded average)
        $childs_level = AssessmentData::where('student_id', $student->id)->where('subject_id', $subject->subject_id)->avg('assessed_level');

        // get the subject name
        $current_subject = Subject::where('id', $subject->subject_id)->first();

        // round the average
        $childs_level = round($childs_level);

        // convert the childs_level into a whole number
        $childs_level = number_format($childs_level, 0);

        // add each child by appending to an array
        if ($childs_level != 0) {
            $compiled_array[] = array_push($array_holder, $current_subject->short_name."-".$childs_level);
        }
    }
}

// count each assessed_level from the resultant array
$counted_array = array_count_values($array_holder);

// remap the array
$counted_array = array_map(null, array_keys($counted_array), $counted_array);

Additional request - sorting

I would like the results to be sorted by count so that Level 1 is first, Level 2 is second, Level 3 is third, and Level 4 is fourth. (Currently, it is sorted in the opposite order - Level 4 is first, Level 3 is second, etc.)

2 Answers 2

1

You have to loop the original array and create a new one:

<?php
$original_array = [
    ['ELA-2', 7],
    ['Science-3', 9],
    ['ELA-1', 5]
    // you can fill in the rest
];

$new_array = [];
foreach($original_array as $v)
{
    $new_array[] = [
        'Subject' => explode('-', $v[0])[0], // split the subject on "-" and retrieve the first item
        'Level' => explode('-', $v[0])[1], // split the subject on "-" and retrieve the second item
        'Count' => $v[1] // this is given to us so just use it
    ];
}

// Sort the finished product in ascending order based on the "count" of the sub-arrays
//
// You can also use this earlier on $original_array if you want
// but you would just change the arithmetic to $a[ 1 ] - $b [ 1 ]
usort( $new_array, function( $a, $b ){
    return $a[ 'Count' ] - $b[ 'Count' ];
} );

print_r($new_array);

Output:

Array
(
    [0] => Array
        (
            [Subject] => ELA
            [Level] => 1
            [Count] => 5
        )

    [1] => Array
        (
            [Subject] => ELA
            [Level] => 2
            [Count] => 7
        )

    [2] => Array
        (
            [Subject] => Science
            [Level] => 3
            [Count] => 9
        )

)

If you wanted to get fancy and array_map() it then this is achieves the same result as foreach(){}:

$new_array = array_map(
    function($v){
        return [
            'Subject' => explode('-', $v[0])[0], // split the subject on "-" and retrieve the first item
            'Level' => explode('-', $v[0])[1], // split the subject on "-" and retrieve the second item
            'Count' => $v[1] // this is given to us so just use it
        ];
    },
    $original_array
);
Sign up to request clarification or add additional context in comments.

10 Comments

Bang on with no need to wrangle this solution into my code. Thank you very much for the clear answer!
@SheldonScott You're welcome! One thing to note is that if the subject has multiple hyphens like Bio-Science-3-2 or something then the use of explode('-', $v[0])[0] will not be ideal and more processing logic will need to be added.
How might I be able to sort the array in descending order? (It is currently in ascending order.)
@SheldonScott Can you define "descending", please? What criteria puts one subject in front of another? If you'd like, please provide an example in your initial question of desired sort order.
@SheldonScott Haha, thanks! Learning to traverse and manipulate arrays has quite possibly had the biggest positive impact on achieving elegant solutions so I'm glad I can share with others
|
0

I want to add an alternative approach. Important notes:

  • don't ask PHP to perform the same explode() call on the same string twice -- cache it as a temporary variable, or as shown in my snippet use all generated elements at once.
  • limit your explode() call by declaring the limit parameter (3rd param) so that the output is consistent and the script is clear about the intended action (seeing the input data is not required to understand the intention).
  • the spaceship operator in usort() is very versatile and will allow you to make a variety of comparisons without changing syntax.

Code: (Demo)

$counted_array = [
    ["ELA-2", 7], 
    ["Science-3", 9],
    ["ELA-1", 5],
    ["Science-2", 9],
    ["ELA-4", 2], 
    ["ELA-3", 7],
    ["Science-4", 2], 
    ["Science-1", 1]
];

$assoc_array = array_map(
    function($subarray){
        return array_combine(
                   ['Subject', 'Level'], explode('-', $subarray[0], 2)
               )
               + ['Count' => $subarray[1]];
    },
    $counted_array
);

usort($assoc_array, function($a, $b) {
    return [$a['Subject'], $a['Level']]
           <=>
           [$b['Subject'], $b['Level']];
});

var_export($assoc_array);

I noticed in your question, you asked for the data to be sorted on Count, but then proceeded to explain how you expected the sorting by Level -- this is ambiguous.

If you want to sort on Count, just use:

usort($assoc_array, function($a, $b) {
    return $a['Count'] <=> $b['Count'];
});

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.