Создание кросс-таблицы или сводной таблицы из массива в php

У меня есть массив объектов, определенный аналогично приведенному ниже:

$scores = array();

// Bob round 1
$s = new RoundScore();
$s->Round_Name = 'Round 1';
$s->Player_Name = 'Bob';
$s->Score = 10;
$scores[0] = $s;

// Bob round 2
$s = new RoundScore();
$s->Round_Name = 'Round 2';
$s->Player_Name = 'Bob';
$s->Score = 7;
$scores[1] = $s;

// Jack round 1
$s = new RoundScore();
$s->Round_Name = 'Round 1';
$s->Player_Name = 'Jack';
$s->Score = 6;
$scores[2] = $s;

// Jack round 2
$s = new RoundScore();
$s->Round_Name = 'Round 2';
$s->Player_Name = 'Jack';
$s->Score = 12;
$scores[3] = $s;

Если я пройдусь по циклу и выгружу объект $scores в таблицу, это будет выглядеть примерно так:

Round_Name   Player   Score
----------------------------
Round 1      Bob        10
Round 2      Bob         7
Round 1      Jack        6
Round 2      Jack       12

Однако я хочу что-то вроде этого:

Player  Round 1  Round 2  Total
-------------------------------
Bob       10        7       17
Jack       6       12       18

Я не буду знать заранее, сколько раундов или игроков будет, и скажем так, я не могу изменить способ построения объектов.

Какой самый эффективный способ сделать это в php?


person Damovisa    schedule 03.04.2009    source источник
comment
Я думаю, у вас опечатка. Для второго раунда Боба у вас есть: $rounds[1] = $s; вместо: $scores[1] = $s;   -  person maxyfc    schedule 03.04.2009
comment
Спасибо, вы правы - я исправил это сейчас :)   -  person Damovisa    schedule 03.04.2009


Ответы (2)


Если мы можем предположить, что:

  • порядок очков в массиве всегда идет по имени игрока, а затем по круглому номеру
  • количество раундов одинаково для каждого игрока

Затем мы можем вывести счет каждого игрока по мере того, как мы перемещались по массиву, вычисляя общее количество в процессе, но сбрасывая его, если мы видим нового игрока:

$round_count = 0;
$header_printed = false;
$current_player = NULL;
$current_total = 0;
$current_output_line = "";
foreach ($scores as $score) {
    // Check whether we have to move to a new player
    if ($score->Player_Name != $current_player) {
        // Check whether we have anything to print before
        // resetting the variables
        if (!is_null($current_player)) {
            if (!$header_printed) {
                printf("%-10s", "Player");
                for ($i = 0; $i < $round_count; $i++) {
                    printf("%-10s", "Round $i");
                }
                printf("%-10s\n", "Total");

                $header_printed = true;
            }

            $current_output_line .= sprintf("%5d\n", $current_total);
            print $current_output_line;
        }

        // Reset the total and various variables for the new player
        $round_count = 0;
        $current_player = $score->Player_Name;
        $current_total = 0;
        $current_output_line = sprintf("%-10s", $score->Player_Name);
    }

    $round_count++;
    $current_total += $score->Score;
    $current_output_line .= sprintf("%5d     ", $score->Score);
}
// The last player is not printed because we exited the loop 
// before the print statement, so we need a print statement here.
if ($current_output_line != "") {
    $current_output_line .= sprintf("%5d\n", $current_total);
    print $current_output_line;
}

Пример вывода:

Player    Round 0   Round 1   Total
Bob          10         7        17
Jack          6        12        18

Это должно быть довольно эффективно, потому что он проходит через массив только один раз.

person maxyfc    schedule 03.04.2009

Насколько я могу судить, массивы PHP реализованы в виде хэш-таблиц (поэтому поиск/обновление должны быть довольно эффективными). Будет ли эффективность использования времени вообще проблемой?

Я бы просто сделал это "простым" способом:

$table = array();
$round_names = array();
$total = array();

foreach ($scores as $score)
{
    $round_names[] = $score->Round_Name;
    $table[$score->Player_Name][$score->Round_Name] = $score->score;
    $total[$score->Player_Name] += $score->score;
}

$round_names = array_unique($round_names);

foreach ($table as $player => $rounds)
{
    echo "$player\t";
    foreach ($round_names as $round)
        echo "$rounds[$round]\t";
    echo "$total[$player]\n";
}

(Я знаю, что массивы не инициализированы должным образом, но вы поняли идею)

person v3.    schedule 03.04.2009
comment
Я проголосовал за этот ответ, а не за ответ maxyfc, потому что он намного проще и надежнее. Я действительно не вижу, чтобы производительность была здесь узким местом; ремонтопригодность кода важнее. Вы, вероятно, также захотите сделать natsort($round_names) кстати. - person Zecc; 20.06.2011
comment
Почему вы использовали foreach ($round_names as $round) echo rounds[$round]\t; вместо foreach ($rounds как $round) echo $round - person sanu; 02.04.2017