How to write a proper constructor function extending the Array class - javascript

Scenario
I have the following piece of code:
const composeMatrix = (nRow, nCol, filler) => Array(nRow).fill(Array(nCol).fill(filler));
class Matrix extends Array {
constructor({ nRows = 3, nCols = 3, filler = 0 } = {}) {
super(...composeMatrix(nRows, nCols, filler));
}
makeTranspose() {
const mat = this;
const column = mat[0];
return column.map((_, i) => {
return mat.map((row) => row[i]);
});
}
}
I'm instantiating a new Matrix like this:
const mat = new Matrix({ nRows: 4, filler: 1 });
Logging mat to the console gives me as expected,
Matrix(4) [
[ 1, 1, 1 ],
[ 1, 1, 1 ],
[ 1, 1, 1 ],
[ 1, 1, 1 ]
]
Problem
Now when I call the makeTranspose method of the class, it returns me this:
[
Matrix(4) [ 1, 1, 1, 1 ],
Matrix(4) [ 1, 1, 1, 1 ],
Matrix(4) [ 1, 1, 1, 1 ]
]
Expected output:
Matrix(3) [
[ 1, 1, 1, 1 ],
[ 1, 1, 1, 1 ],
[ 1, 1, 1, 1 ]
]
What I figured is, the map function calls the constructor of this subclass every time while iterating through the array, which in turn calls super, which then calls the composeMatrix function and a new Matrix gets made.
How can I fix this?
I want a class to extend Array with some added methods.
The constructor needs to take some relevant parameters and function as expected.
I don't want to add functions to the prototype.

A matrix is not an array. You're better off using composition over inheritance.
Array.create = (length, mapping) =>
Array.from({ length }, (value, index) => mapping(index));
class Matrix {
constructor(rows, cols, data) {
this.rows = rows;
this.cols = cols;
this.data = data;
}
static create(rows, cols, mapping) {
const data = Array.create(rows, row =>
Array.create(cols, col => mapping(row, col)));
return new Matrix(rows, cols, data);
}
transpose() {
const { rows, cols, data } = this;
return Matrix.create(cols, rows, (row, col) => data[col][row]);
}
}
const mat = Matrix.create(4, 3, (row, col) => 1);
console.log(mat.transpose());
Using a functional style of programming.
const array = (length, mapping) =>
Array.from({ length }, (value, index) => mapping(index));
const Matrix = (rows, cols, data) => ({ rows, cols, data });
const matrix = (rows, cols, mapping) =>
Matrix(rows, cols, array(rows, row =>
array(cols, col => mapping(row, col))));
const transpose = ({ rows, cols, data }) =>
matrix(cols, rows, (row, col) => data[col][row]);
const mat = matrix(4, 3, (row, col) => 1);
console.log(transpose(mat));

Related

turn a flat array into cell like objects for a grid

I have an array that looks like similar to this,
[
['column1', 'column2', 'column3', 'column4', 'column5'],
['2column1', '2column2', '2column3', '2column4', '2column5']
]
I wanting to turn this array into table that looks similar to this,
header1
header2
header3
header4
header5
column1
column2
column3
column4
column5
2column1
2column2
2column3
2column4
2column5
I want to turn array above into an array of objects if possible that would look like this,
[
[
{ col:a, row: 1, cell_value: 'column1'},
{ col:b, row: 1, cell_value: 'column2'},
{ col:c, row: 1, cell_value: 'column3'},
{ col:d, row: 1, cell_value: 'column4'},
{ col:e, row: 1, cell_value: 'column5'}
],
[
{ col:a, row: 2, cell_value: '2column1'},
{ col:b, row: 2, cell_value: '2column2'},
{ col:c, row: 2, cell_value: '2column3'},
{ col:d, row: 2, cell_value: '2column4'},
{ col:e, row: 2, cell_value: '2column5'}
]
]
I have a function to create the column letters,
export const columnToLetter = (column) => {
let temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
and this is my attempt to create the above array objects,
const data = payload.formatted_values.map((value, index) => {
let columnCount = 0;
while (columnCount <= payload.formatted_values.length) {
const singleCell = {
col: columnToLetter(index+1),
row: columnCount+1,
cell_value: value[columnCount]
}
columnCount++;
return singleCell;
}
});
But the output I get is incorrect, I get this structure,
{col: 'A', row: 1, cell_value: 'Fav'}
{col: 'B', row: 1, cell_value: 'red'}
{col: 'C', row: 1, cell_value: ''}
Which is not what I want, can any advise how I would turn the flat array I start with into a object with the attributes I want?
You could map a nested result.
const
data = [['column1', 'column2', 'column3', 'column4', 'column5'], ['2column1', '2column2', '2column3', '2column4', '2column5']],
result = data.map((a, row) => a.map((cell_value, col) => ({
col: (col + 10).toString(36).toUpperCase(),
row: row + 1,
cell_value
})));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could just loop over it like so:
function makeColumn(arr)
{
var outArr = []
var rowCounter = 0
var cCounter = 97
arr.forEach((item,index) => {
outArr.push([])
item.forEach((i2,ind2) => {
outArr[index].push({
col:String.fromCharCode(cCounter++),
row: rowCounter,
cell_value: arr[rowCounter][ind2]
})
})
rowCounter++
cCounter = 97
})
return outArr
}
Using for and map()
const data = [
['column1', 'column2', 'column3', 'column4', 'column5'],
['2column1', '2column2', '2column3', '2column4', '2column5']
]
const res = []
for (let i = 0; i < data.length; i++) {
res.push(data[i].map((item, index) => {
return { col: String.fromCharCode(index+97), row: i+1, cell_value: item }
}))
}
console.log(res)
You can simply achieve this by just using Array.map() method.
Live Demo :
const arr = [
['column1', 'column2', 'column3', 'column4', 'column5'],
['2column1', '2column2', '2column3', '2column4', '2column5']
];
const res = arr.map((elem, index) => {
return elem.map((col, i) => {
return {
col: String.fromCharCode(97 + i),
row: index + 1,
cell_value: col
}
})
});
console.log(res);

dynamically get columns from 2D array

I have a 2D array of row,through which i want get the column coordinates/information just like i got for the row(rowArr2D)
So,in my Column(colArr2D) i'm just getting all 4th position values in the array since i passed have oRowCount in the function
my goal is to get all columns respectively.
Example:
Row:[ [ 0, 1, 2, 3, 4, 5, 6 ], [ 0, 1, 2, 3, 4, 5, 6 ], [ 0, 1, 2, 3, 4, 5, 6 ], [ 0, 1, 2, 3, 4, 5, 6 ] ]
Columns: [[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3]]
mockTable = { // mocking the portions of my code
GetRowsCount : () => 4,
GetRow: (x) => ({
GetCellsCount : () => 7,
GetCell : (x) => x
})
}
CTable_prototype_GetTableMapping = function(currentTable)
{
//get row information
let oRowCount = currentTable.GetRowsCount();
const rowArr2D = Array(oRowCount);
for (let i = 0; i < oRowCount; i++) {
//get cell information and cell count
let oRow = currentTable.GetRow(i);
let oCellCount = oRow.GetCellsCount();
rowArr2D[i] = Array(oCellCount);
for (let j = 0; j < oCellCount; j++) {
//get cell content
let oCell = oRow.GetCell(j);
rowArr2D[i][j] = oCell;
}
}
// get column information
const colArr2D = (array, colCount) => {
const result = [];
array.forEach(e => {
result.push(e[colCount]);
});
console.log(result);
return result;
};
colArr2D(rowArr2D, oRowCount);
return rowArr2D
console.log(rowArr2D);
};
const theArray = CTable_prototype_GetTableMapping(mockTable);
console.log("full 2D array", theArray)
Give this a try
const colArr2D = (array) =>
array[0].map((a, i) =>
array.map(b => b[i])
);
const arr = [[1,2,3],[4,5,6],[7,8,9]];
console.log(colArr2D(arr))

Merge 2 objects of same array and total the common values within the array inside each object

I tried looking into it but couldn't find the specific use case for my scenario. I am sure it is fairly simple but I am stuck on this for days. Any help will be appreciated
const stores = [
{
id: "61f27aeb766e4b2924532f98",
xName: 'SaurabhTest2',
merchantIDs: [ "61f27bca766e4b2924532fb3" ],
totalMerAcc: 1,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: [ "61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31" ],
totalMerAcc: 2,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: [ "61f277b8766e4b2924532f31" ],
totalMerAcc: 1,
oneTimeData: 100
}]
Desired output:
[
{
id: "61f27aeb766e4b2924532f98",
xName: 'SaurabhTest2',
merchantIDs: [ "61f27bca766e4b2924532fb3" ],
totalMerAcc: 1,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: [ "61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31" ],
totalMerAcc: 2,
oneTimeData: 100
}]
First the stores should merged based on "id" but then also check if the merchantsIDs already exists between the 2 mergers (there could be more than 2 same storeIDs here so more objects), and include all the distinct ones in the merchantIDs within an object and then total it in "totalMerAcc" as well to return something like above.
Here's the code I have written this this point:
function mergeRecurrentsStores(businessStores) {
const result = businessStores.map((item) => {
return [
item[0],
...item[1]
.reduce((accumulator, currentStore) => {
const key = currentStore.id.toString();
const innerItem =
accumulator.get(key) ||
Object.assign({}, currentStore, {
xName: currentStore.xName,
merchantIDs: currentStore.merchantIDs,
totalMerAcc: 0,
oneTimeData: currentStore.oneTimeData,
});
if(innerItem.merchantIDs.some(i => i.includes(currentStore.merchantIDs)) {
}
innerItem.totalMerAcc += currentStore.totalMerAcc;
return accumulator.set(key, innerItem);
}, new Map())
.values(),
];
});
return result;}
The example structure is inside item[1] which I am reducing. You can neglect the item[0] case. That's just for next thing in pipeline.
Any help is welcomed.
Array's map implements a mapping operation. Mapping operations produce a 1:1 result, one result item for each input item. But you don't want that, so map isn't the right tool.
If you were doing functional programming with predefined, reusable reducer functions, reduce might be the right tool, but if you aren't (and you don't seem to be, your reducer is inline), it's just an over-complicated loop. Instead, let's do a nice simple loop.
const byId = new Map();
for (const store of stores) {
let previous = byId.get(store.id);
if (!previous) {
// New one
byId.set(store.id, store);
} else {
// Merge with previous
// NOTE: If you don't want to modify the object in place, change
// `const previous` above to `let previous` and uncomment this:
/*
previous = {...previous, merchantIDs: [...previous.merchantIDs]};
byId.set(previous.id, previous);
*/
for (const merchantID of store.merchantIDs) {
if (!previous.merchantIDs.includes(merchantID)) {
previous.merchantIDs.push(merchantID);
}
}
previous.totalMerAcc = previous.merchantIDs.length; // It seems unnecessary to have `totalMerAcc`
}
}
const result = [...byId.values()];
Live Example:
const stores = [
{
id: "61f27aeb766e4b2924532f98",
xName: 'SaurabhTest2',
merchantIDs: ["61f27bca766e4b2924532fb3"],
totalMerAcc: 1,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: ["61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31"],
totalMerAcc: 2,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: ["61f277b8766e4b2924532f31"],
totalMerAcc: 1,
oneTimeData: 100
}
];
const byId = new Map();
for (const store of stores) {
const previous = byId.get(store.id);
if (!previous) {
// New one
byId.set(store.id, store);
} else {
// Merge with previous
// NOTE: If you don't want to modify the object in place, change
// `const previous` above to `let previous` and uncomment this:
/*
previous = {...previous, merchantIDs: [...previous.merchantIDs]};
byId.set(previous.id, previous);
*/
for (const merchantID of store.merchantIDs) {
if (!previous.merchantIDs.includes(merchantID)) {
previous.merchantIDs.push(merchantID);
}
}
previous.totalMerAcc = previous.merchantIDs.length; // It seems unnecessary to have `totalMerAcc`
}
}
const result = [...byId.values()];
console.log(JSON.stringify(result, null, 4));
.as-console-wrapper {
max-height: 100% !important;
}
For completeness, though, we can do it with reduce (because reduce is a Swiss-army knife, you can do anything related to arrays with it, because again it's basically a loop):
const result = [...stores.reduce((byId, store) => {
const previous = byId.get(store.id);
if (!previous) {
return byId.set(store.id, store);
}
const merchantIDs = [...new Set([...previous.merchantIDs, ...store.merchantIDs])];
return byId.set(store.id, {
...previous,
merchantIDs,
totalMerAcc: merchantIDs.length,
});
}, new Map()).values()];
Live Example:
const stores = [
{
id: "61f27aeb766e4b2924532f98",
xName: 'SaurabhTest2',
merchantIDs: ["61f27bca766e4b2924532fb3"],
totalMerAcc: 1,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: ["61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31"],
totalMerAcc: 2,
oneTimeData: 100
},
{
id: "61f2769b766e4b2924532f1f",
xName: 'SaurabhTest',
merchantIDs: ["61f277b8766e4b2924532f31"],
totalMerAcc: 1,
oneTimeData: 100
}
];
const result = [...stores.reduce((byId, store) => {
const previous = byId.get(store.id);
if (!previous) {
return byId.set(store.id, store);
}
const merchantIDs = [...new Set([...previous.merchantIDs, ...store.merchantIDs])];
return byId.set(store.id, {
...previous,
merchantIDs,
totalMerAcc: merchantIDs.length,
});
}, new Map()).values()];
console.log(JSON.stringify(result, null, 4));
.as-console-wrapper {
max-height: 100% !important;
}

Javascript - Grouping Object

I already figured out the way to grouping object by its category using the code below:
let groupBy = (element, key) => {
return element.reduce((value, x) => {
(value[x[key]] = value[x[key]] || []).push(x);
return value;
}, {});
};
let items = groupBy(results, 'category')
And, the result would be like so:
{
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": "#000000",
"shp_prop": "Batu Ampar"
},
{
"shp_name": "Village Boundary",
"color_prop": "#FFFFFF",
"shp_prop": "Sungai Jawi"
}
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": "#000000",
"shp_prop": "Grassland"
},
]
}
I want to group them again by merging the color_prop and shp_prop to an array inside the object like below:
{
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": ["#000000","#FFFFFF"],
"shp_prop": ["Batu Ampar","Sungai Jawi"]
},
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": ["#000000"],
"shp_prop": ["Grassland"]
},
]
}
I really appreciate it if someone could help me to solve this problem. Thank you.
You could introduce a helper that converts "rows" into "columns".
function toColumns(rows) {
const columns = {};
const keys = Object.keys(rows[0]);
for (const key of keys) columns[key] = [];
for (const row of rows) {
for (const key of keys) {
columns[key].push(row[key]);
}
}
return columns;
}
This helper converts a structure like:
[
{ a: 1, b: 2 },
{ a: 3, b: 4 },
]
Into:
{ a: [1, 3], b: [2, 4] }
With this helper defined you can convert your groups into the desired structure in the following manner:
for (const category in items) {
const columns = toColumns(items[category]);
// use the shp_name of the first item instead of an array
columns.shp_name = columns.shp_name[0];
items[category] = columns;
}
function toColumns(rows) {
const columns = {};
const keys = Object.keys(rows[0]);
for (const key of keys) columns[key] = [];
for (const row of rows) {
for (const key of keys) {
columns[key].push(row[key]);
}
}
return columns;
}
const items = {
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": "#000000",
"shp_prop": "Batu Ampar"
},
{
"shp_name": "Village Boundary",
"color_prop": "#FFFFFF",
"shp_prop": "Sungai Jawi"
}
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": "#000000",
"shp_prop": "Grassland"
},
]
}
for (const category in items) {
const columns = toColumns(items[category]);
columns.shp_name = columns.shp_name[0];
items[category] = columns;
}
console.log(items);
Replace items[category] = columns with result[category] = columns (where result is defined as const result = {} before the loop) if you don't want to mutate the original object.

JS Dynamically Generated For Loop

I am using a 3rd party API that allows me to search for housing properties. Unfortunately the API is not written in a way to allow me to search for a range so I have to make a separate call for each value in the range.
So if I want to search for all the housing properties that have 2 or 3 bedrooms I would have to make call for 2 bedrooms, then another call for 3 bedrooms. Now this can get quite tricky as there are multiple fields that can contain a range of numbers (bedrooms, bathroom, floors, garage size...).
My brute force JavaScript solution for this is to create a nested for loop that will create an array of all the calls. This is not a scalable solution and I'm looking for a way to dynamically create this for loop or another alternative way to get an array of all my calls.
My current solution:
const searchParams = {
minBedrooms: 2,
maxBedrooms: 4,
minBathrooms: 1,
maxBathrooms: 3,
minFloors: 1,
maxFloors: 1
};
let promises = [];
for (let bedrooms = searchParams.minBedrooms; bedrooms <= searchParams.maxBedrooms; bedrooms++) {
for (let bathrooms = searchParams.minBathrooms; bathrooms <= searchParams.maxBathrooms; bathrooms++) {
for (let floors = searchParams.minFloors; floors <= searchParams.maxFloors; floors++) {
promises.push(callApi(bedrooms, bathrooms, floors));
}
}
}
Promise.all(promises).then(response => {
// do stuff with response
}
Furthermore the user might not specify one of the search parameters (ie - number of bedrooms). As a result, the API will not apply that specific filter. My code currently will fail no bedroom values are passed in, and writing condition statements for each for loop is not a desire of mine.
Any ideas on how to dynamically generate the above nested for loop?
EDIT
My current solution will fail if the user does not specify the number of bedrooms but specifies bathrooms/floors as the initial for loop will not get run. I don't want to resort to using condition statements along with lots of nested loops to be creating my promise array. This is why I feel like I need to use a dynamically generated for loop.
One way to look at this is called a Cartesian product A × B × C -- for every a in A and b in B and c in C, you want a tuple (a, b, c).
For example {1, 2} × {3, 4} has 4 resulting tuples: (1, 3), (1, 4), (2, 3), (2, 4).
The easiest way to produce this is to start with just the options in the first set: (1) and (2). Then, for each option in the second set, complete each tuple with the new value:
(1), (2) with 3 added gets (1, 3) and (2, 3)
(1), (2) with 4 added gets (1, 4) and (2, 4)
In code, this might look like this:
// Each key is associated with the set of values that it can take on
const properties = {
"bedrooms": [2, 3],
"bathrooms": [1, 2],
"floors": [1, 2, 3, 4],
}
// Start with a single "empty" tuple
let tuples = [{}]
for (let p in properties) {
// For each property, augment all of the old tuples
let nextTuples = []
for (let option of properties[p]) {
// with each possible option
nextTuples = nextTuples.concat(tuples.map(old => ({[p]: option, ...old})))
}
tuples = nextTuples;
}
with tuples ending up like
[
{
"floors": 1,
"bathrooms": 1,
"bedrooms": 2
},
{
"floors": 1,
"bathrooms": 1,
"bedrooms": 3
},
......
{
"floors": 4,
"bathrooms": 2,
"bedrooms": 3
}
]
Similar to the answer from Curtis F, you can use something like
const properties = {
"bedrooms": {min: 2, max: 3},
"bathrooms": {min: 1, max: 2},
"floors": {min: 1, max: 4},
}
Then build up the "tuples" in a similar manner except that your for loop needs to count from min to max for each object in properties.
One approach you can use is a recursive function, where each layer will iterate one dimension of your matrix:
const dims = [
{ min: 2, max: 4 },
{ min: 1, max: 3 },
{ min: 1, max: 1 },
];
function matrix(dims, ...args) {
const dim = dims[0];
return dims.length
? [...new Array(dim.max - dim.min + 1)]
.map((_,i) => i + dim.min)
.map(x => matrix(dims.slice(1), ...args, x))
.reduce((a, b) => [...a, ...b], [])
: [callApi(...args)];
}
function callApi(bedrooms, bathrooms, floors) {
return `bedrooms: ${bedrooms}, bathrooms: ${bathrooms}, floors: ${floors}`;
}
console.log(matrix(dims));
Use recursive call.
Try following code:
{
function Looping ( parameters, fn, args = [] ) {
if ( parameters.length ) {
let [ [ key, param ], ...pass ] = parameters;
let promises = [];
for ( let i = param.min; i <= param.max; i++ ) {
promises.push( ...Looping( pass, fn, [ ...args, i ] ) );
}
return promises;
}
else {
return [ fn( ...args ) ];
}
}
const searchParams = {
Bedrooms: { min: 2, max: 4 },
Bathrooms: { min: 1, max: 3 },
Floors: { min: 1, max: 1 }
};
function callApi ( a, b, c ) { return Promise.resolve( `Bed: ${a}, Bath: ${b}, Floor: ${c}` ); }
console.time( 'Recursive' );
Promise.all( Looping( Object.entries( searchParams ), ( bedrooms, bathrooms, floors ) => callApi( bedrooms, bathrooms, floors ) ) )
.then( a => {
console.timeEnd( 'Recursive' );
console.log( a );
} );
}
Recursive call type faster than mapping.
( async () => {
await new Promise( resolve => {
console.time( 'map' );
function mm ( a, b ) { let r = []; for ( let i = a; i <= b; i++ ) r.push( i ); return r; }
const properties = {
Bedrooms: mm( 1, 100 ),
Bathrooms: mm( 1, 100 ),
Floors: mm( 1, 100 )
};
// Start with a single "empty" tuple
let tuples = [{}]
for (let p in properties) {
// For each property, augment all of the old tuples
let nextTuples = []
for (let option of properties[p]) {
// with each possible option
nextTuples = nextTuples.concat(tuples.map(old => ({[p]: option, ...old})))
}
tuples = nextTuples;
}
let promises = [];
function callApi ( a, b, c ) { return Promise.resolve( `Bed: ${a}, Bath: ${b}, Floor: ${c}` ); }
for ( const i of tuples ) {
let arg = [];
for ( const [ k, v ] of Object.entries( i ) ) {
arg.push( v );
}
promises.push( callApi( ...arg ) );
}
Promise.all( promises ).then( a => {
console.timeEnd( 'map' );
//console.log( a );
resolve();
} );
} );
await new Promise( resolve => {
function Looping ( parameters, fn, args = [] ) {
if ( parameters.length ) {
let [ [ key, param ], ...pass ] = parameters;
let promises = [];
for ( let i = param.min; i <= param.max; i++ ) {
promises.push( ...Looping( pass, fn, [ ...args, i ] ) );
}
return promises;
}
else {
return [ fn( ...args ) ];
}
}
const searchParams = {
Bedrooms: { min: 1, max: 100 },
Bathrooms: { min: 1, max: 100 },
Floors: { min: 1, max: 100 }
};
function callApi ( a, b, c ) { return Promise.resolve( `Bed: ${a}, Bath: ${b}, Floor: ${c}` ); }
console.time( 'Recursive' );
Promise.all( Looping( Object.entries( searchParams ), ( bedrooms, bathrooms, floors ) => callApi( bedrooms, bathrooms, floors ) ) )
.then( a => {
console.timeEnd( 'Recursive' );
//console.log( a );
resolve();
} );
} );
} )();

Categories

Resources