tratar rebelde para padres los hoy hijo entender consejos como carta años adolescentes php recursion tree

php - rebelde - consejos para padres de adolescentes



Convierta una serie de relaciones padre-hijo en un árbol jerárquico? (8)

Tengo un montón de pares nombre-padre, que me gustaría convertir en estructuras de árbol jerárquico lo menos posible. Entonces, por ejemplo, estos podrían ser los emparejamientos:

Child : Parent H : G F : G G : D E : D A : E B : C C : E D : NULL

Que necesita ser transformado en (a) árbol (s) jerárquico (s):

D ├── E │ ├── A │ │ └── B │ └── C └── G ├── F └── H

El resultado final que deseo es un conjunto anidado de elementos <ul> , con cada <li> contiene el nombre del niño.

No hay inconsistencias en los emparejamientos (el niño es su propio padre, el padre es el hijo del niño, etc.), por lo que probablemente se puedan hacer muchas optimizaciones.

¿Cómo, en PHP, podría ir de una matriz que contiene pares hijo => padres, a un conjunto de <ul> s anidados?

Tengo la sensación de que la recursividad está involucrada, pero no estoy lo suficientemente despierto como para pensarlo bien.


Bueno, para analizar ULs y LIs, sería algo así como:

$array = array ( ''H'' => ''G'' ''F'' => ''G'' ''G'' => ''D'' ''E'' => ''D'' ''A'' => ''E'' ''B'' => ''C'' ''C'' => ''E'' ''D'' => ''NULL'' ); recurse_uls ($array, ''NULL''); function recurse_uls ($array, $parent) { echo ''<ul>''; foreach ($array as $c => $p) { if ($p != $parent) continue; echo ''<li>''.$c.''</li>''; recurse_uls ($array, $c); } echo ''</ul>''; }

Pero me encantaría ver una solución que no requiera que iteres a través de la matriz tan a menudo ...


Bueno, primero convertiría el conjunto directo de pares clave-valor en una matriz jerárquica

function convertToHeiarchical(array $input) { $parents = array(); $root = array(); $children = array(); foreach ($input as $item) { $parents[$item[''id'']] = &$item; if ($item[''parent_id'']) { if (!isset($children[$item[''parent_id'']])) { $children[$item[''parent_id'']] = array(); } $children[$item[''parent_id'']][] = &$item; } else { $root = $item[''id'']; } } foreach ($parents as $id => &$item) { if (isset($children[$id])) { $item[''children''] = $children[$id]; } else { $item[''children''] = array(); } } return $parents[$root]; }

Eso puede convertir una matriz plana con parent_id e id en uno jerárquico:

$item = array( ''id'' => ''A'', ''blah'' => ''blah'', ''children'' => array( array( ''id'' => ''B'', ''blah'' => ''blah'', ''children'' => array( array( ''id'' => ''C'', ''blah'' => ''blah'', ''children'' => array(), ), ), ''id'' => ''D'', ''blah'' => ''blah'', ''children'' => array( array( ''id'' => ''E'', ''blah'' => ''blah'', ''children'' => array(), ), ), ), ), );

Entonces, solo crea una función de renderizado:

function renderItem($item) { $out = "Your OUtput For Each Item Here"; $out .= "<ul>"; foreach ($item[''children''] as $child) { $out .= "<li>".renderItem($child)."</li>"; } $out .= "</ul>"; return $out; }


Esto es lo que se me ocurrió:

$arr = array( ''H'' => ''G'', ''F'' => ''G'', ''G'' => ''D'', ''E'' => ''D'', ''A'' => ''E'', ''B'' => ''C'', ''C'' => ''E'', ''D'' => null ); $nested = parentChild($arr); print_r($nested); function parentChild(&$arr, $parent = false) { if( !$parent) { //initial call $rootKey = array_search( null, $arr); return array($rootKey => parentChild($arr, $rootKey)); }else { // recursing through $keys = array_keys($arr, $parent); $piece = array(); if($keys) { // found children, so handle them if( !is_array($keys) ) { // only one child $piece = parentChild($arr, $keys); }else{ // multiple children foreach( $keys as $key ){ $piece[$key] = parentChild($arr, $key); } } }else { return $parent; //return the main tag (no kids) } return $piece; // return the array built via recursion } }

productos:

Array ( [D] => Array ( [G] => Array ( [H] => H [F] => F ) [E] => Array ( [A] => A [C] => Array ( [B] => B ) ) ) )


Esto requiere una función recursiva muy básica para analizar los pares hijo / padre a una estructura de árbol y otra función recursiva para imprimirla. Solo una función sería suficiente, pero aquí hay dos para mayor claridad (se puede encontrar una función combinada al final de esta respuesta).

Primero inicialice la matriz de pares hijo / padre:

$tree = array( ''H'' => ''G'', ''F'' => ''G'', ''G'' => ''D'', ''E'' => ''D'', ''A'' => ''E'', ''B'' => ''C'', ''C'' => ''E'', ''D'' => null );

Luego, la función que analiza esa matriz en una estructura de árbol jerárquica:

function parseTree($tree, $root = null) { $return = array(); # Traverse the tree and search for direct children of the root foreach($tree as $child => $parent) { # A direct child is found if($parent == $root) { # Remove item from tree (we don''t need to traverse this again) unset($tree[$child]); # Append the child into result array and parse its children $return[] = array( ''name'' => $child, ''children'' => parseTree($tree, $child) ); } } return empty($return) ? null : $return; }

Y una función que atraviesa ese árbol para imprimir una lista desordenada:

function printTree($tree) { if(!is_null($tree) && count($tree) > 0) { echo ''<ul>''; foreach($tree as $node) { echo ''<li>''.$node[''name'']; printTree($node[''children'']); echo ''</li>''; } echo ''</ul>''; } }

Y el uso real:

$result = parseTree($tree); printTree($result);

Aquí está el contenido de $result :

Array( [0] => Array( [name] => D [children] => Array( [0] => Array( [name] => G [children] => Array( [0] => Array( [name] => H [children] => NULL ) [1] => Array( [name] => F [children] => NULL ) ) ) [1] => Array( [name] => E [children] => Array( [0] => Array( [name] => A [children] => NULL ) [1] => Array( [name] => C [children] => Array( [0] => Array( [name] => B [children] => NULL ) ) ) ) ) ) ) )

Si desea un poco más de eficiencia, puede combinar esas funciones en una sola y reducir el número de iteraciones realizadas:

function parseAndPrintTree($root, $tree) { $return = array(); if(!is_null($tree) && count($tree) > 0) { echo ''<ul>''; foreach($tree as $child => $parent) { if($parent == $root) { unset($tree[$child]); echo ''<li>''.$child; parseAndPrintTree($child, $tree); echo ''</li>''; } } echo ''</ul>''; } }

Solo guardarás 8 iteraciones en un conjunto de datos tan pequeño como este pero en conjuntos más grandes podría marcar la diferencia.


Otra forma más simplificada de convertir la estructura plana en $tree en una jerarquía. Solo se necesita una matriz temporal para exponerlo:

// add children to parents $flat = array(); # temporary array foreach ($tree as $name => $parent) { $flat[$name][''name''] = $name; # self if (NULL === $parent) { # no parent, is root element, assign it to $tree $tree = &$flat[$name]; } else { # has parent, add self as child $flat[$parent][''children''][] = &$flat[$name]; } } unset($flat);

Eso es todo para que la jerarquía se convierta en una matriz multidimensional:

Array ( [children] => Array ( [0] => Array ( [children] => Array ( [0] => Array ( [name] => H ) [1] => Array ( [name] => F ) ) [name] => G ) [1] => Array ( [name] => E [children] => Array ( [0] => Array ( [name] => A ) [1] => Array ( [children] => Array ( [0] => Array ( [name] => B ) ) [name] => C ) ) ) ) [name] => D )

El resultado es menos trivial si desea evitar la recursión (puede ser una carga para las estructuras grandes).

Siempre quise resolver el "dilema" de UL / LI para generar una matriz. El dilema es que cada elemento no sabe si los niños darán seguimiento o cuántos elementos anteriores deben cerrarse. En otra respuesta ya lo resolví usando un RecursiveIteratorIterator y buscando getDepth() y otra metainformación que me proporcionó mi propio Iterator escrito: Obteniendo el modelo de conjunto anidado en un <ul> pero ocultando los subárboles "cerrados" . Esa answer muestra que con los iteradores eres bastante flexible.

Sin embargo, esa era una lista preordenada, por lo que no sería adecuada para su ejemplo. Además, siempre quise resolver esto para una especie de estructura de árbol estándar y elementos <ul> y <li> HTML.

El concepto básico que surgió es el siguiente:

  1. TreeNode : resume cada elemento en un tipo TreeNode simple que puede proporcionar su valor (por ejemplo, Name ) y si tiene o no hijos.
  2. TreeNodesIterator - Un RecursiveIterator que puede iterar sobre un conjunto (matriz) de estos TreeNodes . Eso es bastante simple ya que el tipo TreeNode ya sabe si tiene hijos y cuáles.
  3. RecursiveListIterator - Un RecursiveIteratorIterator que tiene todos los eventos necesarios cuando itera recursivamente sobre cualquier tipo de RecursiveIterator :
    • beginIteration / endIteration - Comienzo y fin de la lista principal.
    • beginElement / endElement - endElement y fin de cada elemento.
    • beginChildren / endChildren - Comienzo y fin de cada lista de niños. Este RecursiveListIterator solo proporciona estos eventos en forma de llamadas a función. las listas de niños, como es típico para las listas <ul><li> , se abren y cierran dentro de su elemento padre <li> . Por lo tanto, el evento endElement se endElement después del evento endChildren . Esto podría cambiarse o configurarse para ampliar el uso de esta clase. Los eventos se distribuyen como llamadas de funciones a un objeto decorador, para mantener las cosas separadas.
  4. ListDecorator - Una clase de "decorador" que es solo un receptor de los eventos de RecursiveListIterator .

Comienzo con la lógica de salida principal. Tomando la matriz $tree ahora jerárquica, el código final se ve así:

$root = new TreeNode($tree); $it = new TreeNodesIterator(array($root)); $rit = new RecursiveListIterator($it); $decor = new ListDecorator($rit); $rit->addDecorator($decor); foreach($rit as $item) { $inset = $decor->inset(1); printf("%s%s/n", $inset, $item->getName()); }

Primero veamos el ListDecorator que simplemente envuelve los elementos <ul> y <li> y está decidiendo cómo se ListDecorator la estructura de la lista:

class ListDecorator { private $iterator; public function __construct(RecursiveListIterator $iterator) { $this->iterator = $iterator; } public function inset($add = 0) { return str_repeat('' '', $this->iterator->getDepth()*2+$add); }

El constructor toma el iterador de lista en el que está trabajando. inset es solo una función auxiliar para una buena sangría de la salida. El resto son solo las funciones de salida para cada evento:

public function beginElement() { printf("%s<li>/n", $this->inset()); } public function endElement() { printf("%s</li>/n", $this->inset()); } public function beginChildren() { printf("%s<ul>/n", $this->inset(-1)); } public function endChildren() { printf("%s</ul>/n", $this->inset(-1)); } public function beginIteration() { printf("%s<ul>/n", $this->inset()); } public function endIteration() { printf("%s</ul>/n", $this->inset()); } }

Con estas funciones de salida en mente, este es el proceso principal de reinicio / bucle nuevamente, lo paso paso a paso:

$root = new TreeNode($tree);

Crea el TreeNode raíz que se usará para iniciar la iteración sobre:

$it = new TreeNodesIterator(array($root));

Este TreeNodesIterator es un RecursiveIterator que habilita la iteración recursiva sobre el nodo $root . Se pasa como una matriz porque esa clase necesita algo para iterar y permite la reutilización con un conjunto de elementos TreeNode que también es una matriz de elementos TreeNode .

$rit = new RecursiveListIterator($it);

Este RecursiveListIterator es un RecursiveIteratorIterator que proporciona dichos eventos. Para usarlo, solo se debe proporcionar un ListDecorator (la clase anterior) y se le addDecorator asignar addDecorator :

$decor = new ListDecorator($rit); $rit->addDecorator($decor);

Entonces, todo está configurado para simplemente foreach y dar salida a cada nodo:

foreach($rit as $item) { $inset = $decor->inset(1); printf("%s%s/n", $inset, $item->getName()); }

Como muestra este ejemplo, toda la lógica de salida está encapsulada en la clase ListDecorator y en este único foreach . Todo el recorrido recursivo se ha encapsulado por completo en iteradores recursivos SPL que proporcionan un procedimiento apilado, lo que significa que internamente no se realizan llamadas a la función de recursión.

El evento basado en ListDecorator permite modificar el resultado específicamente y proporcionar múltiples tipos de listas para la misma estructura de datos. Incluso es posible cambiar la entrada ya que los datos de la matriz se han encapsulado en TreeNode .

El ejemplo de código completo:

<?php namespace My; $tree = array(''H'' => ''G'', ''F'' => ''G'', ''G'' => ''D'', ''E'' => ''D'', ''A'' => ''E'', ''B'' => ''C'', ''C'' => ''E'', ''D'' => null); // add children to parents $flat = array(); # temporary array foreach ($tree as $name => $parent) { $flat[$name][''name''] = $name; # self if (NULL === $parent) { # no parent, is root element, assign it to $tree $tree = &$flat[$name]; } else { # has parent, add self as child $flat[$parent][''children''][] = &$flat[$name]; } } unset($flat); class TreeNode { protected $data; public function __construct(array $element) { if (!isset($element[''name''])) throw new InvalidArgumentException(''Element has no name.''); if (isset($element[''children'']) && !is_array($element[''children''])) throw new InvalidArgumentException(''Element has invalid children.''); $this->data = $element; } public function getName() { return $this->data[''name'']; } public function hasChildren() { return isset($this->data[''children'']) && count($this->data[''children'']); } /** * @return array of child TreeNode elements */ public function getChildren() { $children = $this->hasChildren() ? $this->data[''children''] : array(); $class = get_called_class(); foreach($children as &$element) { $element = new $class($element); } unset($element); return $children; } } class TreeNodesIterator implements /RecursiveIterator { private $nodes; public function __construct(array $nodes) { $this->nodes = new /ArrayIterator($nodes); } public function getInnerIterator() { return $this->nodes; } public function getChildren() { return new TreeNodesIterator($this->nodes->current()->getChildren()); } public function hasChildren() { return $this->nodes->current()->hasChildren(); } public function rewind() { $this->nodes->rewind(); } public function valid() { return $this->nodes->valid(); } public function current() { return $this->nodes->current(); } public function key() { return $this->nodes->key(); } public function next() { return $this->nodes->next(); } } class RecursiveListIterator extends /RecursiveIteratorIterator { private $elements; /** * @var ListDecorator */ private $decorator; public function addDecorator(ListDecorator $decorator) { $this->decorator = $decorator; } public function __construct($iterator, $mode = /RecursiveIteratorIterator::SELF_FIRST, $flags = 0) { parent::__construct($iterator, $mode, $flags); } private function event($name) { // event debug code: printf("--- %''.-20s --- (Depth: %d, Element: %d)/n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]); $callback = array($this->decorator, $name); is_callable($callback) && call_user_func($callback); } public function beginElement() { $this->event(''beginElement''); } public function beginChildren() { $this->event(''beginChildren''); } public function endChildren() { $this->testEndElement(); $this->event(''endChildren''); } private function testEndElement($depthOffset = 0) { $depth = $this->getDepth() + $depthOffset; isset($this->elements[$depth]) || $this->elements[$depth] = 0; $this->elements[$depth] && $this->event(''endElement''); } public function nextElement() { $this->testEndElement(); $this->event(''{nextElement}''); $this->event(''beginElement''); $this->elements[$this->getDepth()] = 1; } public function beginIteration() { $this->event(''beginIteration''); } public function endIteration() { $this->testEndElement(); $this->event(''endIteration''); } } class ListDecorator { private $iterator; public function __construct(RecursiveListIterator $iterator) { $this->iterator = $iterator; } public function inset($add = 0) { return str_repeat('' '', $this->iterator->getDepth()*2+$add); } public function beginElement() { printf("%s<li>/n", $this->inset(1)); } public function endElement() { printf("%s</li>/n", $this->inset(1)); } public function beginChildren() { printf("%s<ul>/n", $this->inset()); } public function endChildren() { printf("%s</ul>/n", $this->inset()); } public function beginIteration() { printf("%s<ul>/n", $this->inset()); } public function endIteration() { printf("%s</ul>/n", $this->inset()); } } $root = new TreeNode($tree); $it = new TreeNodesIterator(array($root)); $rit = new RecursiveListIterator($it); $decor = new ListDecorator($rit); $rit->addDecorator($decor); foreach($rit as $item) { $inset = $decor->inset(2); printf("%s%s/n", $inset, $item->getName()); }

Outpupt:

<ul> <li> D <ul> <li> G <ul> <li> H </li> <li> F </li> </ul> </li> <li> E <ul> </li> <li> A </li> <li> C <ul> <li> B </li> </ul> </li> </ul> </li> </ul> </li> </ul>

Demo (variante PHP 5.2)

Una posible variante sería un iterador que itera sobre cualquier RecursiveIterator y proporciona una iteración sobre todos los eventos que pueden ocurrir. Un interruptor / caja dentro del bucle foreach podría entonces tratar con los eventos.

Relacionado:


Otra función más para crear un árbol (sin recurrencia, utiliza referencias en su lugar):

$array = array(''H'' => ''G'', ''F'' => ''G'', ..., ''D'' => null); function to_tree($array) { $flat = array(); $tree = array(); foreach ($array as $child => $parent) { if (!isset($flat[$child])) { $flat[$child] = array(); } if (!empty($parent)) { $flat[$parent][$child] =& $flat[$child]; } else { $tree[$child] =& $flat[$child]; } } return $tree; }

Devuelve una matriz jerárquica como esta:

Array( [D] => Array( [G] => Array( [H] => Array() [F] => Array() ) ... ) )

Que se puede imprimir fácilmente como una lista HTML usando la función recursiva.


Si bien la solución de puede no parecer fácil de leer al principio, es a la vez genio y exponencialmente mejor en términos de rendimiento, esto debería haber sido votado como la mejor respuesta.

Gracias amigo, hice un punto de referencia en su honor para comparar las 2 soluciones presentadas en esta publicación.

Tenía un árbol plano de @ 250k con 6 niveles que tenía que convertir y estaba buscando una forma mejor de hacerlo y evitar iteraciones recursivas.

Recursión vs referencia:

// Generate a 6 level flat tree $root = null; $lvl1 = 13; $lvl2 = 11; $lvl3 = 7; $lvl4 = 5; $lvl5 = 3; $lvl6 = 1; $flatTree = []; for ($i = 1; $i <= 450000; $i++) { if ($i % 3 == 0) { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; } if ($i % 5 == 0) { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; } if ($i % 7 == 0) { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; } if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; } if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; } $lvl6 = $i; } echo ''Array count: '', count($flatTree), PHP_EOL; // Reference function function treeByReference($flatTree) { $flat = []; $tree = []; foreach ($flatTree as $child => $parent) { if (!isset($flat[$child])) { $flat[$child] = []; } if (!empty($parent)) { $flat[$parent][$child] =& $flat[$child]; } else { $tree[$child] =& $flat[$child]; } } return $tree; } // Recursion function function treeByRecursion($flatTree, $root = null) { $return = []; foreach($flatTree as $child => $parent) { if ($parent == $root) { unset($flatTree[$child]); $return[$child] = treeByRecursion($flatTree, $child); } } return $return ?: []; } // Benchmark reference $t1 = microtime(true); $tree = treeByReference($flatTree); echo ''Reference: '', (microtime(true) - $t1), PHP_EOL; // Benchmark recursion $t2 = microtime(true); $tree = treeByRecursion($flatTree); echo ''Recursion: '', (microtime(true) - $t2), PHP_EOL;

La salida habla por sí misma:

Array count: 255493 Reference: 0.3259289264679 (less than 0.4s) Recursion: 6604.9865279198 (almost 2h)


Cómo crear una vista dinámica de árbol y un menú

Paso 1: Primero crearemos la tabla treeview en la base de datos mysql. esta tabla contiene cuatro column.id es la identificación de la tarea y el nombre es el nombre de la tarea.

- -- Table structure for table `treeview_items` -- CREATE TABLE IF NOT EXISTS `treeview_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(200) NOT NULL, `title` varchar(200) NOT NULL, `parent_id` varchar(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ; -- -- Dumping data for table `treeview_items` -- INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES (1, ''task1'', ''task1title'', ''2''), (2, ''task2'', ''task2title'', ''0''), (3, ''task3'', ''task1title3'', ''0''), (4, ''task4'', ''task2title4'', ''3''), (5, ''task4'', ''task1title4'', ''3''), (6, ''task5'', ''task2title5'', ''5'');

Paso 2: método recursivo de vista de árbol que he creado debajo del método tree createTreeView () que llama recursivo si el ID de tarea actual es mayor que el id de tarea previa.

function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) { foreach ($array as $categoryId => $category) { if ($currentParent == $category[''parent_id'']) { if ($currLevel > $prevLevel) echo " <ol class=''tree''> "; if ($currLevel == $prevLevel) echo " </li> "; echo ''<li> <label for="subfolder2">''.$category[''name''].''</label> <input type="checkbox" name="subfolder2"/>''; if ($currLevel > $prevLevel) { $prevLevel = $currLevel; } $currLevel++; createTreeView ($array, $categoryId, $currLevel, $prevLevel); $currLevel--; } } if ($currLevel == $prevLevel) echo " </li> </ol> "; }

Paso 3: crea un archivo de índice para mostrar la vista de árbol. Este es el archivo principal del ejemplo de vista de árbol aquí llamaremos método createTreeView () con los parámetros requeridos.

<body> <link rel="stylesheet" type="text/css" href="_styles.css" media="screen"> <?php mysql_connect(''localhost'', ''root''); mysql_select_db(''test''); $qry="SELECT * FROM treeview_items"; $result=mysql_query($qry); $arrayCategories = array(); while($row = mysql_fetch_assoc($result)){ $arrayCategories[$row[''id'']] = array("parent_id" => $row[''parent_id''], "name" => $row[''name'']); } ?> <div id="content" class="general-style1"> <?php if(mysql_num_rows($result)!=0) { ?> <?php createTreeView($arrayCategories, 0); ?> <?php } ?> </div> </body>

Paso 4: Crear un archivo CSS style.css Aquí escribiremos todas las clases relacionadas con CSS, actualmente estoy usando la lista de órdenes para crear una vista en árbol. también puedes cambiar la ruta de la imagen aquí.

img { border: none; } input, select, textarea, th, td { font-size: 1em; } /* CSS Tree menu styles */ ol.tree { padding: 0 0 0 30px; width: 300px; } li { position: relative; margin-left: -15px; list-style: none; } li.file { margin-left: -1px !important; } li.file a { background: url(document.png) 0 0 no-repeat; color: #fff; padding-left: 21px; text-decoration: none; display: block; } li.file a[href *= ''.pdf''] { background: url(document.png) 0 0 no-repeat; } li.file a[href *= ''.html''] { background: url(document.png) 0 0 no-repeat; } li.file a[href $= ''.css''] { background: url(document.png) 0 0 no-repeat; } li.file a[href $= ''.js''] { background: url(document.png) 0 0 no-repeat; } li input { position: absolute; left: 0; margin-left: 0; opacity: 0; z-index: 2; cursor: pointer; height: 1em; width: 1em; top: 0; } li input + ol { background: url(toggle-small-expand.png) 40px 0 no-repeat; margin: -0.938em 0 0 -44px; /* 15px */ height: 1em; } li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; } li label { background: url(folder-horizontal.png) 15px 1px no-repeat; cursor: pointer; display: block; padding-left: 37px; } li input:checked + ol { background: url(toggle-small.png) 40px 5px no-repeat; margin: -1.25em 0 0 -44px; /* 20px */ padding: 1.563em 0 0 80px; height: auto; } li input:checked + ol > li { display: block; margin: 0 0 0.125em; /* 2px */} li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }

Más detalles