Skip to content
This repository
Browse code

commit JavaScript version

  • Loading branch information...
commit e75b9db7d34e96a69ab9733c617815718d35c178 1 parent 3da5a25
Reg Braithwaite authored February 16, 2013
481  2013/02/turtles-and-iterators.js.md
Source Rendered
... ...
@@ -0,0 +1,481 @@
  1
+Tortoises, Teleporting Turtles, and Iterators
  2
+=============================================
  3
+
  4
+(The ode examples are in JavaScript. [Click Here](http:./turtles-and-iterators.md) to see teh code exampels in CoffeeScript)
  5
+
  6
+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."
  7
+
  8
+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.
  9
+
  10
+I think I told him that I was trying to figure out if I could adapt a hashing algorithm such as XORing everything together. This is the "trick answer" to a question about finding a missing integer from a list, so I was trying the old, "Transform this into [a problem you've already solved](http://www-users.cs.york.ac.uk/susan/joke/3.htm#boil)" meta-algorithm. We moved on from there, and he didn't reveal the "solution."
  11
+
  12
+I went home and pondered the problem. I wanted to solve it. Eventually, I came up with something and tried it (In Java!) on my home PC. I sent him an email sharing my result, to demonstrate my ability to follow through. I then forgot about it for a while.
  13
+
  14
+![Turtles all the way down](http://i.minus.com/i04jwKF6lLEDt.jpg)
  15
+
  16
+Some time later, I was told that the correct solution was:
  17
+
  18
+```javascript
  19
+var LinkedList, list, tortoiseAndHareLoopDetector;
  20
+
  21
+LinkedList = (function() {
  22
+
  23
+  function LinkedList(content, next) {
  24
+    this.content = content;
  25
+    this.next = next != null ? next : void 0;
  26
+  }
  27
+
  28
+  LinkedList.prototype.appendTo = function(content) {
  29
+    return new LinkedList(content, this);
  30
+  };
  31
+
  32
+  LinkedList.prototype.tailNode = function() {
  33
+    var nextThis;
  34
+    return ((nextThis = this.next) != null ? nextThis.tailNode() : void 0) || this;
  35
+  };
  36
+
  37
+  return LinkedList;
  38
+
  39
+})();
  40
+
  41
+tortoiseAndHareLoopDetector = function(list) {
  42
+  var hare, tortoise, nextHare;
  43
+  tortoise = list;
  44
+  hare = list.next;
  45
+  while ((tortoise != null) && (hare != null)) {
  46
+    if (tortoise === hare) {
  47
+      return true;
  48
+    }
  49
+    tortoise = tortoise.next;
  50
+    hare = (nextHare = hare.next) != null ? nextHare.next : void 0;
  51
+  }
  52
+  return false;
  53
+};
  54
+
  55
+list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
  56
+
  57
+tortoiseAndHareLoopDetector(list);
  58
+  //=> false
  59
+
  60
+list.tailNode().next = list.next;
  61
+
  62
+tortoiseAndHareLoopDetector(list);
  63
+  //=> true
  64
+```
  65
+
  66
+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.
  67
+
  68
+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:
  69
+
  70
+```javascript
  71
+var list, teleportingTurtleLoopDetector;
  72
+
  73
+teleportingTurtleLoopDetector = function(list) {
  74
+  var i, rabbit, speed, turtle;
  75
+  speed = 1;
  76
+  turtle = rabbit = list;
  77
+  while (true) {
  78
+    for (i = 0; i <= speed; i += 1) {
  79
+      rabbit = rabbit.next;
  80
+      if (rabbit == null) {
  81
+        return false;
  82
+      }
  83
+      if (rabbit === turtle) {
  84
+        return true;
  85
+      }
  86
+    }
  87
+    turtle = rabbit;
  88
+    speed *= 2;
  89
+  }
  90
+  return false;
  91
+};
  92
+
  93
+list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
  94
+
  95
+teleportingTurtleLoopDetector(list);
  96
+  //=> false
  97
+
  98
+list.tailNode().next = list.next;
  99
+
  100
+teleportingTurtleLoopDetector(list);
  101
+  //=> true
  102
+```
  103
+
  104
+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.
  105
+
  106
+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.
  107
+
  108
+**a very simple problem**
  109
+
  110
+Let's consider a remarkably simple problem: Finding the sum of the elements of an array. In iterative style, it looks like this:
  111
+
  112
+```javascript
  113
+function sum (array) {
  114
+  var number, total, _i, len;
  115
+  total = 0;
  116
+  for (i = 0, len = array.length; i < len; i++) {
  117
+    number = array[i];
  118
+    total += number;
  119
+  }
  120
+  return total;
  121
+};
  122
+```
  123
+
  124
+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?
  125
+
  126
+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.
  127
+
  128
+Since we don't have iterators baked into the underlying JavaScript engine yet, we'll write our iterators as functions:
  129
+
  130
+```javascript
  131
+var LinkedList, list;
  132
+
  133
+LinkedList = (function() {
  134
+
  135
+  function LinkedList(content, next) {
  136
+    this.content = content;
  137
+    this.next = next != null ? next : void 0;
  138
+  }
  139
+
  140
+  LinkedList.prototype.appendTo = function(content) {
  141
+    return new LinkedList(content, this);
  142
+  };
  143
+
  144
+  LinkedList.prototype.tailNode = function() {
  145
+    var nextThis;
  146
+    return ((nextThis = this.next) != null ? nextThis.tailNode() : void 0) || this;
  147
+  };
  148
+
  149
+  return LinkedList;
  150
+
  151
+})();
  152
+
  153
+function ListIterator (list) {
  154
+  return function() {
  155
+    var node;
  156
+    node = list != null ? list.content : void 0;
  157
+    list = list != null ? list.next : void 0;
  158
+    return node;
  159
+  };
  160
+};
  161
+
  162
+function sum (iter) {
  163
+  var number, total;
  164
+  total = 0;
  165
+  number = iter();
  166
+  while (number != null) {
  167
+    total += number;
  168
+    number = iter();
  169
+  }
  170
+  return total;
  171
+};
  172
+
  173
+list = new LinkedList(5).appendTo(4).appendTo(3).appendTo(2).appendTo(1);
  174
+
  175
+sum(ListIterator(list));
  176
+  //=> 15
  177
+
  178
+function ArrayIterator (array) {
  179
+  var index;
  180
+  index = 0;
  181
+  return function() {
  182
+    return array[index++];
  183
+  };
  184
+};
  185
+
  186
+sum(ArrayIterator([1, 2, 3, 4, 5]));
  187
+  //=> 15
  188
+```
  189
+
  190
+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.
  191
+
  192
+> 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.
  193
+
  194
+```javascript
  195
+function LeafIterator (array) {
  196
+  var index, myself, state;
  197
+  index = 0;
  198
+  state = [];
  199
+  myself = function() {
  200
+    var element, tempState;
  201
+    element = array[index++];
  202
+    if (element instanceof Array) {
  203
+      state.push({
  204
+        array: array,
  205
+        index: index
  206
+      });
  207
+      array = element;
  208
+      index = 0;
  209
+      return myself();
  210
+    } else if (element === void 0) {
  211
+      if (state.length > 0) {
  212
+        tempState = state.pop(), array = tempState.array, index = tempState.index;
  213
+        return myself();
  214
+      } else {
  215
+        return void 0;
  216
+      }
  217
+    } else {
  218
+      return element;
  219
+    }
  220
+  };
  221
+  return myself;
  222
+};
  223
+
  224
+sum(LeafIterator([1, [2, [3, 4]], [5]]));
  225
+  //=> 15
  226
+```
  227
+
  228
+We've successfully separated the issue of what one does with data from how one traverses over the elements.
  229
+
  230
+**folding**
  231
+
  232
+Just as pure functional programmers love to talk monads, newcomers to functional programming in multi-paradigm languages often drool over [folding](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) a/k/a mapping/injecting/reducing. We're just a level of abstraction away:
  233
+
  234
+```javascript
  235
+function fold (iter, binaryFn, seed) {
  236
+  var acc, element;
  237
+  acc = seed;
  238
+  element = iter();
  239
+  while (element != null) {
  240
+    acc = binaryFn.call(element, acc, element);
  241
+    element = iter();
  242
+  }
  243
+  return acc;
  244
+};
  245
+
  246
+function foldingSum (iter) {
  247
+  return fold(iter, (function(x, y) {
  248
+    return x + y;
  249
+  }), 0);
  250
+};
  251
+
  252
+foldingSum(LeafIterator([1, [2, [3, 4]], [5]]));
  253
+  #=> 15
  254
+```
  255
+
  256
+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.
  257
+
  258
+**unfolding and laziness**
  259
+
  260
+Iterators are functions. When they iterate over an array or linked list, they are traversing something that is already there. But they could, in principle, manufacture the data as they go. Let's consider the simplest example:
  261
+
  262
+```javascript
  263
+function NumberIterator (base) {
  264
+  var number;
  265
+  if (base == null) {
  266
+    base = 0;
  267
+  }
  268
+  number = base;
  269
+  return function() {
  270
+    return number++;
  271
+  };
  272
+};
  273
+
  274
+fromOne = NumberIterator(1);
  275
+
  276
+fromOne();
  277
+  //=> 1
  278
+fromOne();
  279
+  //=> 2
  280
+fromOne();
  281
+  //=> 3
  282
+fromOne();
  283
+  //=> 4
  284
+fromOne();
  285
+  //=> 5
  286
+```
  287
+
  288
+And here's another one:
  289
+
  290
+```coffeescript
  291
+FibonacciIterator = ->
  292
+  previous = 0
  293
+  current = 1
  294
+  ->
  295
+    value = current
  296
+    [previous, current] = [current, current + previous]
  297
+    value
  298
+```
  299
+
  300
+```javascript
  301
+function FibonacciIterator () {
  302
+  var current, previous;
  303
+  previous = 0;
  304
+  current = 1;
  305
+  return function() {
  306
+    var value, tempValues;
  307
+    value = current;
  308
+    tempValues = [current, current + previous], previous = tempValues[0], current = tempValues[1];
  309
+    return value;
  310
+  };
  311
+};
  312
+```
  313
+
  314
+```
  315
+fib = FibonacciIterator()
  316
+
  317
+fib()
  318
+  #=> 1
  319
+fib()
  320
+  #=> 1
  321
+fib()
  322
+  #=> 2
  323
+fib()
  324
+  #=> 3
  325
+fib()
  326
+  #=> 5
  327
+```
  328
+
  329
+A function that starts with a seed and expands it into a data structure is called an *unfold*. It's the opposite of a fold. It's possible to write a generic unfold mechanism, but let's pass on to what we can do with unfolded iterators.
  330
+
  331
+This business of going on forever has some drawbacks. Let's introduce an idea: A function that takes an Iterator and returns another iterator. We can start with `take`, an easy function that returns an iterator that only returns a fixed number of elements:
  332
+
  333
+```javascript
  334
+take = function(iter, numberToTake) {
  335
+  var count;
  336
+  count = 0;
  337
+  return function() {
  338
+    if (++count <= numberToTake) {
  339
+      return iter();
  340
+    } else {
  341
+      return void 0;
  342
+    }
  343
+  };
  344
+};
  345
+
  346
+oneToFive = take(NumberIterator(1), 5);
  347
+
  348
+oneToFive();
  349
+  //=> 1
  350
+oneToFive();
  351
+  //=> 2
  352
+oneToFive();
  353
+  //=> 3
  354
+oneToFive();
  355
+  //=> 4
  356
+oneToFive();
  357
+  //=> 5
  358
+oneToFive();
  359
+  //=> undefined
  360
+```
  361
+
  362
+With `take`, we can do things like return the squares of the first five numbers:
  363
+
  364
+```javascript
  365
+square(take(NumberIterator(1), 5))
  366
+
  367
+  //=> [ 1,
  368
+  //     4,
  369
+  //     9,
  370
+  //     16,
  371
+  //     25 ]
  372
+```
  373
+
  374
+How about the squares of the odd numbers from the first five numbers?
  375
+
  376
+```javascript
  377
+square(odds(take(NumberIterator(1), 5)))
  378
+  //=> TypeError: object is not a function
  379
+```
  380
+
  381
+Bzzzt! Our `odds` function returns an array, not an iterator.
  382
+
  383
+```javascript
  384
+square(take(odds(NumberIterator(1)), 5))
  385
+  //=> RangeError: Maximum call stack size exceeded
  386
+```
  387
+
  388
+You can't take the first five odd numbers at all, because `odds` tries to get the entire set of numbers and accumulate the odd ones in an array. This can be fixed. For unfolds and other infinite iterators, we need more functions that transform one iterator into another:
  389
+
  390
+```coffeescript
  391
+iteratorMap = (iter, unaryFn) ->
  392
+  ->
  393
+    element = iter()
  394
+    if element?
  395
+      unaryFn.call(element, element)
  396
+    else
  397
+      undefined
  398
+      
  399
+squaresIterator = (iter) -> iteratorMap iter, (n) -> n * n
  400
+
  401
+iteratorFilter = (iter, unaryPredicateFn) ->
  402
+  ->
  403
+    element = iter()
  404
+    while element?
  405
+      return element if unaryPredicateFn.call(element, element)
  406
+      element = iter()
  407
+    undefined
  408
+
  409
+oddsFilter = (iter) -> iteratorFilter iter, odd
  410
+```
  411
+
  412
+```javascript
  413
+
  414
+function iteratorMap (iter, unaryFn) {
  415
+  return function() {
  416
+    var element;
  417
+    element = iter();
  418
+    if (element != null) {
  419
+      return unaryFn.call(element, element);
  420
+    } else {
  421
+      return void 0;
  422
+    }
  423
+  };
  424
+};
  425
+
  426
+function squaresIterator (iter) {
  427
+  return iteratorMap(iter, function(n) {
  428
+    return n * n;
  429
+  });
  430
+};
  431
+
  432
+function iteratorFilter (iter, unaryPredicateFn) {
  433
+  return function() {
  434
+    var element;
  435
+    element = iter();
  436
+    while (element != null) {
  437
+      if (unaryPredicateFn.call(element, element)) {
  438
+        return element;
  439
+      }
  440
+      element = iter();
  441
+    }
  442
+    return void 0;
  443
+  };
  444
+};
  445
+
  446
+function oddsFilter (iter) {
  447
+  return iteratorFilter(iter, odd);
  448
+};
  449
+```
  450
+
  451
+Now we can do things like take the sum of the first five odd squares of fibonacci numbers:
  452
+
  453
+```javascript
  454
+foldingSum(take(oddsFilter(squaresIterator(FibonacciIterator())), 5))
  455
+  //=> 205
  456
+```
  457
+
  458
+This solution composes the parts we already have, rather than writing a tricky bit of code with ifs and whiles and boundary conditions.
  459
+
  460
+**summary**
  461
+
  462
+Untangling the concerns of how to iterate over data from what to do with data leads us to thinking of iterators and working directly with iterators. For example, we can map and filter iterators rather than trying to write separate map and filter functions or methods for each type of data structure. This leads to the possibility of working with lazy or infinite iterators.
  463
+
  464
+---
  465
+
  466
+My recent work:
  467
+
  468
+![](http://i.minus.com/iL337yTdgFj7.png)[![JavaScript Allongé](http://i.minus.com/iW2E1A8M5UWe6.jpeg)][ja]![](http://i.minus.com/iL337yTdgFj7.png)[![CoffeeScript Ristretto](http://i.minus.com/iMmGxzIZkHSLD.jpeg)](http://leanpub.com/coffeescript-ristretto "CoffeeScript Ristretto")![](http://i.minus.com/iL337yTdgFj7.png)[![Kestrels, Quirky Birds, and Hopeless Egocentricity](http://i.minus.com/ibw1f1ARQ4bhi1.jpeg)](http://leanpub.com/combinators "Kestrels, Quirky Birds, and Hopeless Egocentricity")
  469
+
  470
+* [JavaScript Allongé](http://leanpub.com/javascript-allonge), [CoffeeScript Ristretto](http://leanpub.com/coffeescript-ristretto), and my [other books](http://leanpub.com/u/raganwald).
  471
+* [allong.es](http://allong.es), practical function combinators and decorators for JavaScript.
  472
+* [Method Combinators](https://github.com/raganwald/method-combinators), a CoffeeScript/JavaScript library for writing method decorators, simply and easily.
  473
+* [jQuery Combinators](http://github.com/raganwald/jquery-combinators), what else? A jQuery plugin for writing your own fluent, jQuery-like code. 
  474
+
  475
+[ja]: http://leanpub.com/javascript-allonge "JavaScript Allongé"
  476
+
  477
+---
  478
+
  479
+(Spot a bug or a spelling mistake? This is a Github repo, fork it and send me a pull request!)
  480
+
  481
+[Reg Braithwaite](http://braythwayt.com) | [@raganwald](http://twitter.com/raganwald)
44  2013/02/turtles-and-iterators.md
Source Rendered
@@ -586,6 +586,45 @@ iteratorFilter = (iter, unaryPredicateFn) ->
586 586
 oddsFilter = (iter) -> iteratorFilter iter, odd
587 587
 ```
588 588
 
  589
+```javascript
  590
+
  591
+function iteratorMap (iter, unaryFn) {
  592
+  return function() {
  593
+    var element;
  594
+    element = iter();
  595
+    if (element != null) {
  596
+      return unaryFn.call(element, element);
  597
+    } else {
  598
+      return void 0;
  599
+    }
  600
+  };
  601
+};
  602
+
  603
+function squaresIterator (iter) {
  604
+  return iteratorMap(iter, function(n) {
  605
+    return n * n;
  606
+  });
  607
+};
  608
+
  609
+function iteratorFilter (iter, unaryPredicateFn) {
  610
+  return function() {
  611
+    var element;
  612
+    element = iter();
  613
+    while (element != null) {
  614
+      if (unaryPredicateFn.call(element, element)) {
  615
+        return element;
  616
+      }
  617
+      element = iter();
  618
+    }
  619
+    return void 0;
  620
+  };
  621
+};
  622
+
  623
+function oddsFilter (iter) {
  624
+  return iteratorFilter(iter, odd);
  625
+};
  626
+```
  627
+
589 628
 Now we can do things like take the sum of the first five odd squares of fibonacci numbers:
590 629
 
591 630
 ```coffeescript
@@ -593,6 +632,11 @@ foldingSum take (oddsFilter squaresIterator FibonacciIterator()), 5
593 632
   #=> 205
594 633
 ```
595 634
 
  635
+```javascript
  636
+foldingSum(take(oddsFilter(squaresIterator(FibonacciIterator())), 5))
  637
+  //=> 205
  638
+```
  639
+
596 640
 This solution composes the parts we already have, rather than writing a tricky bit of code with ifs and whiles and boundary conditions.
597 641
 
598 642
 **summary**

0 notes on commit e75b9db

Please sign in to comment.
Something went wrong with that request. Please try again.