node-enumerable

ES2017 ready LINQ features written in TypeScript


Keywords
linq, enumerable, enumerables, sequence, sequences, array, arrays, generator, generators, es6, es2017, typescript, ecmascript, 2015, node, nodejs, dotnet, net, browser, browsers, web, dom, async-await, ecmascript2015, ecmascript2017, es6-javascript, iterator, javascript, linq-methods, mit-license, nodejs-library, promises, typescript2
License
MIT
Install
npm install node-enumerable@6.0.0

Documentation

npm npm

node-enumerable

ES2017 ready LINQ library written in TypeScript.

Donate

Table of contents

  1. Requirements
  2. Installation
  3. Usage
  4. Playground / demos
  5. Examples
  6. Documentation
  7. License
  8. Tests

Requirements []

Installation []

NodeJS []

Run

npm install node-enumerable --save

inside project folder to install the module.

The module requires at least Node.js 12+.

Browser []

Download the latest version from here.

<html>
  <head>
    <!-- node-enumerable -->
    <script type="text/javascript" src="js/enumerable.js"></script>
  </head>

  <body>
    <script type="text/javascript">
    
        // test code

        let seq = Enumerable.create(1, 2, 3);

        for (let item of seq) {
            alert(item);
        }
    
    </script>
  </body>
</html>

Usage []

Create a sequence []

const Enumerable = require('node-enumerable');

function *testGenerator() {
    yield 111;
    yield 222;
    yield 333;
}

// from a list of values / objects with variable length
let seq1 = Enumerable.create(1, 'MK', true, null, {});

// from an array
let seq2 = Enumerable.from([11, 22, 33, 44]);
// from a generator
let seq3 = Enumerable.from( testGenerator() );
// from a string
// 
// 'A', 'j', 'n', 'a', 't'
let seq4 = Enumerable.from('Ajnat');  // alt: Enumerable.fromString('Ajnat');

// range of numbers: 2, 3, 4, 5, 6
let seq5 = Enumerable.range(2, 5);

// 5979 'TM' strings
let seq6 = Enumerable.repeat('TM', 5979);

// build, using factory function
// 
// 'item_1', 'item_2', 'item_3'
let seq7 = Enumerable.build((cancel, index) => {
    if (index < 3) {
        return 'item_' + (index + 1);
    }
    else {
        cancel();  // we tell that we
                   // want to cancel here
    }
});

// build, using factory function
// by building a flatten list
// 
// 1, 10, 100, 2, 20, 200, 3, 30, 300
let seq8 = Enumerable.buildMany((cancel, index) => {
    let n = index + 1;

    return [ n, n * 10, n * 100 ];
}, 3);  // create 3 elements
        // 
        // the 'build()' function has
        // a same argument

// create empty sequence
let seq9 = Enumerable.empty();

Work with them []

let seq = Enumerable.create(5979, 23979, null, '23979', 1781, 241279);

let newSeq = seq.where((x) => x !== null)  // remove all elements that are (null)
                .skip(1)  // skip one element (5979)
                .take(3)  // take next remaining 3 elements (23979, 23979, 1781)
                .distinct()  // remove duplicates
                .select((x) => "" + x)  // convert to strings
                .order();  // order by element ascending

// you also can use the
// 'each' and 'forEach' methods
// of the sequence to do the
// following job
for (let item of newSeq) {
    // [0] 1781
    // [1] 23979
    console.log(item);
}

Most methods are chainable as in .NET context.

Async operations []

const FS = require('fs');
const Path = require('path');

let seq = Enumerable.create('file1.txt', 'file2.txt', 'file3.txt', 'file4.txt');

seq.async((context) => {
    if (context.isFirst) {
        context.result = 0;  // initialize a counter
                             // value for the result
                             // s. 'counter' parameter
                             // of then() method below
    }

    // [0] file1.txt
    // [1] file2.txt
    // [2] file3.txt
    // [3] file4.txt
    let fileName = context.item;

    let fullPath = Path.join(__dirname, fileName);

    if (context.index < 2) {
        FS.readFile(fullPath, (err, data) => {
            if (err) {
                context.reject(err);  // has to be called if action
                                      // FAILED with the error object
                                      // or value as argument
            }
            else {
                ++context.result;  // update counter value
                                   // for the result
                                   // s. 'counter' of then()
                                   // method below

                context.resolve();  // has to be invoked if
                                    // invocation was SUCCESSFUL
            }
        });
    }
    else {
        context.cancel();  // cancel at 3rd element
    }
}).then((counter) => {
    // OK

    console.log('Number of loaded files: ' + counter);  // 2
}).catch((err) => {
    console.log('One action failed: ' + err);
});

The context argument of the async() method uses the AsyncActionContext interface.

Playground / demos []

You can test all features in your browser.

Examples []

Filters []

// distinct()
// 1, 2, 4, 3
Enumerable.create(1, 2, 4, 2, 3)
          .distinct();
// distinctBy()
// "grape", "passionfruit", "banana", "raspberry"
Enumerable.create("grape", "passionfruit", "banana", "mango", 
                  "orange", "raspberry", "apple", "blueberry")
          .distinctBy(x => x.length);
 
// except()
// 2.0, 2.1, 2.3, 2.4, 2.5
Enumerable.create(2.0, 2.1, 2.2, 2.3, 2.4, 2.5)
          .except([2.2]); 
 
// intersect()
// 26, 30
Enumerable.create(44, 26, 92, 30, 71, 38)
          .intersect([30, 59, 83, 47, 26, 4, 3]);
       
// ofType()
// '5979', 'Tanja'
Enumerable.create(1, '5979', 2, 'Tanja', 3)
          .ofType('string');  // typeof x === 'string'
          
// union()
// 5, 3, 9, 7, 8, 6, 4, 1, 0
Enumerable.create(5, 3, 9, 7, 5, 9, 3, 7)
          .union([8, 3, 6, 4, 4, 9, 1, 0]);
          
// where()
// 1, 2, 3
Enumerable.create(1, 2, 3, 4)
          .where((x) => x < 4);

Sort elements []

// orderBy(), thenBy()
//
// "apple", "grape", "mango", "banana",
// "orange", "blueberry", "raspberry", "passionfruit"
Enumerable.create("grape", "passionfruit", "banana", "mango", 
                  "orange", "raspberry", "apple", "blueberry")
          .orderBy((x) => x.length)  // complement: orderByDescending()
          .thenBy((x) => x);  // complement: thenByDescending()
                              // shorter: then()

// reverse()
// 4, 3, 2, 1
Enumerable.create(1, 2, 3, 4)
          .reverse();

// rand()
// e.g.: 2, 5, 7, 8, 0, 4, 6, 9, 3, 1
Enumerable.range(0, 10)
          .rand();  // alt: shuffle()

Take / skip elements []

// skip()
// 3, 4
Enumerable.create(0, 1, 2, 3, 4)
          .skip(3);

// skipLast()
// 0, 1, 2, 3
Enumerable.create(0, 1, 2, 3, 4)
          .skipLast();

// skipWhile()
// 55, 666, 77
Enumerable.create(22, 33, 44, 55, 666, 77)
          .skipWhile((x) => x < 50);
          
// take()
// 0, 1, 2
Enumerable.create(0, 1, 2, 3, 4)
          .take(3);

// takeWhile()
// 22, 33, 44
Enumerable.create(22, 33, 44, 55)
          .takeWhile((x) => x < 50);

Get one element []

// elementAt()
// 33
Enumerable.create(11, 22, 33, 44)
          .elementAt(2);
          
// elementAtOrDefault()
// 'TM'
Enumerable.create(11, 22, 33, 44)
          .elementAtOrDefault(4, 'TM');  // out of range
          
// first()
// 11
Enumerable.create(11, 22, 33, 44)
          .first();
          
// firstOrDefault()
// 'MK'
Enumerable.create()
          .firstOrDefault('MK');
          
// last()
// 44
Enumerable.create(11, 22, 33, 44)
          .last();
          
// lastOrDefault()
// 'PZ'
Enumerable.create()
          .lastOrDefault('PZ');

// single()
// EXCEPTION, because we have more than one element
Enumerable.create(11, 22, 33, 44)
          .single();
          
// singleOrDefault()
// 11
Enumerable.create(11)
          .singleOrDefault('YS');

All methods with NO OrDefault suffix will throw exceptions if no element was found.

You also can use a function as first argument for all of these methods that works as filter / condition:

// first()
// 22
Enumerable.create(11, 22, 33, 44)
          .first((x) => x >= 20);

Accumulators []

// aggregate()
// " Marcel Joachim Kloubert"
Enumerable.create('Marcel', 'Joachim', 'Kloubert')
          .aggregate((accumulator, item) => {
                         return accumulator += ' ' + item;
                     }, '');

// average()
// 2.5
Enumerable.create(1, 2, 3, 4)
          .average();

// "M., Tanja"
Enumerable.create('M.', 'Tanja')
          .joinToString(', ');

Minimum / maximum values []

// max()
// 3
Enumerable.create(1, 3, 2)
          .max(); 
          
// min()
// 1
Enumerable.create(2, 3, 1, 2)
          .min();

Joins []

class Person {
    constructor(name: string) {
        this.name = name;
    }

    public name: string;
}

class Pet {
    constructor(name: string, owner: Person) {
        this.name = name;
        this.owner = owner;
    }

    public name: string;
    public owner: Person;
}

let persons = [
    new Person("Tanja"),
    new Person("Marcel"),
    new Person("Yvonne"),
    new Person("Josefine")
];

let pets = [
    new Pet("Gina", persons[1]),
    new Pet("Schnuffi", persons[1]),
    new Pet("Schnuffel", persons[2]),
    new Pet("WauWau", persons[0]),
    new Pet("Lulu", persons[3]),
    new Pet("Asta", persons[1]),
];

// groupJoin()
// 
// [0] 'Owner: Tanja; Pets: WauWau, Sparky'
// [1] 'Owner: Marcel; Pets: Gina, Schnuffi, Asta'
// [2] 'Owner: Yvonne; Pets: Schnuffel'
// [3] 'Owner: Josefine; Pets: Lulu'
Enumerable.from(persons)
          .groupJoin(pets,
                     (person) => person.name,
                     (pet) => pet.owner.name,
                     (person, petsOfPerson) => {
                         let petList = petsOfPerson
                             .select(pet => pet.name)
                             .joinToString(', ');
                     
                         return 'Owner: ' + person.name + '; Pets: ' + petList;
                     });

// join()
// 
// [0] 'Owner: Tanja; Pet: WauWau'
// [1] 'Owner: Marcel; Pet: Gina'
// [2] 'Owner: Marcel; Pet: Schnuffi'
// [3] 'Owner: Marcel; Pet: Asta'
// [4] 'Owner: Yvonne; Pet: Schnuffel'
// [5] 'Owner: Josefine; Pet: Lulu'
Enumerable.from(persons)
          .join(pets,
                (person) => person.name,
                (pet) => pet.owner.name,
                (person, pet) => {
                    return 'Owner: ' + person.name + '; Pet: ' + pet.name;
                });

Groupings []

// groupBy()
Enumerable.create("grape", "passionfruit", "blueberry",
                  "apple", "banana")
          .groupBy(fruit => fruit[0].toLowerCase())
          .each((grouping) => {
                    // grouping[0].key = 'g'
                    // grouping[0][0] = 'grape'
                    
                    // grouping[1].key = 'p'
                    // grouping[1][0] = 'passionfruit'
                    
                    // grouping[2].key = 'b'
                    // grouping[2][0] = 'blueberry'
                    // grouping[2][1] = 'banana'
                    
                    // grouping[3].key = 'a'
                    // grouping[3][0] = 'apple'
                });

Projection []

// flatten()
// 1, (false), 3, 44, '555', 66.6, (true)
Enumerable.from( [ [ 1, false, 3 ], 44, [ '555', 66.6, true ] ] )
          .flatten();

// select()
// "MARCEL", "KLOUBERT"
Enumerable.create("Marcel", "Kloubert")
          .select(x => x.toUpperCase());
          
// selectMany()
// 1, 10, 100, 2, 20, 200, 3, 30, 300
Enumerable.create(1, 2, 3)
          .selectMany(x => [ x, x * 10, x * 100 ]);

// zip()
// "Marcel Kloubert", "Bill Gates", "Albert Einstein"
Enumerable.create('Marcel', 'Bill', 'Albert')
          .zip(['Kloubert', 'Gates', 'Einstein', 'Adenauer'],
               (firstName, lastName) => {
                   return `${firstName} ${lastName}`;
               });

Checks / conditions []

// all()
// (false)
Enumerable.create(1, 2, '3', 4)
          .all((x) => typeof x !== "string");

// any()
// (true)
Enumerable.create(1, 2, '3', 4)
          .any((x) => typeof x === "string");

// contains()
// (true)
Enumerable.create(1, 2, '3')
          .contains(3);

// not()
// 1, 2, 4
Enumerable.create(1, 2, '3', 4)
          .not((x) => typeof x === "string");
 
// sequenceEqual()
// (false)         
Enumerable.create(1, 2, 3)
          .sequenceEqual([1, 3, 2]);

Conversions []

// toArray()
let jsArray = Enumerable.create(1, 2, 3, 4)
                        .toArray();
  
// toObject()
let obj = Enumerable.create(1, 2, 3, 4)
                    .toObject((item, index) => "item" + index);  

// toLookup()
// 
// lookup['A'][0] = 'Albert'
// lookup['B'][0] = 'Bill'
// lookup['B'][1] = 'barney'
// lookup['K'][0] = 'Konrad'
// lookup['M'][0] = 'Marcel'
let lookup = Enumerable.create('Bill', 'Marcel', 'barney', 'Albert', 'Konrad')
                       .toLookup(x => x[0].toUpperCase());

Count []

// 3
Enumerable.create(0, 1, 2)
          .count();  // a second call will return 0
                     // if reset() method is not called
          
// 2
Enumerable.create(0, 1, 2)
          .count((x) => x > 0);

// 4
Enumerable.create(11, 22, 33, 44)
          .length();  // a second call will return
                      // the same value, because we have an array
                      // based sequence here
                      //
                      // a generator based sequence will behave as count()

// (false)
Enumerable.create(111, 222, 333)
          .isEmpty();

// all are (false)
Enumerable.isNullOrEmpty(
    Enumerable.create(1111, 2222, 3333)
);
Enumerable.isUndefinedNullOrEmpty(
    Enumerable.create(11111, 22222, 33333)
);
Enumerable.isUndefinedNullOrEmpty(
    Enumerable.create(0, true, false)
);

Math []

// abs()
// 1, 22.57, 444, NaN, -333.85, NaN
Enumerable.create(-1, 22.57, 444, true, -333.85, false)
          .abs();

// ceil()
// -1, 23, 444, NaN, -333, NaN
Enumerable.create(-1, 22.47, 444, null, -333.85, false)
          .ceil();

// cos()
// 0.004, -0.99996, -0.01
Enumerable.create(11, 22, 33)
          .cos();  // complement: arcCos()

// cosH()
// 29937.07, 1792456423.07, 107321789892958.03
Enumerable.create(11, 22, 33)
          .cosH();  // complement: arcCosH()

// exp()
// 2.72, 7.39, 20.09
Enumerable.create(1, 2, 3)
          .exp();

// floor()
// -1, 23, 444, NaN, -334, NaN
Enumerable.create(-1, 22.47, 444.0, undefined, -333.85, true)
          .floor();

// log()
// 0, 1, 2, 3, 4
Enumerable.create(1, 2, 4, 8, 16)
          .log(2);

// pow()
// 1, 4, 9, 16
Enumerable.create(1, 2, 3, 4)
          .pow(2);

// product()
// 24
Enumerable.create(1, 2, 3, 4)
          .product();

// root()
// 1, 2, 3, 4
Enumerable.create(1, 8, 27, 64)
          .root(3);

// round()
// -1, 23, 444, NaN, -334, 2, NaN
Enumerable.create(-1, 22.47, 444.0, undefined, -333.85, 1.5, true)
          .round();

// sin()
// 0.84, 0.91, 0.14
Enumerable.create(1, 2, 3)
          .sin();  // complement: arcSin()

// sinH()
// 1.18, 3.63, 10.02
Enumerable.create(1, 2, 3)
          .sinH();  // complement: arcSinH()

// sqrt()
// 1, 2, 3, 4
Enumerable.create(1, 4, 9, 16)
          .sqrt();

// sum()
// 10
Enumerable.create(1, 2, 3, 4)
          .sum();

// tan()
// 1.72, -1.76, -0.01
Enumerable.create(111, 222, 333)
          .tan();  // complement: arcTan()

// tanH()
// 0, 0.46, -0.76
Enumerable.create(0, 0.5, -1)
          .tanH();  // complement: arcTanH()

More []

assert []

let seq1 = Enumerable.range(0, 10);
seq1.assert((x) => {
    return x % 2 !== 1;
});  // will throw an exception
     // at second element (1)

let seq2 = Enumerable.range(0, 10);
seq2.assertAll((x) => {
    return x % 2 !== 1;
});  // will throw an aggregated exception
     // at the end
     // for all odd values

chunk []

let seq = Enumerable.range(0, 10);
for (let chunk of seq.chunk(3)) {
    // [0] => [0, 1, 2]
    // [1] => [3, 4, 5]
    // [2] => [6, 7, 8]
    // [3] => [9]
}

clone []

let father = Enumerable.create(0, 1, 2);

// create 3 clones of 'father'
for (let child of father.clone(3)) {
    //TODO
}

// alt: father.clone().take(3)

concat / concatArray []

// 0, 1, 2, 'PZ', 'TM', 'MK'
Enumerable.create(0, 1, 2)
          .concat(['PZ'], ['TM', 'MK']);  // alt: append()

// 0, 111, 222, 'pz', 'tm', 'mk'
Enumerable.create(0, 111, 222)
          .concatArray([ [ 'pz', 'tm' ], [ 'mk' ] ]);  // alt: appendArray()

consume []

function createIteratorAndStorage(size) {
    let storage = [];

    return {
        iterator: makeIterator(size, storage),
        storage: storage,
    };
}

function *makeIterator(size, storage) {
    for (let i = 0; i < size; i++) {
        yield i;

        storage.push(i);
    }
}

const OBJ = createIteratorAndStorage(100);

const SEQ = Enumerable.from(OBJ.iterator);
SEQ.consume();  // enumerates the 'iterator' in OBJ
                // and fills the 'storage' in OBJ

defaultIfEmpty / defaultArrayIfEmpty []

// 0, 1, 2
Enumerable.create(0, 1, 2)
          .defaultIfEmpty('PZ', 'TM', 'MK');
          
// 'PZ', 'TM', 'MK'
Enumerable.empty()
          .defaultIfEmpty('PZ', 'TM', 'MK');

// 0, 11, 22
Enumerable.create(0, 11, 22)
          .defaultArrayIfEmpty(['pz', 'tm', 'mk']);
// alt: defaultSequenceIfEmpty()

// 'pz', 'tm', 'mk'
Enumerable.empty()
          .defaultArrayIfEmpty(['pz', 'tm', 'mk']);

forAll []

let arr = [];

try {
    // alt: eachAll()
    Enumerable.range(0, 5).forAll(x => {
        if (x % 2 === 0) {
            throw 'Error in value ' + x;
        }

        arr.push(x);
    });
}
catch (e) {
    // access the list of errors by
    // 'e.errors'

    // e.errors[0] = 'Error in value 0';
    // e.errors[1] = 'Error in value 2';
    // e.errors[2] = 'Error in value 3';
}

// arr[0] === 1
// arr[1] === 3
// arr[2] === 5

intersperse / intersperseArray []

// 0, '-', 1, '-', 2
Enumerable.range(0, 3)
          .intersperse('-');

// -- or --
Enumerable.range(0, 3)
          .intersperseArray( ['-'] );

pipe []

let arr1 = [];
let arr2 = [];

let seq = Enumerable.create(1, 2, 3).pipe((x) => {
    arr1.push(x * 10);
});
for (let item of seq) {
    arr2.push(item);
}

// arr1 = [10, 20, 30]
// arr2 = [1, 2, 3]

popFrom / shiftFrom []

let arr1 = [ 11, 22, 33 ];
for (let item of Enumerable.popFrom(arr1)) {
    // [0] 33
    // [1] 22
    // [2] 11
}
// arr1 is empty now

let arr2 = [ 111, 222, 333 ];
for (let item of Enumerable.shiftFrom(arr2)) {
    // [0] 111
    // [1] 222
    // [2] 333
}
// arr2 is empty now

prepend / prependArray []

// 'PZ', 'TM', 'MK', 0, 1, 2
Enumerable.create(0, 1, 2)
          .prepend(['PZ'], ['TM', 'MK']);

// 'pz', 'tm', 'mk', 0, 111, 222
Enumerable.create(0, 111, 222)
          .prependArray([ [ 'pz', 'tm' ], [ 'mk' ] ]);

pushTo []

let arr = [];
Enumerable.create(0, 1, 2)
          .pushTo(arr);

// arr: [0, 1, 2]

random []

for (let value of Enumerable.random(10)) {
    // 10 random numbers
    // between 0 and 1
}

for (let value of Enumerable.random(23979,
                                    v => v * 5979)) {
    // 23979 random numbers
    // between 0 and 5979
}

reset []

let seq = Enumerable.create(0, 1, 2);

seq.each(x => {
             console.log(x);
         });

seq.reset()
   .each(x => {
             console.log(x * 2);
         });

trace []

// write items via 'console.trace()'
Enumerable.create(0, 1, 2)
          .trace();

// with formatter
Enumerable.create(1.2, 2.3, 3.45)
          .trace(x => 'Item: ' + x);

Documentation []

The API documentation can be found here.

License []

MIT license

Tests []

Go to the module folder and run

tsc
npm test

to start unit tests from test/ subfolder.