diff --git a/src/api/col-map.js b/src/api/col-map.js new file mode 100644 index 0000000..9c16f4c --- /dev/null +++ b/src/api/col-map.js @@ -0,0 +1,19 @@ +/** + * Map for collections. + * @param {function} f Call back function that is called on each item. + * @param {number} start The start index in the collection. + * @param {number} end The end index. + * @param {function} itemFn Item function to create chainable-methods of the item. + * @return {Array} The mapped collection. + * @private + */ +var colMap = function ( f, start, end, itemFn ) { + const result = []; + for ( let k = start; k <= end; k += 1 ) { + // Use relative indexing by adding `start` from `k`. + result.push(f( itemFn( k ), ( k - start ) )); + } + return result; +}; + +module.exports = colMap; diff --git a/src/api/sel-map.js b/src/api/sel-map.js new file mode 100644 index 0000000..4d4b9d2 --- /dev/null +++ b/src/api/sel-map.js @@ -0,0 +1,13 @@ +/** + * Map selected items. + * @param {function} f Call back function that is called on each item. + * @param {number[]} selection Array containing indexes to the selected items. + * @param {function} itemFn Item function to create chainable-methods of the item. + * @return {Array} Array of mapped items. + * @private + */ +var selMap = function ( f, selection, itemFn ) { + return selection.map( ( item, i ) => f( itemFn( item ), i ) ); +}; // selMap() + +module.exports = selMap; diff --git a/src/doc-v2.js b/src/doc-v2.js index bc5bf70..ebc0f6e 100644 --- a/src/doc-v2.js +++ b/src/doc-v2.js @@ -46,6 +46,9 @@ var selGetItemAt = require( './api/sel-get-item.js' ); var colEach = require( './api/col-each.js' ); var selEach = require( './api/sel-each.js' ); +var colMap = require( './api/col-map.js' ); +var selMap = require( './api/sel-map.js' ); + // **Filter** for collection & selection. var colFilter = require( './api/col-filter.js' ); var selFilter = require( './api/sel-filter.js' ); @@ -185,6 +188,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => selEach( f, selectedTokens, itemToken ); + // Map. + api.map = ( f ) => selMap( f, selectedTokens, itemToken ); // Filter. api.filter = ( f ) => selFilter( f, selectedTokens, itemToken, colSelectedTokens ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -213,6 +218,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => colEach( f, start, end, itemToken ); + // Map. + api.map = ( f ) => colMap( f, start, end, itemToken ); // Filter. api.filter = ( f ) => colFilter( f, start, end, itemToken, colSelectedTokens ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -275,6 +282,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => selEach( f, selectedEntities, itemEntity ); + // Map. + api.map = ( f ) => selMap( f, selectedEntities, itemEntity ); // Filter. api.filter = ( f ) => selFilter( f, selectedEntities, itemEntity, colSelectedEntities ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -299,6 +308,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => colEach( f, 0, entities.length - 1, itemEntity ); + // Map. + api.map = ( f ) => colMap( f, 0, entities.length - 1, itemEntity ); // Filter. api.filter = ( f ) => colFilter( f, 0, entities.length - 1, itemEntity, colSelectedEntities ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -357,6 +368,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => selEach( f, selectedCustomEntities, itemCustomEntity ); + // Map. + api.map = ( f ) => selMap( f, selectedCustomEntities, itemCustomEntity ); // Filter. api.filter = ( f ) => selFilter( f, selectedCustomEntities, itemCustomEntity, colSelectedCustomEntities ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -381,6 +394,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => colEach( f, 0, customEntities.length - 1, itemCustomEntity ); + // Map. + api.map = ( f ) => colMap( f, 0, customEntities.length - 1, itemCustomEntity ); // Filter. api.filter = ( f ) => colFilter( f, 0, customEntities.length - 1, itemCustomEntity, colSelectedCustomEntities ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. @@ -438,6 +453,8 @@ var doc = function ( docData, addons ) { var api = Object.create( null ); // Iterator. api.each = ( f ) => colEach( f, 0, sentences.length - 1, itemSentence ); + // Map. + api.map = ( f ) => colMap( f, 0, sentences.length - 1, itemSentence ); // Item at `k`th index. If `k` is outside valid range, return `undefined` like JS. api.itemAt = ( k ) => colGetItemAt( k, 0, ( sentences.length - 1 ), itemSentence ); // Length of this collection. diff --git a/test/apiA-specs.js b/test/apiA-specs.js index ef6b444..7f9db3e 100644 --- a/test/apiA-specs.js +++ b/test/apiA-specs.js @@ -171,6 +171,14 @@ describe( 'APIs — A', function () { } ); } ); + it( '.map() iterator should go through each sentence', function () { + var result = doc1.sentences().map( ( s ) => s.out() ); + expect( result ).to.deep.equal( as1 ); + + result = doc2.sentences().map( ( s ) => s.out() ); + expect( result ).to.deep.equal( as2 ); + } ); + it( '.itemAt() should return correct sentence item', function () { const i11 = doc1.sentences().itemAt( 0 ); expect( i11.out() ).to.equal( as1[ 0 ] ); @@ -189,6 +197,15 @@ describe( 'APIs — A', function () { } ); } ); + it( '.tokens().map() should return retlative token indexes — k', function () { + var result = doc1.sentences().itemAt( 2 ).tokens().map( ( t ) => t.out() ); + console.log('🚀 ~ file: apiA-specs.js:202 ~ result:', result, d2s2t); + expect(result).to.deep.equal( d1s2t ); + + result = doc2.sentences().itemAt( 2 ).tokens().map( ( t ) => t.out() ); + expect(result).to.deep.equal( d2s2t ); + } ); + it( 'sentence.entities() should sentence wise entities correctly', function () { doc1.sentences().each( ( s, k ) => { expect( s.entities().out( its.detail ) ).deep.equal( d1SentenceWiseEntities[ k ] ); @@ -225,6 +242,14 @@ describe( 'APIs — A', function () { } ); } ); + it('.map() should return array of entities with their details', function () { + const result1 = doc1.entities().map((e) => e.out(its.detail)); + expect(result1).to.deep.equal(ae1); + + const result2 = doc2.entities().map((e) => e.out(its.detail)); + expect(result2).to.deep.equal(ae2); + }); + it( '.itemAt() should return correct entity item', function () { const i11 = doc1.entities().itemAt( 5 ); expect( i11.out( its.detail ) ).to.deep.equal( ae1[ 5 ] ); @@ -283,6 +308,24 @@ describe( 'APIs — A', function () { expect( e.parentSentence().out() ).to.deep.equal( doc2.sentences().itemAt( es[ k ] ).out() ); }); } ); + + it('entities().map() --- sentence() should point correct sentence for each entity', function () { + // Maps entity's index to sentence's index. + const es = [ 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2 ]; + + const expectedSentencesForDoc1 = es.map((sentenceIdx) => + doc1.sentences().itemAt(sentenceIdx).out() + ); + const expectedSentencesForDoc2 = es.map((sentenceIdx) => + doc2.sentences().itemAt(sentenceIdx).out() + ); + + const actualSentencesForDoc1 = doc1.entities().map((e) => e.parentSentence().out()); + const actualSentencesForDoc2 = doc2.entities().map((e) => e.parentSentence().out()); + + expect(actualSentencesForDoc1).to.deep.equal(expectedSentencesForDoc1); + expect(actualSentencesForDoc2).to.deep.equal(expectedSentencesForDoc2); + }); } ); // doc.entities() API describe( 'doc.tokens() API', function () { @@ -367,6 +410,20 @@ describe( 'APIs — A', function () { } ); } ); + it('.map() should correctly map filtered numbers', function () { + const mappedF1 = doc1.tokens() + .filter((t) => t.out(its.type) === 'number') + .map((t) => t.out()); + + expect(mappedF1).to.deep.equal(f1num); + + const mappedF2 = doc2.tokens() + .filter((t) => t.out(its.type) === 'number') + .map((t) => t.out()); + + expect(mappedF2).to.deep.equal(f2num); + }); + it( '.itemAt() should return correct item from filtered numebrs', function () { const i1 = doc1.tokens() .filter( ( t ) => ( t.out( its.type ) === 'number' ) ) diff --git a/test/apiB-specs.js b/test/apiB-specs.js index 3d77915..4ce0a98 100644 --- a/test/apiB-specs.js +++ b/test/apiB-specs.js @@ -227,6 +227,23 @@ describe( 'APIs — B', function () { } } ); // .each() method via .itmAt() + it('.map() method via .itemAt()', function () { + const mappedSentences = []; + + for (let i = 0; i < docs.length; i += 1) { + const doc = docs[i]; + const sentencesFromMap = doc.sentences().map((s, k) => { + expect(s.out()).to.deep.equal(doc.sentences().itemAt(k).out()); + expect(s.out()).to.deep.equal(sentences[i][k]); + return s.out(); + }); + + mappedSentences.push(sentencesFromMap); + } + + expect(mappedSentences).to.deep.equal(sentences); + }); + it( '.length() method', function () { for ( let i = 0; i < docs.length; i += 1 ) { expect( docs[ i ].sentences().length() ).to.equal( sentences[ i ].length ); @@ -305,6 +322,15 @@ describe( 'APIs — B', function () { } // for } ); // .each() method + it('.map() method', function () { + for (let i = 0; i < docs.length; i += 1) { + const doc = docs[i]; + const result = doc.entities().map((e) => (e ? e.out() : undefined)); + + expect(result).to.deep.equal([].concat([], ...entities[i])); + } + }); + it( '.filter() method', function () { expect( docs[ 0 ].entities().filter( dates ).out() ).to.deep.equal( d0[ DATE ] ); expect( docs[ 0 ].entities().filter( cardinals ).out() ).to.deep.equal( d0[ CARDINAL ] ); @@ -348,6 +374,11 @@ describe( 'APIs — B', function () { } ); } ); // .each() method + it('.map() method', function () { + const result = selOfCardinals.map((e) => e.out()); + expect(result).to.deep.equal(d0[CARDINAL]); + }); + it( '.filter() method', function () { expect( selOfCardinals.filter( ( e ) => ( e.out() === d0[ CARDINAL ][ 0 ] ) ).out() ).to.deep.equal( d0[ CARDINAL ].slice( 0, 1 ) ); } ); // .filter() method @@ -420,6 +451,15 @@ describe( 'APIs — B', function () { } // for } ); // .each() method + it('.map() method', function () { + for (let i = 0; i < docs.length; i += 1) { + const doc = docs[i]; + const result = doc.customEntities().map((e) => (e ? e.out() : undefined)); + + expect(result).to.deep.equal([].concat([], ...customEntities[i])); + } + }); + it( '.filter() method', function () { expect( docs[ 0 ].customEntities().filter( fighters ).out() ).to.deep.equal( d0ce.fighters ); expect( docs[ 0 ].customEntities().filter( orgs ).out() ).to.deep.equal( d0ce.orgs ); @@ -463,6 +503,11 @@ describe( 'APIs — B', function () { } ); } ); // .each() method + it( '.map() method', function () { + const result = selOfFighters.map( ( e ) => e.out()); + expect( result ).to.deep.equal( d0ce.fighters ); + } ); // .each() method + it( '.filter() method', function () { expect( selOfFighters.filter( ( e ) => ( e.out() === d0ce.fighters[ 0 ] ) ).out() ).to.deep.equal( d0ce.fighters.slice( 0, 1 ) ); } ); // .filter() method @@ -535,6 +580,15 @@ describe( 'APIs — B', function () { } // for } ); // .each() method + it('.map() method', function () { + for (let i = 0; i < docs.length; i += 1) { + const doc = docs[i]; + const result = doc.tokens().map((t) => (t ? t.out() : undefined)); + + expect(result).to.deep.equal([].concat([], ...tokens[i])); + } + }); + it( '.filter() method', function () { expect( docs[ 0 ].tokens().filter( words ).out() ).to.deep.equal( d0words ); expect( docs[ 1 ].tokens().filter( words ).out() ).to.deep.equal( d1words ); @@ -583,6 +637,14 @@ describe( 'APIs — B', function () { } ); } ); // .each() method + it('.map() method', function () { + const result0 = selOfWords0.map((e) => e.out()); + const result1 = selOfWords1.map((e) => e.out()); + + expect(result0).to.deep.equal(d0words); + expect(result1).to.deep.equal(d1words); + }); + it( '.filter() method', function () { expect( selOfWords0.filter( ( t ) => ( t.out().length < 3 ) ).out() ).to.deep.equal( [ 'by', 'is', 'by' ] ); expect( selOfWords1.filter( ( t ) => ( t.out().length < 3 ) ).out() ).to.deep.equal( [ 'me', 'up', 'on', 'my', 'we' ] );