FOUR POSSIBLE SOLUTIONS TO THE FILE-SEARCH EXERCISE. The spec: // searches path, starting from this Folder, to find the Entry at the end of path. // Param: path - a string of form "Name1/Name2/.../Namem", e.g., "F/G/a.txt" // Returns: the Entry named by the path. If path is invalid, returns null // Example: say that root is the handle to the "Root" folder, which holds subfolder "F", // which itself holds a file, "a.txt". Then, root.search("F/a.txt") returns // the handle to the TextFile object named "a.txt". SOLUTION 1: First off, if we forget about shortcuts and assume that the path is always well-formed, e.g., Folder1/Folder2/.../Foldern/Destination, then the loop solution is simple: public Entry Search(string path) { // make an array of strings, e.g., "F/G/a.txt" becomes {"F", "G", "a.txt"}: string[] names = path.Split('/'); Entry currentEntry = this; // currentEntry remembers how far we've searched. // it is initialized to the handle of this Folder foreach (string name in names) { // assert: (i) currentEntry is the handle to a Folder !!! // (ii) the path, names[0]/.../names[m], have led us to currentEntry, // and we have names[m+1] to find next currentEntry = ((Folder)currentEntry).find(name); } // assert: loop is finished and currentEntry is the handle to the Destination return currentEntry; } SOLUTION 2: The loop algorithm grows a bit complex when we add Shortcuts and when we must handle bad paths like "F/a.txt/G/b.txt": public Entry search(string path) { // make an array of strings, e.g., "F/G/a.txt" becomes {"F", "G", "a.txt"}: string[] names = path.Split('/'); Entry currentEntry = this; // currentEntry remembers how far we've searched. // it is initialized to the handle of this Folder int count = 0; // how many names we have searched in the path so far foreach (string name in names) { // assert: (i) currentEntry is the handle to a Folder // (ii) the path, names[0]/.../names[count-1], have led us to currentEntry, // find the next name in the path of names: currentEntry = ((Folder)currentEntry).find(name); count = count + 1; // If the new currentEntry is a shortcut, then "jump it" to a Folder or TextFile: while (currentEntry != null && currentEntry is Shortcut) { currentEntry = ((Shortcut)currentEntry).Link; } if (currentEntry == null) { break; } // bad path if (currentEntry is TextFile) { break; } // end of the search // else currentEntry is again the handle to a Folder } // the foreach loop has ended: if (count == names.Length ) { // did we successfully search the entire path ? return currentEntry; } else { return null; } // an error } SOLUTION 3: The inner while-loop is a bit ugly, and we might merge it with the outer loop. The result is not necessarily easier to understand, alas. public Entry search(string path) { // make an array of strings, e.g., "F/G/a.txt" becomes {"F", "G", "a.txt"}: string[] names = path.Split('/'); Entry currentEntry = this; // currentEntry remembers how far we've searched // it is initialized to the handle of this Folder int count = 0; // how many names we have searched in the path so far while (true) { // assert: the path, names[0]/.../names[count-1], have led us to currentEntry. if (currentEntry == null) { break; } // a bad path else if (currentEntry is TextFile) { break; } // end of the search else if (currentEntry is Shortcut) { currentEntry = ((Shortcut)currentEntry).Link; // "jump" to link } else if (count == names.Length) { break; } // searched all the path -- finished else { // assert: the currentEntry != null, and there are more names to search if (currentEntry is Folder) { currentEntry = ((Folder)currentEntry).find(names[count]); count = count + 1; } else { currentEntry = null; } // this case should never happen } } // loop ended: if (count == names.Length ) { // did we successfully search the entire path ? return currentEntry; } else { return null; } // an error, because the search ended prematurely } SOLUTION 4: Here is a recursive solution that uses cases similar to the ones seen above. public Entry search(string path) { // make an array of strings, e.g., "F/G/a.txt" becomes {"F", "G", "a.txt"}: string[] names = path.Split('/'); // call a recursive helper function to examine the names one by one: return searchE(this, new List(names)); } // helper function that searches the names starting from Entry e: // Params: e - how far we have searched so far (the "currentEntry" seen in the loop code) // names - the list of names still left to search in the original path // Returns: the handle of the Entry at the end of the path of names. public Entry searchE(Entry e, List names) { if (e == null) { return null; } // a bad starting point if (e is Shortcut) { Entry next = ((Shortcut)e).Link; // "jump" to link and return searchE(next, names); // continue search from the jump } if (names.Count == 0) { return e; } // empty list of names --- finished the search. // assert: e is nonnull, not a Shortcut, and there are more names to look up if (e is Folder) { Entry next = ((Folder)e).find(names[0]); // look for the next name in e names.RemoveAt(0); // finished with the front name on the list return searchE(next, names); // finish the search with remaining names } if (e is TextFile) { // can't lookup names[0] in a TextFile -- a bad path return null; } else { return null; } // this option should never arise during execution. } It is also possible to reformat searchE in visitor-design-pattern-style, but this takes some effort. The basic idea goes somewhat like this: public Entry search(string path) { // make an array of strings, e.g., "F/G/a.txt" becomes {"F", "G", "a.txt"}: string[] names = path.Split('/'); // call a recursive helper function to examine the names one by one: return this.searchE(new List(names)); } and in class Folder we have: public Entry searchE(List names) { Entry next = ((Folder)e).find(names[0]); // look for the next name in e names.RemoveAt(0); // finished with the front name on the list return next.searchE(names); // finish the search with remaining names } and in class Shortcut we have: public Entry searchE(List names) { Entry next = ((Shortcut)e).Link; // "jump" to link and return next.searchE(names); // continue search from the jump } etc. But the code is not quite finished --- what's missing????