001 package ps3.graph; 002 003 import java.util.*; 004 005 /** 006 * A NodeCountingPath characterizes a path of WeightedNodes. The cost 007 * for a path is the number of WeightedNodes it contains; the cost of the 008 * WeightedNodes are ignored.<p> 009 * 010 * A NodeCountingPath is immutable. A new NodeCountingPath is returned through 011 * the extend path operation. <p> 012 * 013 * <p>The main purpose of this class is to illustrate that there can be multiple 014 * implementations of Paths of WeightedNodes.</p> 015 * 016 * Specfields inherited from Path: 017 * 018 * @specfield cost : double // cost of traversing this path. 019 * @specfield elements : sequence // sequence of nodes in this path. 020 * 021 **/ 022 023 public class NodeCountingPath implements Path<WeightedNode, NodeCountingPath> { 024 025 // The representation invariant describes what invariants hold for 026 // the concrete representation. 027 // 028 // RepInvariant: 029 // RI(c) = 030 // (c.node != null) && 031 // (c.path == null) ==> (c.cost == 1) && 032 // (c.path != null) ==> (c.cost == 1 + c.path.cost) 033 // 034 035 // 036 // Abstraction Function: 037 // 038 // The abstract state is given in terms of the specfields of the 039 // Path interface, namely the cost and elements of a path. 040 // 041 // The AF uses two helper functions, which map a concrete state 042 // 'c' to the abstract state. 043 // 044 // AF(c) = < ncpcost(c), ncpelms(c) > 045 // (Maps c to ncp cost and ncp elements abstract fields recursively) 046 // 047 // ncpcost(c) = c.cost 048 // ncpelms(c) = / [c.node] if (c.path == null) 049 // \ ncpelms(c.path) + [c.node] if (c.path != null) 050 // 051 // (Note that [c.node] appears at the *end* not the *start* of the 052 // path sequence.) 053 // 054 // To make the AF(c) clearer, we could also write the following: 055 // AF(c) = < cost, elements > where 056 // cost = c.cost 057 // elements = [c.node] if c.path == null 058 // [c.node] + c.path.elements if c.path != null 059 // 060 061 /** The WeightedNode at the end of the path. */ 062 private final WeightedNode node; 063 /** A NodeCountingPath which, when extended with 'node' at the end, 064 * is equal to this. May be null iff this path has only 1 element. */ 065 private final /*@Nullable*/ NodeCountingPath path; 066 /** The cost of this NodeCountingPath (that is, its length). */ 067 private final int cost; 068 069 070 /** 071 * Constructs a NodeCountingPath containing one node. 072 * 073 * @requires node != null 074 * @effects Creates a new NodeCountingPath which originates at 075 * <code>node</code>. 076 **/ 077 public NodeCountingPath(WeightedNode node) { 078 this(node, null); 079 } 080 081 /** 082 * @requires node != null 083 * @effects Creates a new NodeCountingPath 'res' such that 084 * res.elements = path.elements + [ node ] 085 **/ 086 private NodeCountingPath(WeightedNode node, 087 /*@Nullable*/ NodeCountingPath path) { 088 if (node == null) { 089 throw new IllegalArgumentException(); 090 } 091 this.node = node; 092 this.path = path; 093 if (path != null) { 094 this.cost = 1 + path.cost; 095 } else { 096 this.cost = 1; 097 } 098 } 099 100 101 102 // Specified by Path interface. 103 public NodeCountingPath extend(WeightedNode node) { 104 return new NodeCountingPath(node, this); 105 } 106 107 // Specified by Path interface. 108 public double cost() { 109 return cost; 110 } 111 112 // Specified by Path interface (which extends Iterable) 113 public Iterator<WeightedNode> iterator() { 114 // reverse the linked list, so that elements are returned in order 115 // from start to end of the path. 116 List<WeightedNode> accumulator = new LinkedList<WeightedNode>(); 117 for (NodeCountingPath cur = this; cur!=null; cur = cur.path) { 118 accumulator.add(0, cur.end()); 119 } 120 return accumulator.iterator(); 121 } 122 123 /** 124 * @return a string representation of this. 125 **/ 126 public String toString() { 127 StringBuilder sb = new StringBuilder(); 128 sb.append("[NodeCountingPath: "); 129 boolean first=true; 130 for (WeightedNode wn : this) { 131 if (first) first = false; 132 else sb.append(", "); 133 sb.append(wn); 134 } 135 sb.append("]"); 136 return sb.toString(); 137 } 138 139 /** 140 * @return true iff o is a NodeCountingPath and o.elements is the 141 * same sequence as this.elements 142 **/ 143 @Override 144 public boolean equals(/*@Nullable*/ Object o) { 145 if (!(o instanceof NodeCountingPath)) 146 return false; 147 return this.equals((NodeCountingPath) o); 148 } 149 150 /** 151 * @return true iff wnp.elements is the same sequence as this.elements 152 **/ 153 public boolean equals(NodeCountingPath wnp) { 154 return (wnp != null) && 155 this.node.equals(wnp.node) && 156 (this.path == null ? wnp.path==null : this.path.equals(wnp.path)); 157 } 158 159 /** 160 * @return a valid hashcode for this. 161 **/ 162 public int hashCode() { 163 return node.hashCode() + (this.path==null ? 0 : 13 * path.hashCode()); 164 } 165 166 167 /** 168 * Compares the cost of this path to the given path. 169 * @return the value 0 if the cost of this path is equal to the 170 * cost of the given path; a value less than 0 if this.cost is 171 * less than p.cost; and a value greater than 0 if this.cost 172 * is greater than p.cost. 173 * @see java.lang.Comparable#compareTo 174 */ 175 public int compareTo(Path<?,?> p) { 176 return Double.compare(this.cost(), p.cost()); 177 } 178 179 /** 180 * Return the end of this path 181 */ 182 public WeightedNode end() { 183 return node; 184 } 185 }