interface IFilter{
  // method that selects objects with some property
  boolean select();
}

interface IFilter2{
  // method that selects objects with some property
  boolean select2(Object obj);
}


class CheapBook implements IFilter2{
  // determine whether the given book is cheap
  boolean select2(Object obj){
    return ((Book)obj).price < 10;
  }
}


class NewAuthor implements IFilter2{
  // determine whether the given book was written by contemporary author
  boolean select2(Object obj){
    return ( ((Book)obj).author.isContemporary() );
  }
}


class Book implements IFilter{
  String title;
  Author author;
  int price;

  Book(String title, Author author, int price) {
    this.title = title;
    this.author = author;
    this.price = price;
  }

  boolean equals(Object obj){
    return    (this.title.equals( ((Book) obj).title))
           && (this.author.equals( ((Book) obj).author)) 
           && (this.price == ((Book) obj).price);
  }

  boolean select(){
    return this.price < 10;
  }
}


class Author implements IFilter{
  String name;
  int year;

  Author(String name, int year){
    this.name = name;
    this.year = year;
  }

  boolean equals(Object obj) {
    return    (this.name.equals(  ((Author) obj).name ))
           && (this.year == (  ((Author) obj).year ));
  }

  boolean select(){
    return this.year > 1940;
  }

  // determine whetheri this is a contemporary author
  boolean isContemporary(){
    return this.year > 1940;
  }
}


abstract class ALoObj{

  // count the number of items in this list
  abstract int count();

  // determine whether this list contains the given object
  abstract boolean contains(Object obj);

  // remove the first occurrence of the given object from this list
  abstract ALoObj remove(Object obj);

  // determine whether there is a 'selected' object in this list
  abstract boolean orMap();

  // determine whether there is a 'selected' object in this list
  abstract boolean orMap2(IFilter2 filter);
}


class MTLoObj extends ALoObj{

  MTLoObj(){ }

  int count(){ return 0; }

  boolean contains(Object obj){ return false; }

  ALoObj remove(Object obj){ return this; }

  boolean orMap(){ return false; }

  boolean orMap2(IFilter2 filter){ return false; }

}


class ConsLoObj extends ALoObj{
  Object fst;
  ALoObj rst;

  ConsLoObj(Object fst, ALoObj rst){
    this.fst = fst;
    this.rst = rst;
  }

// Template:
// ...this.fst...
// ...this.rst...
// ...this.count()...
// ...this.contains(Object obj)...
// ...this.remove(Object obj)...
// ...this.rst.orMap()...


  int count() { 
    return 1 + this.rst.count();
  }

  boolean contains(Object obj){
    return    (this.fst.equals(obj))
           || (this.rst.contains(obj));
  }

  ALoObj remove(Object obj){
    if (this.fst.equals(obj))
      return this.rst;
    else
      return new ConsLoObj(this.fst, this.rst.remove(obj));
  }

  boolean orMap(){
    return    ( ((IFilter)(this.fst)).select() )
           || (this.rst.orMap());
  }

  boolean orMap2(IFilter2 filter){
    return    filter.select2(this.fst)
           || (this.rst.orMap2(filter));
  }
}

// "Examples"
// 
// Author mf = new Author("Matthias", 1958);
// 
// Author bard = new Author("Shakespeare", 1716);
// 
// Book b1 = new Book ("HtDP", mf, 60);
// 
// Book b2 = new Book ("LL", mf, 18);
// 
// Book b3 = new Book ("Hamlet", bard, 7);
// 
// ALoObj mtlist = new MTLoObj(); 
// 
// ALoObj list1 = new ConsLoObj(b1, new ConsLoObj(b2, mtlist));
// 
// ALoObj list2 = new ConsLoObj(b3, list1);
// 
// 
// "Testing the count() method in class ListofObj"
// 
// mtlist.count() == 0
// 
// list1.count() == 2
// 
// list2.count() == 3
// 
// "Testing the contains() method in class ListofObj"
// 
// mtlist.contains(b1) == false
// 
// list1.contains(b3) == false
// 
// list2.contains(b3) == true
// 
// list2.contains(b2) == true
// 
// "Testing the remove() method in class ListofObj"
// 
// mtlist.remove(b1).equals(mtlist)
// 
// list1.remove(b3).count()
// 
// list2.remove(b3).count()
// 
// "Testing select() method in class Book"
// 
// b1.select() == false
// 
// b2.select() == true
// 
// "Testing select() method in class Author"
// 
// mf.select() == true
// 
// bard.select() == false
// 
// "Testing orMap() method on lists of books"
// 
// mtlist.orMap() == false
// 
// list1.orMap() == false
// 
// list2.orMap() == true
// 
// CheapBook cheapFilter = new CheapBook();
// 
// mtlist.orMap2(cheapFilter) == false
// 
// list1.orMap2(cheapFilter) == false
// 
// list2.orMap2(cheapFilter) == true
// 
// NewAuthor authorFilter = new NewAuthor();
// 
// mtlist.orMap2(authorFilter) == false
// 
// list1.orMap2(authorFilter) == true
// 
// list2.orMap2(authorFilter) == true
// 



// Examples
// 
// Author mf = new Author("Matthias" 1958);
// Author bard = new Author("Shakespeare", 1716);
// 
// Book b1 = new Book ("HtDP", mf, 2001);
// Book b2 = new Book ("LL", mf, 1974);
// Book b3 = new Book ("Hamlet", bard, 1736);
// 
// ALoObj mtlist = new MTLoObj(); 
// ALoObj list1 = new ConsLoObj(b1, new ConsLoObj(b2, mtlist));
// ALoObj list2 = new ConsLoObj(b3, mtlist);
// 
// "Testing the count() method in class ListofObj"
// mtlist.count() == 0;
// list1.count() == 2;
// list2.count() == 3;
// 
// "Testing the contains() method in class ListofObj"
// mtlist.contains(b1) == false;
// list1.contains(b3) == false;
// list2.contains(b3) == true
// list2.contains(b2) == true;
// 
// "Testing the remove() method in class ListofObj"
// mtlist.remove(b1).equals(mtlist);
// list1.remove(bard).equals(list1);
// list2.remove(b3).equals(list1);
// 
// NewAuthor authorFilter = new NewAuthor();
// mtlist.orMap2(authorFilter) == false
// list1.orMap2(authorFilter) == true
// list2.orMap2(authorFilter) == true
// 

