PHP: Пошаговое обновление состояния массива. Симуляция распространения энергии. Метод array_fill.
Glossary overview

PHP: Пошаговое обновление состояния массива. Симуляция распространения энергии. Метод array_fill.

Фабула

$cells = [3, 1, 0, 2, 4, 0, 1];

Есть массив чисел это “клетки”:

Каждое число это “энергия” клетки.

Правила одного шага

  1. Каждая клетка с энергией > 0:
  • теряет 1 энергию
  • отдает +1 энергии соседям (левому и правому, если они есть)
  1. Клетки с 0 энергией:
  • ничего не отдают
  • могут получать энергию от соседей
  1. Все изменения применяются одновременно, не по очереди.

Нужно

  1. Реализовать функцию step(array $cells): array
  2. Прогнать симуляцию N шагов
  3. После каждого шага сохранять состояние

Пример

start: [3,1,0,2]
step1: [3,1,2,1]
step2: [3,2,3,1]

Решение.

<?php

$cells = [3, 1, 0, 2, 4, 0, 1];

function step(array $cells): array
{
    $n = count($cells);

    // 1) база: -1 всем, кто > 0
    $base = [];
    for ($i = 0; $i < $n; $i++) {
        $base[$i] = ($cells[$i] > 0) ? ($cells[$i] - 1) : 0;
    }

    // 2) прибавки (дельты) от соседей, считаем по исходному $cells
    //    считаем, сколько энергии КАЖДАЯ клетка получит от соседей за шаг...
    $delta = array_fill(0, $n, 0);

    for ($i = 0; $i < $n; $i++) {
        if ($cells[$i] <= 0) {
            continue;
        }

        if ($i - 1 >= 0) {
            $delta[$i - 1] += 1;
        }
        if ($i + 1 < $n) {
            $delta[$i + 1] += 1;
        }
    }

    // 3) итог: база + дельта
    $result = [];
    for ($i = 0; $i < $n; $i++) {
        $result[$i] = $base[$i] + $delta[$i];
    }

    return $result;
}

$step1 = step($cells);
print_r($step1);

// Output
Array
(
    [0] => 1
    [1] => 1
    [2] => 2
    [3] => 1
    [4] => 1
    [5] => 2
    [6] => 0
)

Особенности:

  • array_fill – создает массив нужной длины и сразу заполняет его одним значением.

Пример:

$a = array_fill(0, 5, 0);

// Output
[0, 0, 0, 0, 0]

То есть:

  • начать с индекса 0
  • создать 5 элементов
  • каждый элемент равен 0

Наше решение состоит из 3 этапов:

  • Отнимаем если надо от каждого элемента -1. Собираем в массив base.
function step(array $cells): array
{
$n = count($cells);

// 1) база: -1 всем, кто > 0
$base = [];
for ($i = 0; $i < $n; $i++) {
    $base[$i] = ($cells[$i] > 0) ? ($cells[$i] - 1) : 0;
}
...

// Output
Array
(
    [0] => 2
    [1] => 0
    [2] => 0
    [3] => 1
    [4] => 3
    [5] => 0
    [6] => 0
)
  • Считаем сколько энергии КАЖДАЯ клетка получит от соседей за шаг.

Формируем массив прибавок. Получаем массив сколько надо прибавить к каждому элементу

 // 2) прибавки (дельты) от соседей, считаем по исходному $cells
    //    считаем, сколько энергии КАЖДАЯ клетка получит от соседей за шаг...
    $delta = array_fill(0, $n, 0);

    for ($i = 0; $i < $n; $i++) {
        if ($cells[$i] <= 0) {
            continue;
        }

        if ($i - 1 >= 0) {
            $delta[$i - 1] += 1;
        }
        if ($i + 1 < $n) {
            $delta[$i + 1] += 1;
        }
    }

// Output
Array
(
    [0] => 1
    [1] => 1
    [2] => 2
    [3] => 1
    [4] => 1
    [5] => 2
    [6] => 0
)

это значит:

  • клетка 0 получит +1
  • клетка 1 получит +1
  • клетка 2 получит +2

Мы ничего еще не применяем, только считаем.

  • Последний этап, сумируем Базовый массив с прибавочным.
    // 3) итог: база + дельта
    $result = [];
    for ($i = 0; $i < $n; $i++) {
        $result[$i] = $base[$i] + $delta[$i];
    }

    return $result;

// Output
Array
(
    [0] => 3
    [1] => 1
    [2] => 2
    [3] => 2
    [4] => 4
    [5] => 2
    [6] => 0
)

И еще я хочу вызывать функцию в цикле, пока например массив не перестанет меняться (условно, такого не будет):

<?php

$cells = [3, 1, 0, 2, 4, 0, 1];

$history = [];
$step = 0;

while (true) {
    // сохраняем текущее состояние
    $history["step{$step}"] = $cells;

    $next = step($cells);

    // если изменений больше нет — выходим
    if ($next === $cells) {
        break;
    }
    
    if ($step > 100) {
	    break;
	}

    $cells = $next;
    $step++;
}

// вывод
foreach ($history as $name => $state) {
    echo $name . ': [' . implode(',', $state) . ']' . PHP_EOL;
}


function step(array $cells): array
{
    $n = count($cells);

    // 1) база: -1 всем, кто > 0
    $base = [];
    for ($i = 0; $i < $n; $i++) {
        $base[$i] = ($cells[$i] > 0) ? ($cells[$i] - 1) : 0;
    }

    // 2) прибавки (дельты) от соседей, считаем по исходному $cells
    //    считаем, сколько энергии КАЖДАЯ клетка получит от соседей за шаг...
    $delta = array_fill(0, $n, 0);

    for ($i = 0; $i < $n; $i++) {
        if ($cells[$i] <= 0) {
            continue;
        }

        if ($i - 1 >= 0) {
            $delta[$i - 1] += 1;
        }
        if ($i + 1 < $n) {
            $delta[$i + 1] += 1;
        }
    }

    // 3) итог: база + дельта
    $result = [];
    for ($i = 0; $i < $n; $i++) {
        $result[$i] = $base[$i] + $delta[$i];
    }

    return $result;
}

// Output
step0: [3,1,0,2,4,0,1]
step1: [3,1,2,2,4,2,0]
step2: [3,2,3,3,5,2,1]
step3: [3,3,4,4,6,3,1]
step4: [3,4,5,5,7,4,1]
step5: [3,5,6,6,8,5,1]
step6: [3,6,7,7,9,6,1]
step7: [3,7,8,8,10,7,1]
step8: [3,8,9,9,11,8,1]
step9: [3,9,10,10,12,9,1]
step10: [3,10,11,11,13,10,1]
step11: [3,11,12,12,14,11,1]
...

Базовая идея

Есть:

  • текущее состояние $cells
  • новое состояние $next = step($cells)
  • $history хранит все состояния: start, step1, step2…

Если:

$next === $cells

значит система перестала меняться, можно останавливать цикл.

Более аккуратный вариант (функция)

function simulate(array $cells, int $maxSteps = 100): array
{
    $history = [];
    $step = 0;

    while ($step < $maxSteps) {
        $history[] = $cells;

        $next = step($cells);
        if ($next === $cells) {
            break;
        }

        $cells = $next;
        $step++;
    }

    return $history;
}

Весь код

<?php

$cells = [3, 1, 0, 2, 4, 0, 1];

function simulate(array $cells, int $maxSteps = 100): array
{
    $history = [];
    $step = 0;

    while ($step < $maxSteps) {
        $history[] = $cells;

        $next = step($cells);
        if ($next === $cells) {
            break;
        }

        $cells = $next;
        $step++;
    }

    return $history;
}



function step(array $cells): array
{
    $n = count($cells);

    // 1) база: -1 всем, кто > 0
    $base = [];
    for ($i = 0; $i < $n; $i++) {
        $base[$i] = ($cells[$i] > 0) ? ($cells[$i] - 1) : 0;
    }

    // 2) прибавки (дельты) от соседей, считаем по исходному $cells
    //    считаем, сколько энергии КАЖДАЯ клетка получит от соседей за шаг...
    $delta = array_fill(0, $n, 0);

    for ($i = 0; $i < $n; $i++) {
        if ($cells[$i] <= 0) {
            continue;
        }

        if ($i - 1 >= 0) {
            $delta[$i - 1] += 1;
        }
        if ($i + 1 < $n) {
            $delta[$i + 1] += 1;
        }
    }

    // 3) итог: база + дельта
    $result = [];
    for ($i = 0; $i < $n; $i++) {
        $result[$i] = $base[$i] + $delta[$i];
    }

    return $result;
}

print_r(simulate($cells, 20));