Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
CRAP
47.22% covered (danger)
47.22%
17 / 36
LinkedList
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
14.20
47.22% covered (danger)
47.22%
17 / 36
 assertLLIsValid
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 ll_insert_before
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
13 / 13
 ll_remove
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 10
 ll_replace
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
<?php
declare( strict_types = 1 );
// phpcs:disable Generic.NamingConventions.CamelCapsFunctionName.ScopeNotCamelCaps
namespace Wikimedia\Dodo;
/******************************************************************************
 * linked_list.php
 * ---------------
 *
 * Methods to operate on nodes of a circular linked list, where the
 * nodes are linked by references called _previousSibling and _nextSibling.
 *
 * That means our Node object is a node in a linked list! Yes, in reality
 * this is just rather tightly coupled to Node.
 */
class LinkedList {
    /**
     * Determine if the object we want to treat as a (circular) linked list
     * has the necessary data elements and that the elements aren't null.
     *
     * @param Node $a "circular ll node"
     */
    private static function assertLLIsValid( $a ) {
        Util::assert( $a !== null, "list is falsy" );
        Util::assert( $a->_previousSibling !== null, "previous is falsy" );
        Util::assert( $a->_nextSibling !== null, "next is falsy" );
        /* TODO: Check that list is actually circular? */
    }
    /**
     * Insert $a before $b
     *
     * @param Node $a "circular ll node" (THING TO BE INSERTED BEFORE $b)
     * @param Node $b "circular ll node" (THING BEFORE WHICH WE INSERT $a)
     * @return void
     *
     * NOTE
     * Given what this is actually doing (if you draw it out), this could
     * probably be renamed to 'link', where we are linking $a to $b.
     */
    public static function ll_insert_before( $a, $b ) {
        self::assertLLIsValid( $a );
        self::assertLLIsValid( $b );
        $a_first = $a;
        $a_last  = $a->_previousSibling;
        $b_first = $b;
        $b_last  = $b->_previousSibling;
        $a_first->_previousSibling = $b_last;
        $a_last->_nextSibling      = $b_first;
        $b_last->_nextSibling      = $a_first;
        $b_first->_previousSibling = $a_last;
        self::assertLLIsValid( $a );
        self::assertLLIsValid( $b );
    }
    /**
     * Remove a single node $a from its list
     *
     * @param Node $a "circular ll node" to be removed
     * @return void
     *
     * NOTE
     * Again, given what this is doing, could probably re-name
     * to 'unlink'.
     */
    public static function ll_remove( $a ) {
        self::assertLLIsValid( $a );
        $prev = $a->_previousSibling;
        if ( $prev === $a ) {
            return;
        }
        $next = $a->_nextSibling;
        $prev->_nextSibling = $next;
        $next->_previousSibling = $prev;
        $a->_previousSibling = $a->_nextSibling = $a;
        self::assertLLIsValid( $a );
    }
    /**
     * Replace a single node $a with a list $b (which could be null)
     *
     * @param Node $a "circular ll node"
     * @param ?Node $b "circular ll node" (or null)
     * @return void
     *
     * NOTE
     * I don't like this method. It's confusing.
     */
    public static function ll_replace( $a, $b ) {
        self::assertLLIsValid( $a );
        if ( $b !== null ) {
            self::assertLLIsValid( $b );
            self::ll_insert_before( $b, $a );
        }
        self::ll_remove( $a );
        self::assertLLIsValid( $a );
        if ( $b !== null ) {
            self::assertLLIsValid( $b );
        }
    }
}