@nickolay0704

Как найти циклы в массиве?

Имеется массив такого вида:

Array
(
    [385] => 392
    [386] => 392
    [387] => 392
    [390] => 402
    [402] => 421
    [405] => 401
    [409] => 416
    [410] => 401
    [414] => 402
    [416] => 389
    [420] => 421
    [421] => 422
    [422] => 420
)

У каждого элемента должно быть своё конечное значение. Но есть в этом массиве проблемное место:

[420] => 421
[421] => 422
[422] => 420

420 переходит в 421, 421 в 422, 422 снова в 420 - тут идёт зацикливание цепочки.

Если есть зацикливание, то как получить эти три значения: 420, 421 и 422?

Понимаю, что нужно рекурсивно обойти массив и записывать повторы, но пока ничего толкового не смог сделать. Также нюанс в том, что эти переходящие друг в друга цепочки могут быть очень длинными, и в массиве может оказаться несколько таких цепочек.

Как получить эти зацикленные цепочки?
  • Вопрос задан
  • 281 просмотр
Решения вопроса 2
0xD34F
@0xD34F
Вот говнокод:

$result = [];

foreach ($arr as $n) {
  for ($visited = []; array_key_exists($n, $arr); $visited[] = $n, $n = $arr[$n]) {
    if (($i = array_search($n, $visited)) !== false) {
      $loop = array_slice($visited, $i);
      if (empty(array_intersect(array_column($result, 0), $loop))) {
        $result[] = $loop;
      }
      break;
    }
  }
}

или

$result = [];
$visited = [];
$iLoop = -1;

foreach ($arr as $n) {
  for ($iLoop++; isset($arr[$n]); $visited[$n] = $iLoop, $n = $arr[$n]) {
    if (isset($visited[$n])) {
      if ($visited[$n] === $iLoop) {
        for ($loop = [ $m = $n ]; ($m = $arr[$m]) !== $n; $loop[] = $m) ;
        $result[] = $loop;
      }
      break;
    }
  }
}
Ответ написан
Комментировать
@ksnk
Вот более оптимизированный говнокод ;)
$array = [
    385 => 392,
    386 => 392,
    387 => 392,
    390 => 402,
    402 => 421,
    405 => 401,
    409 => 416,
    410 => 401,
    414 => 402,
    416 => 389,
    420 => 421,
    421 => 422,
    422 => 420,
];

foreach ($array as $key => $value) {
    $level=0;
    $leaf=[$key=>$level]; $v=$value;
    while(true){
        if(!isset($array[$v])) break;
        $v=$array[$v];
        if(isset($leaf[$v])) {
            // loop found
            // is it real loop ?
            if($key==$v) {
                printf("\n %s => %s", $key, $value);
            }
            break;
        }
        $leaf[$v]=++$level;
    }
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
SilenceOfWinter
@SilenceOfWinter Куратор тега PHP
та еще зажигалка...
не совсем понятно зачем там рекурсия
$i = 1;
foreach ($array as $key => $value) {
    $arrayTail = array_slice($array, $i);
    if (in_array($key, $arrayTail)) {
         // зацикливание
    }
    $i++;
}
Ответ написан
@Vitsliputsli
Вариант с единственным чтением каждого элемента, если ссылок на кольцо много, оно все равно будет прочитано 1 раз, хотя кольцо храним в отдельном массиве.
do {
    $path = [key($array), $link = current($array)];
    while (isset($array[$link2 = $link])) {
        $link = $array[$link2];
        unset($array[$link2]);
        $pathPos = array_search($link,$path); // более для понятности, для длинных колец эффективнее хранить только ключи и искать по ним
        if (false !== $pathPos) {
            var_export(array_slice($path,$pathPos));
            break;
        }
        $path[] = $link;
    }
} while (false!==next($array));
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы