class LinkedListNode:
    """
    Node to be used in linked list

    === Attributes ===
    @param LinkedListNode nxt: successor to this LinkedListNode
    @param LinkedListNode prev: predecessor to this LinkedListNode
    @param object value: data this LinkedListNode represents
    """

    def __init__(self, value, nxt=None, prev=None):
        """
        Create LinkedListNode self with data value, successor nxt, and
        predecessor prev.

        @param LinkedListNode self: this LinkedListNode
        @param object value: data of this linked list node
        @param LinkedListNode|None nxt: successor to this LinkedListNode.
        @param LinkedListNode|None prev: predecessor to this LinkedListNode.
        @rtype: None
        """
        self.value = value
        self.nxt = nxt
        self.prev = prev
        if self.prev:
            self.prev.nxt = self
        if self.nxt:
            self.nxt.prev = self

    def __str__(self):
        """
        Return a user-friendly representation of this LinkedListNode.

        @param LinkedListNode self: this LinkedListNode
        @rtype: str

        >>> n = LinkedListNode(5, LinkedListNode(7))
        >>> print(n)
        5 -> 7 ->|
        """
        s = "{} ->".format(self.value)
        cur_node = self
        while cur_node is not None:
            if cur_node.nxt is None:
                s += "|"
            else:
                s += " {} ->".format(cur_node.nxt.value)
            cur_node = cur_node.nxt
        return s

    def print_from_back(self):
        """ Return a user-friendly representation of this LinkedListNode,
        following back links, if any.

        @param LinkedListNode self: this LinkedListNode
        @rtype: str

        >>> n = LinkedListNode(5, LinkedListNode(7))
        >>> n.nxt.print_from_back()
        '| <- 5 <- 7'
        """
        s = " <- {}".format(self.value)
        cur_node = self
        while cur_node is not None:
            if cur_node.prev is None:
                s = "|" + s
            else:
                s = " <- {}".format(cur_node.prev.value) + s
            cur_node = cur_node.prev
        return s


class LinkedList:
    """
    Collection of LinkedListNodes

    === Attributes ==
    @param: LinkedListNode front: first node of this LinkedList
    @param LinkedListNode back: last node of this LinkedList
    @param int size: number of nodes in this LinkedList
                        a non-negative integer
    """

    def __init__(self):
        """
        Create an empty linked list.

        @param LinkedList self: this LinkedList
        @rtype: None
        """
        self.front, self.back = None, None
        self.size = 0

    def __str__(self):
        """
        Return a human-friendly string representation of LinkedList self.

        @param LinkedList self: this LinkedList
        @rtype: str

        >>> lnk = LinkedList()
        >>> lnk.insert(5)
        >>> print(lnk)
        5 ->|
        """
        return str(self.front)

    def print_from_back(self):
        """
        Return another human-friendly string representation of LinkedList self,
        starting from the back.

        @param LinkedList self: this LinkedList
        @rtype: str

        >>> lnk = LinkedList()
        >>> lnk.insert(5)
        >>> lnk.insert(7)
        >>> lnk.print_from_back()
        '| <- 5 <- 7'
        """
        return self.back.print_from_back()

    def __contains__(self, value):
        """
        Return whether LinkedList self contains value.

        @param LinkedList self: this LinkedList.
        @param object value: value to search for in self
        @rtype: bool

        >>> lnk = LinkedList()
        >>> lnk.insert(0)
        >>> lnk.insert(1)
        >>> lnk.insert(2)
        >>> lnk.__contains__(1)
        True
        >>> 1 in lnk
        True
        >>> lnk.__contains__(3)
        False
        """
        cur_node = self.front
        while cur_node:
            if cur_node.value == value:
                return True
            cur_node = cur_node.nxt
        return False

    def insert(self, value):
        """
        Insert value in sorted order.

        @param LinkedList self: this LinkedList
        @param object value: value to insert
        @rtype: None

        >>> lnk = LinkedList()
        >>> lnk.insert(3)
        >>> lnk.insert(0)
        >>> lnk.insert(2)
        >>> lnk.insert(1)
        >>> str(lnk)
        '0 -> 1 -> 2 -> 3 ->|'
        >>> lnk.print_from_back()
        '| <- 0 <- 1 <- 2 <- 3'
        """
        newnode = LinkedListNode(value)

        if self.size == 0:
            self.front = self.back = newnode
        elif self.front.value > value:
            self.front.prev = newnode
            newnode.nxt = self.front
            self.front = newnode
        else:
            cur = self.front
            while cur.nxt is not None and cur.nxt.value < value:
                cur = cur.nxt

            if cur.nxt is not None:
                newnode.nxt = cur.nxt
                cur.nxt.prev = newnode
                cur.nxt = newnode
                newnode.prev = cur
            else:
                cur.nxt = newnode
                newnode.prev = cur
                self.back = newnode
                
        self.size += 1

if __name__ == '__main__':
    import doctest
    doctest.testmod()
