ddinchev
ddinchev

Reputation: 34673

How to move to prev/next element of an array

Let's say we have a list of integers:

var fibonacci = [1,1,2,3,5,8,13,21];

I want to be able to get the next and previous element (just to move the element pointer, without modifying the array) in following manner (example, might go without prototype to redefine the Array interface but why not):

fibonacci.prev(); // returns false
fibonacci.next(); // returns 1
fibonacci.next(); // returns 1
fibonacci.next(); // returns 2
fibonacci.next(); // returns 3
fibonacci.next(); // returns 5
fibonacci.next(); // returns 8

fibonacci.prev(); // returns 5

fibonacci.next(); // returns 8
fibonacci.next(); // returns 13
fibonacci.next(); // returns false

Upvotes: 24

Views: 109265

Answers (6)

sonit yadav
sonit yadav

Reputation: 11

Yeah, you can use an array iterator.

var fibonacci = [1,1,2,3,5,8,13,21];

const fibIterator = fibonacci[Symbol.iterator]()

fibIterator.next(); // {value: 1, done: false}
fibIterator.next(); // {value: 1, done: false}
fibIterator.next(); // {value: 3, done: false}
....
....
fibIterator.next(); // {value: 21, done: false}
fibIterator.next(); // {value: undefined, done: true}

Upvotes: 1

wLc
wLc

Reputation: 1008

Safer than modifying the native object's prototype is creating a factory function for additional needed array functions:

const moreArrayFunctions = arr => ({
    current: 0,
    arr,
    next(){

        if( this.current >= ( this.arr.length - 1 ) ){
             this.current = this.arr.length - 1;
        }else{
            this.current++;
        }

        return this.arr[this.current];
    },
    prev(){

        if( this.current <= 0 ){
            this.current = 0;
        }else{
            this.current--;
        }

        return this.arr[this.current];
    }
});

const fibonacci = moreArrayFunctions([1,1,2,3,5,8,13,21]);

fibonacci.next();
fibonacci.prev();
fibonacci.current

Upvotes: 1

aelor
aelor

Reputation: 11116

ES6 provides us with generator functions which can allow us to print out an array quite simply like below

function* data() {
  yield* [1, 1, 2, 3, 5, 8, 13, 21];
}

var fibonnacci = data();

fibonnacci.next()
> {value: 1, done: false}

fibonnacci.next()
> {value: 1, done: false}

fibonnacci.next()
> {value: 2, done: false}

fibonnacci.next()
> {value: 3, done: false}

fibonnacci.next()
> {value: 5, done: false}

fibonnacci.next()
> {value: 8, done: false}

fibonnacci.next()
> {value: 13, done: false}

fibonnacci.next()
> {value: 21, done: false}

fibonnacci.next()
> {value: undefined, done: true}

However, an example program does exist on the MDN docs which can help print the fibonnacci series upto the element we like to.

function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {  
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

Upvotes: 4

T.J. Crowder
T.J. Crowder

Reputation: 1074989

The next aspect of this is now built into arrays, because as of ES2015, arrays are iterables, which means you can get an iterator for them which has a next method (but keep reading for the "prev" part):

const a = [1, 2, 3, 4, 5];
const iter = a[Symbol.iterator]();
let result;
while (!(result = iter.next()).done) {
  console.log(result.value);
}

Iterators only go forward though, not both ways. And of course, you normally don't use an iterator explicitly, you normally use it as part of some iteration construct, such as for-of:

const a = [1, 2, 3, 4, 5];
for (const value of a) {
  console.log(value);
}

You can easily give yourself a two-way iterator:

  1. By making a standalone function that accepts the array and returns the iterator, or

  2. By subclassing Array and overriding the iterator in the subclass, or

  3. By replacing the default Array iterator with your own (just make sure it works exactly like the default one when going forward!)

Here's an example with a subclass:

class MyArray extends Array {
  // Define the iterator function for this class
  [Symbol.iterator]() {
    // `index` points at the next value we'll return
    let index = 0;
    // Return the iterator
    return {
      // `next` returns the next
      next: () => {
        const done = index >= this.length;
        const value = done ? undefined : this[index++];
        return { value, done };
      },
      // `prev` returns the previous
      prev: () => {
        const done = index == 0;
        const value = done ? undefined : this[--index];
        return { value, done };
      }
    };
  }
}

// Demonstrate usage:
const a = new MyArray("a", "b");
const i = a[Symbol.iterator]();
console.log("next", JSON.stringify(i.next()));
console.log("next", JSON.stringify(i.next()));
console.log("next", JSON.stringify(i.next()));
console.log("prev", JSON.stringify(i.prev()));
console.log("prev", JSON.stringify(i.prev()));
console.log("prev", JSON.stringify(i.prev()));
console.log("next", JSON.stringify(i.next()));
.as-console-wrapper {
  max-height: 100% !important;
}

Upvotes: 7

MaxArt
MaxArt

Reputation: 22637

If you want to keep the list as an Array, you'll have to change its [[prototype]] to make it look like an iterable collection:

Array.prototype.next = function() {
    return this[++this.current];
};
Array.prototype.prev = function() {
    return this[--this.current];
};
Array.prototype.current = 0;

Now every Array will have the methods prev and next, and the current property, which points to the "current" elements. A caveat: the current property can be modified, thus leading to impredictable results.

Post scriptum: I don't recommend to make prev and next return false when the index is out of range. If you really want to, you can change the methods to something like:

Array.prototype.next = function() {
    if (!((this.current + 1) in this)) return false;
    return this[++this.current];
};

UPDATE mid-2016

I'm updating this answer because it seems it's still receiving views and votes. I should have clarified that the given answer is a proof of concept and in general extending the prototype of native classes is a bad practice, and should be avoided in production projects.

In particular, it's not much because it's going to mess with for...in cycles - which should always be avoided for arrays and it's definitely a bad practice for iterating through their elements - and also because since IE9 we can reliably do this instead:

Object.defineProperty(Array.prototype, "next", {
    value: function() { return this[++this.current]; },
    enumerable: false
});

The main problem is that extending native classes is not future-proof, i.e. it may happen that ECMA will introduce a next method for arrays that will probably be incompatible with your implementation. It already happened even with very common JS frameworks - the last case was MooTools' contains array extension which led ECMA to change the name to includes (bad move, IMO, since we already have contains in DOMTokenList objects like Element.classList).

That being said, it's not that you must not extend native prototypes, but you should be aware of what you're doing. The first advice I can give you is to choose names that won't clash with future standard extensions, e.g. myCompanyNext instead of just next. This will cost you some code elegance but will make you sleep sound.

Even better, in this case you can effectively extend the Array class:

function MyTraversableArray() {
    if (typeof arguments[0] === "number")
        this.length = arguments[0];
    else this.push.apply(this, arguments);

    this.current = 0;
}
MyTraversableArray.prototype = [];
MyTraversableArray.prototype.constructor = MyTraversableArray;
MyTraversableArray.prototype.next = function() {
    return this[++this.current];
};
MyTraversableArray.prototype.prev = function() {
    return this[--this.current];
};

In ES6, moreover, it's easier to extend native classes:

class MyTraversableArray extends Array {
    next() {
        return this[++this.current];
    }
}

Alas, transpilers have a hard time with native class extensions, and Babel removed its support. But it's because they can't exactly replicate some behaviours which have no influence in our case, so you can stick with the above old ES3 code.

Upvotes: 41

Pete
Pete

Reputation: 2558

I generally recommend against adding things to Array.prototype because of the amount of really bad JavaScript out there. For instance, if you set Array.protoype.next = function () {} and someone has the following code, then there's a problem:

var total = 0, i, myArr = [0,1,2];
for(i in myArr) {
    total += myArr[i];
}
total; //is probably "3next"

This bad use of for-in loops is disturbingly common out there. So you're asking for trouble by adding to Array's prototype. However, it's pretty easy to build a wrapper to do what you're looking to do:

var iterifyArr = function (arr) {
    var cur = 0;
    arr.next = (function () { return (++cur >= this.length) ? false : this[cur]; });
    arr.prev = (function () { return (--cur < 0) ? false : this[cur]; });
    return arr;
};

var fibonacci = [1, 1, 2, 3, 5, 8, 13];
iterifyArr(fibonacci);

fibonacci.prev(); // returns false
fibonacci.next(); // returns 1
fibonacci.next(); // returns 1
fibonacci.next(); // returns 2
fibonacci.next(); // returns 3
fibonacci.next(); // returns 5
fibonacci.next(); // returns 8
fibonacci.prev(); // returns 5
fibonacci.next(); // returns 8
fibonacci.next(); // returns 13
fibonacci.next(); // returns false

A couple notes:

First of all, you probably want to have it return undefined instead of false if you go past the end. Secondly, because this method hides cur using a closure, you don't have access to it on your array. So you might want to have a cur() method to grab the current value:

//Inside the iterifyArr function:
    //...
    arr.cur = (function () { return this[cur]; });
    //...

Finally, your requirements are unclear on how far past the end the "pointer" is maintained. Take the following code for example (assuming fibonacci is set as above):

fibonacci.prev(); //false
fibonacci.prev(); //false
fibonacci.next(); //Should this be false or 1?

In my code, it would be false, but you might want it to be 1, in which case you'd have to make a couple simple changes to my code.

Oh, and because it the function returns arr, you can "iterify" an array on the same line as you define it, like so:

var fibonacci = iterifyArr([1, 1, 2, 3, 5, 8, 13]);

That might make things a bit cleaner for you. You can also reset the iterator by re-calling iterifyArr on your array, or you could write a method to reset it pretty easily (just set cur to 0).

Upvotes: 27

Related Questions