简体   繁体   中英

Lisp Linked List Emulation Java

For my assignment we are to make an emulation of a LISP Linked List in Java. There are two types of List, an EmptyList and a NonEmptyList. The EmptyList is mostly trivial and only serves the purpose of ending the Linked List. The way it is supposed to work is that each List has a head and a tail. The head is an Object and the Tail is the next Linked List. I have a Linked List interface as follows:

public interface LispList {
    EmptyList NIL = new EmptyList();

    boolean empty();
    Object head();
    LispList tail();
    LispList cons(Object inputHead);
}

And here is the NonEmptyList class:

public class NonEmptyList implements LispList{
    Object head;
    LispList tail;

    public NonEmptyList(Object inputHead) {
        this.head = inputHead;
        this.tail = new NonEmptyList(head);
    }

    public boolean empty() {
        return false;
    }

    public Object head() {
        return head;
    }

    public LispList tail() {
        return tail;
    }

    public String toString() {
        return head() + " " + tail().toString();
    }

    public NonEmptyList cons(Object inputHead) {
        NonEmptyList a = new NonEmptyList(inputHead);
        return a;
    }

    public class NIL{
        EmptyList NIL;
    }
}

EmptyList:

public class EmptyList implements LispList {

    public EmptyList() {

    }

    public boolean empty() {
        return true;
    }

    public Object head() {
        throw new UnsupportedOperationException("EmptyList");
    }

    public LispList tail() {
        throw new UnsupportedOperationException("EmptyList");
    }

    public String toString() {
        return "";
    }

    public class NIL{
        EmptyList NIL;
    }

    public NonEmptyList cons(Object inputHead) {
        NonEmptyList a = new NonEmptyList(inputHead);
        return a;
    }
}

And here is my tester:

public class LispListTest {

    public static void main(String[] args) {
        LispList list = LispList.NIL.cons("C").cons("B").cons("A");

        System.out.println(list.tail());

        System.out.println(list.toString());
    }
}

The problem I am having is in the constructor of the NonEmptyList. The way I have it currently gives me a Stack Overflow Exception. I have tried a few different things and none of them work the way I need them to. I'm not sure how to make the constructor so the tail points to the next list.

This is my first attempt at a linked list so I might be making a pretty simple mistake.

Short anwser

First, EmptyList class needs to be singleton. Not sure how to achieve it Java, but you shouldn't open the constructor to everybody to use.

Then, the constructor for NonEmptyList should take two arguments:

  1. The object for the head.
  2. The tail, the instance of LispList . Better if you overload the constructor with this argument defaulting to EmptyList (singleton) instance.

Currently, in the constructor NonEmptyList you recursively call it when assigning the tail : in a way you are constructing an infinite list with repeated element: (a) this is not what you want and (b) without laziness this will cause stack overflow.

Lastly, cons is a constructor for a non-empty list, thus there is no need for a method called cons .

A guide into Lisp lists

Most Lisp dialects construct the list on the top of a pair . There is some criticism about the way Lisps do it: this introduces concepts of proper and improper lists and it's hard to define a generic comparison on lists. Yet, it's an easy and efficient way to do it.

A pair is constructed using CONS function (I will be using Common Lisp, CL, for demonstration):

(cons 12 45) => (12 . 45)

Notice the dot in the printed form of the pair. Parts of the pair can be extracted using functions CAR and CDR :

(car (cons 12 45)) => 12
(cdr (cons 12 45)) => 45

Pairs can be combined with other pairs:

(cons (cons (cons 1 2) 3) (cons 4 5))
=> (((1 . 2) . 3) 4 . 5)

CL provides combination functions of CAR and CDR to extract sub-pairs, eg CDDAR is a shortcut for (CDR (CDR (CAR OBJ))) : it takes the first item of the pair (which must be a pair itself), then the second item of the result and the second item of that result.

Lisps also define a special object of an empty pair, or nothing (but in fact, this object is not a pair, it's like mathematical "empty set" which is not a set...). In CL there two synonyms for it: NIL or () .

A list is constructed using pairs ordered in certain way. A list is:

  • Either NIL , or empty
  • Or a (CONS OBJ TAIL) , where OBJ is any object and TAIL is a list.

To distinguish operations on pairs and their combinations from operations on lists, Common Lisp provides synonymous functions:

  • FIRST extracts the first item of the list (aka head), synonym for CAR .
  • REST returns the tail of the list, synonym for (CAR (CDR LIST)) or (CADR LIST) .

So, here are examples of lists:

  • NIL is an empty list
  • (CONS 1 NIL) (printed (1) ) is the list of one element; FIRST will return 1 and REST will return an empty list NIL .
  • (CONS 1 (CONS 2 NIL)) (printed (1 2) ) is the list of two elements; FIRST will return 1 and REST will return the tail list (2) .
  • Generally, the list of numbers from 1 to N will be (CONS 1 (CONS 2 (CONS 3 ... (CONS N NIL) ..))) .

Here is correct and tested answer:

LispList interface:

public interface LispList
{
LispList NIL = new EmptyList();
boolean isEmpty();
Object head();
LispList tail();
LispList cons(Object head);
}

EmptyList class

public class EmptyList implements LispList {
public String toString() {
    return "";
}

@Override
public boolean isEmpty() {
    return true;
}

@Override
public Object head() {
    throw new UnsupportedOperationException();
}

@Override
public LispList tail() {
    throw new UnsupportedOperationException();
}

@Override
public LispList cons(Object head) {
    return new NonEmptyList(head, new EmptyList());
}

}

NonEmptyList class

public class NonEmptyList implements LispList {
private LispList tail;
private Object head;

public NonEmptyList(Object head, LispList tail) {
    this.head = head;
    this.tail = tail;
}

public String toString() {
    return head() + " " + tail().toString();
}

@Override
public boolean isEmpty() {
    return false;
}

@Override
public Object head() {
    return head;
}

@Override
public LispList tail() {
    return tail;
}

@Override
public LispList cons(Object head) {
    return new NonEmptyList(head, new NonEmptyList(head(), tail));
}

}

And test main class:

public class LispListTester
{
public static void main(String[] args)
{
    LispList list1 = new EmptyList();
    System.out.println("[" + list1 + "]");
    System.out.println("Expected: []");

    LispList list2 = new NonEmptyList("A", new EmptyList());
    System.out.println(list2);
    System.out.println("Expected: A");

    LispList list3 = new NonEmptyList("A", new NonEmptyList("B",
            new NonEmptyList("C", new EmptyList())));
    System.out.println(list3);
    System.out.println("Expected: A B C");

    LispList list4 =    LispList.NIL.cons("E").cons("D").cons("C").cons("B").cons("A");
    System.out.println(list4);
    System.out.println("Expected: A B C D E");
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM