Skip to content

Instantly share code, notes, and snippets.

@shiftyp
Last active July 4, 2017 18:29
Show Gist options
  • Save shiftyp/c8353cc64f7eb08a9033 to your computer and use it in GitHub Desktop.
Save shiftyp/c8353cc64f7eb08a9033 to your computer and use it in GitHub Desktop.
Eloquent JavaScript: Chapter 6, Exercise 3

Eloquent JavaScript: Chapter 6, Exercise 3

For this problem you have to come up with an api that works for two different object types: The RangeSeq which represents a range of integer values between two given values, and the ArraySeq which wraps an array with a set of values. The interface should allow for the values represented by these two object types to be iterated (looped) over.

The interface I came up with could be represented like this if I wrote it into a plain object:

{
  next: function(){
    // returns the next object in the sequence
  },
  get end() : function() {
    // returns a boolean indicating whether we've reached the end of the sequence
  }
}

It's up to us to make sure that each object type we create that implements this interface has next and end that actually do something with the data internally represented by the object. So here is a workable solution to this problem:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Eloquent JavaScript: Chapter 6, Exercise 3</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<script src="./index.js" type="text/javascript"></script>
</head>
<body>
<div class="container">
<p class="lead">This gist logs to the console, so open the console in developer tools to see the output</p>
</div>
</body>
</html>
/*
So here is our RangeSeq implementation. It stores the from and to values
and uses those to determine what values it should return from this.next,
and if the end of the sequence has been reached in this.end.
*/
function RangeSeq(from, to) {
this.from = from;
this.to = to;
}
/*
Here we are using this.from to keep track of what value were at.
We increment it each time next is called. One thing to note is
that I'm incrementing this.from on the same line where I return
it. There are two ways to use the increment (++) operator. You
can put it before, which will equal the incremented value, or
you put it after which will give you the current value. So even
though I'm returning this.from++, it actually returns the current
value of this.from then increments it.
*/
RangeSeq.prototype.next = function() {
// We can actually do whats called "eating our own dogfood" here
// and make use of this.end within this.next.
if (this.end) {
// If we don't have any more values, return null. Returning null
// when there is no more data is a standard practice in a lot of
// languages including JavaScript, so we'll use that convention.
return null;
} else {
return this.from++;
}
};
/*
Here's how we know if we've reached the end of the sequence. We've
been incrementing this.from, so if it's greater than this.to we
know we've reached the end.
*/
Object.defineProperty(RangeSeq.prototype, 'end', {
get: function() {
return this.from > this.to;
}
})
/*
So here is the ArraySeq implementation. Again, we need to have a next
and end function to match the interface, but the implementation will
be different since we're dealing with an actual array of values.
*/
function ArraySeq(arr) {
// We don't want someone modifying the passed array and changing the
// internal state of our sequence, so we make an internal copy of the
// passed array by calling Array#slice passing no arguments.
this.arr = arr.slice();
// We need to keep track of our current position in the array, so we
// create a property called index to store that position. We start at
// zero since that's the first index in the array.
this.index = 0;
}
ArraySeq.prototype.next = function() {
// Again, eating our own dogfood.
if (this.end) {
return null;
} else {
// This is the same incrementing situation as in the RangeSeq. This
// statement gets the current value of this.index, then increments it.
return this.arr[this.index++];
}
}
/*
Since we're incrementing this.index, we know we've reached the end of the
array when this.index is equal to this.arr.length.
*/
Object.defineProperty(ArraySeq.prototype, 'end', {
get: function() {
return this.index === this.arr.length;
}
});
/*
Now we can make use of our sequence implementations. As far as interacting
with our sequences, the code that uses our API (called the API consumer)
doesn't care which sequence it is, only that it implements the functions
of the sequence interface. This is really powerful, and is the whole point
of this pattern. We can write code that is "agnostic" to the particular
implementation, so if we want to use the code with another sequence, or
internally change how a sequence works, we won't have to change the code
that uses it.
*/
function logFive(sequence) {
for (var i = 0; i < 5 && !sequence.end; i++) {
console.log(sequence.next());
}
}
/*
And that's it! Now we can use our sequence implementations and log to the
console.
*/
console.log('logFive(new ArraySeq([1, 2]));\n')
logFive(new ArraySeq([1, 2]));
console.log('logFive(new RangeSeq(100, 1000)));\n');
logFive(new RangeSeq(100, 1000));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment