Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

revert to CS

  • Loading branch information...
commit 1eb0774abaaa2526a0ae97d4d4f0e9fabe1e92b5 1 parent e75b9db
@raganwald raganwald authored
Showing with 5 additions and 326 deletions.
  1. +5 −326 2013/02/turtles-and-iterators.md
View
331 2013/02/turtles-and-iterators.md
@@ -1,6 +1,8 @@
Tortoises, Teleporting Turtles, and Iterators
=============================================
+(The code examples are in CoffeeScript. [Click here](http:./turtles-and-iterators.md) for code examples in JavaScript.)
+
A good long while ago (The First Age of Internet Startups), someone asked me one of those pet algorithm questions. It was, "Write an algorithm to detect a loop in a linked list, in constant space."
I'm not particularly surprised that I couldn't think up an answer in a few minutes at the time. And to the interviewer's credit, he didn't terminate the interview on the spot, he asked me to describe the kinds of things going through my head.
@@ -42,54 +44,6 @@ tortoiseAndHareLoopDetector list
#=> true
```
-```javascript
-var LinkedList, list, tortoiseAndHareLoopDetector;
-
-LinkedList = (function() {
-
- function LinkedList(content, next) {
- this.content = content;
- this.next = next != null ? next : void 0;
- }
-
- LinkedList.prototype.appendTo = function(content) {
- return new LinkedList(content, this);
- };
-
- LinkedList.prototype.tailNode = function() {
- var nextThis;
- return ((nextThis = this.next) != null ? nextThis.tailNode() : void 0) || this;
- };
-
- return LinkedList;
-
-})();
-
-tortoiseAndHareLoopDetector = function(list) {
- var hare, tortoise, nextHare;
- tortoise = list;
- hare = list.next;
- while ((tortoise != null) && (hare != null)) {
- if (tortoise === hare) {
- return true;
- }
- tortoise = tortoise.next;
- hare = (nextHare = hare.next) != null ? nextHare.next : void 0;
- }
- return false;
-};
-
-list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
-
-tortoiseAndHareLoopDetector(list);
- //=> false
-
-list.tailNode().next = list.next;
-
-tortoiseAndHareLoopDetector(list);
- //=> true
-```
-
This algorithm is called "The Tortoise and the Hare," and was discovered by Robert Floyd in the 1960s. You have two node references, and one traverses the list at twice the speed of the other. No matter how large it is, you will eventually have the fast reference equal to the slow reference, and thus you'll detect the loop.
At the time, I couldn't think of any way to use hashing to solve the problem, so I gave up and tried to fit this into a powers-of-two algorithm. My first pass at it was clumsy, but it was roughly equivalent to this:
@@ -120,40 +74,6 @@ teleportingTurtleLoopDetector list
#=> true
```
-```javascript
-var list, teleportingTurtleLoopDetector;
-
-teleportingTurtleLoopDetector = function(list) {
- var i, rabbit, speed, turtle;
- speed = 1;
- turtle = rabbit = list;
- while (true) {
- for (i = 0; i <= speed; i += 1) {
- rabbit = rabbit.next;
- if (rabbit == null) {
- return false;
- }
- if (rabbit === turtle) {
- return true;
- }
- }
- turtle = rabbit;
- speed *= 2;
- }
- return false;
-};
-
-list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
-
-teleportingTurtleLoopDetector(list);
- //=> false
-
-list.tailNode().next = list.next;
-
-teleportingTurtleLoopDetector(list);
- //=> true
-```
-
Today, thanks to [Reddit](http://www.reddit.com/r/programming/comments/18io6e/detecting_a_loop_in_singly_linked_list_tortoise/), I came across a discussion of this algorithm, [The Tale of the Teleporting Turtle](http://www.penzba.co.uk/Writings/TheTeleportingTurtle.html). I'd like to congratulate myself for thinking of a fast algorithm, but the simple truth is that I got lucky. It's not like I thought of both algorithms and compared them on the basis of time complexity. Nor, for that matter, did I think of it in the interview.
Reading about these algorithms today reminded me of a separation of concerns issue: Untangling how you traverse a data structure from what you do with its elements.
@@ -169,18 +89,6 @@ sum = (array) ->
total
```
-```javascript
-function sum (array) {
- var number, total, _i, len;
- total = 0;
- for (i = 0, len = array.length; i < len; i++) {
- number = array[i];
- total += number;
- }
- return total;
-};
-```
-
What's the sum of a linked list of numbers? How about the sum of a tree of numbers (represented as an array of array of numbers)? Must we re-write the `sum` function for each data structure?
There are two roads ahead. One involves a generalized `reduce` or `fold` method for each data structure. The other involves writing an [Iterator](https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.7#Iterators) for each data structure and writing our `sum` to take an iterator as its argument. Let's use iterators, especially since we need two different iterators for the same data structure, so a single object method is inconvenient.
@@ -223,66 +131,6 @@ sum ArrayIterator [1..5]
#=> 15
```
-```javascript
-var LinkedList, list;
-
-LinkedList = (function() {
-
- function LinkedList(content, next) {
- this.content = content;
- this.next = next != null ? next : void 0;
- }
-
- LinkedList.prototype.appendTo = function(content) {
- return new LinkedList(content, this);
- };
-
- LinkedList.prototype.tailNode = function() {
- var nextThis;
- return ((nextThis = this.next) != null ? nextThis.tailNode() : void 0) || this;
- };
-
- return LinkedList;
-
-})();
-
-function ListIterator (list) {
- return function() {
- var node;
- node = list != null ? list.content : void 0;
- list = list != null ? list.next : void 0;
- return node;
- };
-};
-
-function sum (iter) {
- var number, total;
- total = 0;
- number = iter();
- while (number != null) {
- total += number;
- number = iter();
- }
- return total;
-};
-
-list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
-
-sum(ListIterator(list));
- //=> 15
-
-function ArrayIterator (array) {
- var index;
- index = 0;
- return function() {
- return array[index++];
- };
-};
-
-sum(ArrayIterator([1, 2, 3, 4, 5]));
- //=> 15
-```
-
Summing an array that can contain nested arrays adds a degree of complexity. Writing a function that iterates recursively over a data structure is an interesting problem, one that is trivial in a language with [coroutines](https://en.wikipedia.org/wiki/Coroutine). Since we don't have Generators yet, and we don't want to try to turn our loop detection inside-out, we'll Greenspun our own coroutine by maintaining our own stack.
> This business of managing your own stack may seem weird to anyone born after 1970, but old fogeys fondly remember that after walking barefoot to and from University uphill in a blizzard both ways, the interview question brain teaser of the day was to write a "Towers of Hanoi" solver in a language like BASIC that didn't have reentrant subroutines.
@@ -312,40 +160,6 @@ sum LeafIterator [1, [2, [3, 4]], [5]]
#=> 15
```
-```javascript
-function LeafIterator (array) {
- var index, myself, state;
- index = 0;
- state = [];
- myself = function() {
- var element, tempState;
- element = array[index++];
- if (element instanceof Array) {
- state.push({
- array: array,
- index: index
- });
- array = element;
- index = 0;
- return myself();
- } else if (element === void 0) {
- if (state.length > 0) {
- tempState = state.pop(), array = tempState.array, index = tempState.index;
- return myself();
- } else {
- return void 0;
- }
- } else {
- return element;
- }
- };
- return myself;
-};
-
-sum(LeafIterator([1, [2, [3, 4]], [5]]));
- //=> 15
-```
-
We've successfully separated the issue of what one does with data from how one traverses over the elements.
**folding**
@@ -364,29 +178,6 @@ fold = (iter, binaryFn, seed) ->
foldingSum = (iter) -> fold iter, ((x, y) -> x + y), 0
foldingSum LeafIterator [1, [2, [3, 4]], [5]]
- #=> 15
-```
-
-```javascript
-function fold (iter, binaryFn, seed) {
- var acc, element;
- acc = seed;
- element = iter();
- while (element != null) {
- acc = binaryFn.call(element, acc, element);
- element = iter();
- }
- return acc;
-};
-
-function foldingSum (iter) {
- return fold(iter, (function(x, y) {
- return x + y;
- }), 0);
-};
-
-foldingSum(LeafIterator([1, [2, [3, 4]], [5]]));
- #=> 15
```
Fold turns an iterator over a finite data structure into an accumulator. And once again, it works with any data structure. You don't need a different kind of fold for each kind of data structure you use.
@@ -400,22 +191,7 @@ NumberIterator = (base = 0) ->
number = base
->
number++
-```
-
-```javascript
-function NumberIterator (base) {
- var number;
- if (base == null) {
- base = 0;
- }
- number = base;
- return function() {
- return number++;
- };
-};
-```
-
-```
+
fromOne = NumberIterator(1)
fromOne()
@@ -440,23 +216,7 @@ FibonacciIterator = ->
value = current
[previous, current] = [current, current + previous]
value
-```
-
-```javascript
-function FibonacciIterator () {
- var current, previous;
- previous = 0;
- current = 1;
- return function() {
- var value, tempValues;
- value = current;
- tempValues = [current, current + previous], previous = tempValues[0], current = tempValues[1];
- return value;
- };
-};
-```
-
-```
+
fib = FibonacciIterator()
fib()
@@ -483,23 +243,7 @@ take = (iter, numberToTake) ->
iter()
else
undefined
-```
-```javascript
-take = function(iter, numberToTake) {
- var count;
- count = 0;
- return function() {
- if (++count <= numberToTake) {
- return iter();
- } else {
- return void 0;
- }
- };
-};
-```
-
-```
oneToFive = take NumberIterator(1), 5
oneToFive()
@@ -520,13 +264,6 @@ With `take`, we can do things like return the squares of the first five numbers:
```coffeescript
square take NumberIterator(1), 5
-```
-
-```javascript
-square(take(NumberIterator(1), 5))
-```
-
-```
#=> [ 1,
# 4,
# 9,
@@ -538,27 +275,13 @@ How about the squares of the odd numbers from the first five numbers?
```coffeescript
square odds take NumberIterator(1), 5
-```
-
-```javascript
-square(odds(take(NumberIterator(1), 5)))
-```
-
-```
#=> TypeError: object is not a function
```
Bzzzt! Our `odds` function returns an array, not an iterator.
```coffeescript
-square take odds(NumberIterator(1)), 5
-```
-
-```javascript
-square(take(odds(NumberIterator(1)), 5))
-```
-
-```
+square take odds(NumberIterator(1)), 5
#=> RangeError: Maximum call stack size exceeded
```
@@ -586,45 +309,6 @@ iteratorFilter = (iter, unaryPredicateFn) ->
oddsFilter = (iter) -> iteratorFilter iter, odd
```
-```javascript
-
-function iteratorMap (iter, unaryFn) {
- return function() {
- var element;
- element = iter();
- if (element != null) {
- return unaryFn.call(element, element);
- } else {
- return void 0;
- }
- };
-};
-
-function squaresIterator (iter) {
- return iteratorMap(iter, function(n) {
- return n * n;
- });
-};
-
-function iteratorFilter (iter, unaryPredicateFn) {
- return function() {
- var element;
- element = iter();
- while (element != null) {
- if (unaryPredicateFn.call(element, element)) {
- return element;
- }
- element = iter();
- }
- return void 0;
- };
-};
-
-function oddsFilter (iter) {
- return iteratorFilter(iter, odd);
-};
-```
-
Now we can do things like take the sum of the first five odd squares of fibonacci numbers:
```coffeescript
@@ -632,11 +316,6 @@ foldingSum take (oddsFilter squaresIterator FibonacciIterator()), 5
#=> 205
```
-```javascript
-foldingSum(take(oddsFilter(squaresIterator(FibonacciIterator())), 5))
- //=> 205
-```
-
This solution composes the parts we already have, rather than writing a tricky bit of code with ifs and whiles and boundary conditions.
**summary**
Please sign in to comment.
Something went wrong with that request. Please try again.