String sequence in ES6 Given an unique string that represents a sequence of characters, the class should implement three methods (getNextChar, getNextString and the genSequence generator).

How it works


We give a sequence of characters. For example: "abc"
We give a starting string. For example: "a"
The generator yields the following strings infinitely.


The next string of an existing string works like a dictionary, only the length doesn't change as long as there are still characters to fulfill.  

a
b
c
aa
ab
ac
ba
..
cc
aaa
aab


This is not homework, but a user in Stack Overflow chat presented the challenge and I considered taking it to a higher level.

class SeqString {

    constructor (sequence) {

        this._sequence = sequence;
        this._first    = sequence[0];
        this._last     = sequence.slice(-1);

        return this;
    }

    getNextChar (char) {

        let seq   = this._sequence,
            index = (seq.indexOf(char) + 1) % seq.length;

        return seq[index];
    }

    getNextString (str) {

        let last = str.slice(-1),
            init = str.slice(0, str.length - 1),

            lastOfSeq  = this._last,
            firstOfSeq = this._first;

        // If it is overflowing
        if (last === lastOfSeq) {

            let trail = 1,
                i     = init.length;

            // Look for an index that won't overflow
            while (i-- &amp;&amp; init[i] === lastOfSeq) ++trail;

            // If there is no such index, then the result is a new sequence
            // with an increased length
            if (i === -1) return firstOfSeq.repeat(trail + 1);

            // If there is, change the matching character at the index
            // and reset every character after that
            str = init.slice(0, i) + this.getNextChar(init[i]) +
                  firstOfSeq.repeat(trail);

            return str;

        }

        return init + this.getNextChar(last);
    }

    *genSequence (str) {

        let newStr = this.getNextString(str);

        yield newStr;
        yield* this.genSequence(newStr);    
    }

}


Specific questions


How can I improve my class functions, while maintaining a balance between performance and readability?
Tips on how I could make the code more readable for others.


Test cases

I have made a function test, to help debugging.

function test (sequence, startStr, times = 10) {

    let it = new SeqString(sequence).genSequence(startStr);

    while (times--)
        console.log(it.next().value);
}


Examples of output

Alphaset tests

const lowerAlpha = "abcdefghijklmnopqrstuvwxyz";
const alpha      = lowerAlpha + lowerAlpha.toUpperCase();

test(alpha, 'abc', 1); // abd 
test(alpha, 'BZZ', 1); // Caa
test(alpha, 'Z', 1); // aa 
test(alpha, 'ZZ', 1); // aaa


A long sequence for test(alpha, "Stack"): http://lpaste.net/137336

Other tests

test("pen", "p", 10);


  e  n  pp  ..  np


test("0123456789", "5", 10);



  6  7  8  9 00 01  02 ..


It should


Use ES6 features
Expose a generator
Handle every kind of unique sequence
Be readable
Well, to me it looks pretty good. I've made a few minor tweaks, mainly renaming variables, removing unneeded temp variables and renaming the class and methods, to make the code a little more self documenting. 

There was one thing that struct me as odd. Your class only works with charSets which contain unique chars. "ppen" will output "p" ten times. 

I would definitely add a pruning feature to remove any duplicates, that would make it a more robust class. Something like this first snippet below.



const CharStitcher = (function(){

    class CharStitcher {

        constructor (string) {
            string = CharStitcher.pruneChars(string);
            this._str = string;
            this._1st = string[0];
            this._last = string[ string.length -1 ];
            return this;
        }

        next (char) {
            return this._str[ ( this._str.indexOf(char) + 1 ) % this._str.length ];
        }

        genString (str) {

            let lastChar = str.slice(-1);
            let otherChars = str.slice(0, str.length - 1);

            if (lastChar === this._last) {
                let charAt = 1;
                let upTo = otherChars.length;
                while ( otherChars[upTo] === this._last &amp;&amp; --upTo ) ++charAt;
                if ( upTo === -1 ) return this._1st.repeat( charAt + 1 );
                return otherChars.slice( 0, upTo ) + this.next( otherChars[upTo] ) + this._1st.repeat( charAt );
            }

            return otherChars + this.next(lastChar);
        }

        *stitch (str, newStr) {
            newStr = this.genString(str);
            yield newStr, yield* this.stitch(newStr);    
        }

    }

    CharStitcher.pruneChars = function (source, pruned){
      pruned = {}
      source.split('').forEach(function(char){
        if(!Object.prototype.hasOwnProperty.call(pruned, char)){
          pruned[char] = undefined;
        }
      })
      return Object.keys(pruned).join('');
    }

    return CharStitcher;

})();




Otherwise, I've removed all the comments, I feel it remains just as readable as your initial version.

class CharStitcher {

    constructor (string) {
        this._str = string;
        this._1st = string[0];
        this._last = string[ string.length -1 ];
        return this;
    }

    next (char) {
        return this._str[ ( this._str.indexOf(char) + 1 ) % this._str.length ];
    }

    genString (str) {

        let lastChar = str.slice(-1);
        let otherChars = str.slice(0, str.length - 1);

        if (lastChar === this._last) {
            let charAt = 1;
            let upTo = otherChars.length;
            while ( otherChars[upTo] === this._last &amp;&amp; --upTo ) ++charAt;
            if ( upTo === -1 ) return this._1st.repeat( charAt + 1 );
            return otherChars.slice( 0, upTo ) + this.next( otherChars[upTo] ) + this._1st.repeat( charAt );
        }

        return otherChars + this.next(lastChar);
    }

    *stitch (str, newStr) {
        newStr = this.genString(str);
        yield newStr;
        yield* this.stitch(newStr);    
    }

}


I've also rewritten your test a little bit, also just minor tweaks.

function test (charSet, startStr, times = 10) {
    let progress = new CharStitcher(charSet).stitch(startStr);
    while ( times-- ) console.log(progress.next().value);
}

test("pen", "p", 10);




class CharStitcher {

    constructor (string) {
        this._str = string;
        this._1st = string[0];
        this._last = string[ string.length -1 ];
        return this;
    }

    next (char) {
        return this._str[ ( this._str.indexOf(char) + 1 ) % this._str.length ];
    }

    genString (str) {

        let lastChar = str.slice(-1);
        let otherChars = str.slice(0, str.length - 1);

        if (lastChar === this._last) {

            let charAt = 1;
            let upTo = otherChars.length;

            while ( otherChars[upTo] === this._last &amp;&amp; upTo-- &amp;&amp; ++charAt);

            if ( upTo === -1 ) return this._1st.repeat( charAt + 1 );
            
            return otherChars.slice( 0, upTo ) + this.next( otherChars[upTo] ) + this._1st.repeat( charAt );

        }

        return otherChars + this.next(lastChar);

    }

    *stitch (str, newStr) {

        newStr = this.genString(str);
        
        yield newStr;
        
        yield* this.stitch(newStr);    

    }

}


function test (charSet, startStr, times = 10) {
    let progress = new CharStitcher(charSet).stitch(startStr);
    while ( times-- ) console.log(progress.next().value);
}

test("pen", "p", 10);




Again, nothing major, hope my comments are helpful.