objects array php arrays recursion identity circular-reference

objects - search in arrays php



¿Hay una manera de detectar matrices circulares en PHP puro? (4)

Estoy intentando implementar mi propia función de estilo de serialización / var_dump en PHP. Parece imposible si existe la posibilidad de arreglos circulares (que hay).

En versiones recientes de PHP, var_dump parece detectar matrices circulares:

php > $a = array(); php > $a[] = &$a; php > var_dump($a); array(1) { [0]=> &array(1) { [0]=> *RECURSION* } }

¿Cómo implementaría mi propio tipo de método de serialización en PHP que puede detectar de manera similar? No puedo simplemente hacer un seguimiento de las matrices que he visitado, porque la comparación estricta de las matrices en PHP devuelve verdadero para las diferentes matrices que contienen los mismos elementos y la comparación de las matrices circulares provoca un error fatal, de todos modos.

php > $b = array(1,2); php > $c = array(1,2); php > var_dump($b === $c); bool(true) php > $a = array(); php > $a[] = &$a; php > var_dump($a === $a); PHP Fatal error: Nesting level too deep - recursive dependency? in php shell code on line 1

He buscado una forma de encontrar una ID única (puntero) para una matriz, pero no puedo encontrar una. spl_object_hash solo funciona en objetos, no en arreglos. Si lanzo varios arreglos diferentes a objetos, todos obtendrán el mismo valor de spl_object_hash (¿por qué?).

EDITAR:

Llamar a print_r, var_dump o serializar en cada matriz y luego usar algún mecanismo para detectar la presencia de recursión tal como lo detectan esos métodos es una pesadilla de complejidad algorítmica y básicamente hará que cualquier uso sea demasiado lento para que sea práctico en matrices anidadas grandes.

RESPUESTA ACEPTADA:

Acepté la siguiente respuesta que fue la primera en sugerir que se modifique temporalmente la matriz para ver si es igual a otra matriz. Eso responde al "¿Cómo comparo dos matrices para la identidad?" a partir de la cual la detección de recursión es trivial.


El siguiente método isRecursiveArray (array) detecta matrices circulares / recursivas. Mantiene un registro de las matrices que se han visitado agregando temporalmente un elemento que contiene una referencia de objeto conocida al final de la matriz.

Si necesita ayuda para escribir el método de serialización, actualice su pregunta de tema y proporcione un formato de muestra de serialización en su pregunta.

function removeLastElementIfSame(array & $array, $reference) { if(end($array) === $reference) { unset($array[key($array)]); } } function isRecursiveArrayIteration(array & $array, $reference) { $last_element = end($array); if($reference === $last_element) { return true; } $array[] = $reference; foreach($array as &$element) { if(is_array($element)) { if(isRecursiveArrayIteration($element, $reference)) { removeLastElementIfSame($array, $reference); return true; } } } removeLastElementIfSame($array, $reference); return false; } function isRecursiveArray(array $array) { $some_reference = new stdclass(); return isRecursiveArrayIteration($array, $some_reference); } $array = array(''a'',''b'',''c''); var_dump(isRecursiveArray($array)); print_r($array); $array = array(''a'',''b'',''c''); $array[] = $array; var_dump(isRecursiveArray($array)); print_r($array); $array = array(''a'',''b'',''c''); $array[] = &$array; var_dump(isRecursiveArray($array)); print_r($array); $array = array(''a'',''b'',''c''); $array[] = &$array; $array = array($array); var_dump(isRecursiveArray($array)); print_r($array);


Método divertido (sé que es estúpido :)), pero puedes modificarlo y seguir el "camino" hacia el elemento recursivo. Esto es solo una idea :) En base a la propiedad de la cadena serializada, cuando se inicie la recursión será la misma que la cadena de la matriz original. Como puede ver, lo probé en muchas variaciones diferentes y podría ser algo que pueda "engañarlo", pero "detecta" todas las recursiones enumeradas. Y no probé matrices recursivas con objetos.

$a = array(''b1''=>''a1'',''b2''=>''a2'',''b4''=>''a3'',''b5''=>''R:1;}}}''); $a[''a1''] = &$a; $a[''b6''] = &$a; $a[''b6''][] = array(1,2,&$a); $b = serialize($a); print_r($a); function WalkArrayRecursive(&$array_name, &$temp){ if (is_array($array_name)){ foreach ($array_name as $k => &$v){ if (is_array($v)){ if (strpos($temp, preg_replace(''#R:/d+;/}+$#'', '''', serialize($v)))===0) { echo "/n Recursion detected at " . $k ."/n"; continue; } WalkArrayRecursive($v, $temp); } } } } WalkArrayRecursive($a, $b);

regexp es para la situación cuando el elemento con recursión está en el ''final'' de la matriz. y, sí, esta recursión está relacionada con toda la matriz. Es posible hacer una recursión de los subelementos, pero es demasiado tarde para que piense en ellos. De alguna manera, cada elemento de la matriz debe ser revisado para la recursión en sus subelementos. De la misma manera, como arriba, a través de la salida de la función print_r, o buscando un registro específico para la recursión en una cadena serializada ( R:4;} algo como esto). Y el rastreo debería comenzar a partir de ese elemento, comparando todo lo que aparece debajo de mi script. Todo esto es solo si desea detectar dónde comienza la recursión, no solo si la tiene o no.

ps: pero lo mejor debería ser, como creo, escribir tu propia función unserialize a partir de una cadena seriada creada por php en sí.


Mi enfoque es tener una matriz temporal que contenga una copia de todos los objetos que ya se iteraron. así aquí

// We use this to detect recursion. global $recursion; $recursion = []; function dump( $data, $label, $level = 0 ) { global $recursion; // Some nice output for debugging/testing... echo "/n"; echo str_repeat( " ", $level ); echo $label . " (" . gettype( $data ) . ") "; // -- start of our recursion detection logic if ( is_object( $data ) ) { foreach ( $recursion as $done ) { if ( $done === $data ) { echo "*RECURSION*"; return; } } // This is the key-line: Remember that we processed this item! $recursion[] = $data; } // -- end of recursion check if ( is_array( $data ) || is_object( $data ) ) { foreach ( (array) $data as $key => $item ) { dump( $item, $key, $level + 1 ); } } else { echo "= " . $data; } }

Y aquí hay un rápido código de demostración para ilustrar cómo funciona:

$obj = new StdClass(); $obj->arr = []; $obj->arr[] = ''Foo''; $obj->arr[] = $obj; $obj->arr[] = ''Bar''; $obj->final = 12345; $obj->a2 = $obj->arr; dump( $obj, ''obj'' );

Este script generará la siguiente salida:

obj (object) arr (array) 0 (string) = Foo 1 (object) *RECURSION* 2 (string) = Bar final (integer) = 12345 a2 (array) 0 (string) = Foo 1 (object) *RECURSION* 2 (string) = Bar


No es elegante, pero resuelve su problema (al menos si no tiene a alguien que use * RECURSION * como un valor).

<?php $a[] = &$a; if(strpos(print_r($a,1),''*RECURSION*'') !== FALSE) echo 1;