JS Array.push acts in unexpected way [duplicate] - javascript

This question already has an answer here:
javascript push returning number instead of object [duplicate]
(1 answer)
Closed 8 months ago.
I am experiencing unexpected behaviour of push function.
The problem is with the latest line of code cited below.
export enum non_searchFieldsNames {
language = 'language',
categories = 'categories',
subtitle = 'subtitle',
publishedDate = 'publishedDate',
id = 'id',
}
enum columnsEnum {
title,
authors,
language,
categories,
subtitle,
publishedDate,
}
function toArray(obj: header | contentCategories | sourceFields) {
return Object.entries(obj)
.sort((a, b) => {
return +a - +b;
})
.map(item => item[1]);
}
let sourceFieldsObject: sourceFields = {
[columnsEnum.title]: searchFieldsNames.title,
[columnsEnum.authors]: searchFieldsNames.authors,
[columnsEnum.language]: non_searchFieldsNames.language,
[columnsEnum.categories]: non_searchFieldsNames.categories,
[columnsEnum.subtitle]: non_searchFieldsNames.subtitle,
[columnsEnum.publishedDate]: non_searchFieldsNames.publishedDate,
};
const sourceFieldsArray = toArray(sourceFieldsObject).push(non_searchFieldsNames.id);
The problem is with the latest line of code. The value I do receive here is 7.
When I simplify it like this
const sourceFieldsArray = toArray(sourceFieldsObject)
I receive an array(however, without the value I try to add, of course).
When I split the logic
const sourceFieldsArray = (toArray(sourceFieldsObject));
sourceFieldsArray.push(non_searchFieldsNames.id);
I get what I wanted. Anyway, I would like to have it as one-liner. So, what is my error?
I have tried also
const sourceFieldsArray (toArray(sourceFieldsObject)).push(non_searchFieldsNames.id)
But it does not help.

push modifies the main array directly, and it does not return a new array as you expected, but the count of items in that array.
You can check the below demo for push's returned value
const array = [1,1,1] //3 items
const count = array.push(1) //1 more item
console.log(count) //total is 4 items
If you want to have a one-liner, you can try the cloning approach with the spreading operator
const sourceFieldsArray = [...toArray(sourceFieldsObject), non_searchFieldsNames.id]
Side note that this approach means you're creating a new array completely, so you need to be aware of some performance situations with a huge array.

Related

Filtering duplicates form array of objects

looking for an explanation as to why the below code doesn't filter correctly. I have an array of objects, one of those properties is buy_mth_yr which is in the format mmm-yy as below.
buy_mth_yr : 'Apr-18'
I am trying to get a list of all unique buy_mth_yr values and return as a list of label-value objects, to be passed into a filter. I have already found a solution in a separate stack overflow question, the point of me asking this question is more to understand why my original solution below doesn't work. so lets say data is my list of objects with the buy_mth_yr key. the if statement calls every time and I end up with an unfiltered list. any help/insight appreciated!
let distinct = [];
data.forEach(record =>{
let temp = {label: record.buy_mth_yr, value: record.buy_mth_yr}
if(!(temp in distinct)){
distinct.push(temp)
}
})
return distinct;
to understand why my original solution below doesn't work
Because you are doing a referential comparison - first link that popped up on google, it might help: https://dmitripavlutin.com/how-to-compare-objects-in-javascript/#1-referential-equality
({a:1}) !== ({a:1})
// but
const obj = {a:1};
obj === obj
temp will never be in distinct because it is always a new object, every time.
EDIT:
To add to this, a better solution (and there's a thousand ways to write this, below is but one example) would look like this:
const isShallowEqualWith = (a) => (b) => {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
return keysA.length === keysB.length && keysA.every(k => a[k] === b[k]);
}
let distinct = [];
data.forEach(record =>{
let temp = {label: record.buy_mth_yr, value: record.buy_mth_yr}
if(!distinct.find(isShallowEqualWith(temp)) {
distinct.push(temp);
}
})
return distinct;
I think your !(temp in distinct) is not the check you want to do : use !distinct.includes(temp) instead.
However you should take a look to lodash library with _.uniq and _.uniqBy functions : https://www.geeksforgeeks.org/lodash-_-uniqby-method/

Given an array of objects, count how many (possibly different) properties are defined

In a GeoJSON file, some properties are shared by all "features" (element) of the entire collection (array). But some properties are defined only for a subset of the collection.
I've found this question: [javascript] counting properties of the objects in an array of objects, but it doesn't answer my problem.
Example:
const features =
[ {"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4] ...]}},
{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4] ...]}},
{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4] ...]]}},
// ... for instance 1000 different cities
{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[...]}}
];
expected result: a list a all existing properties and their cardinality, letting us know how (in)complete is the data-set. For instance:
properties: 1000, properties.name: 1000, properties.zip: 890, properties.updated: 412,
geometry: 1000, geometry.type: 1000, geometry.coordinates: 1000
I have a (rather complicated) solution, but I do suspect that some people have already faced the same issue (seems a data science classic), with a better one (performance matters).
Here is my clumsy solution:
// 1: list all properties encountered in the features array, at least two levels deep
const countProps = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f)))), []);
// adding all the properties of each individual feature, then removing duplicates using the array-set-array trick
const countProp2s = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f.properties)))), []);
const countProp2g = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f.geometry)))), []);
// 2: counting the number of defined occurrences of each property of the list 1
const countPerProp = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f[pf]), 0)}`;
const countPerProp2s = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f.properties[pf]), 0)}`;
const countPerProp2g = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f.geometry[pf]), 0)}`;
const cardinalities = countProps(features).map((kk,i) => countPerProp(ff)(kk)) +
countProp2s(features).map(kk => countPerProp2s(ff)(kk)) +
countProp2g(features).map(kk => countPerProp2g(ff)(kk));
Therefore, there are three issues:
-step 1: this is much work (adding everything before removing most of it) for a rather simple operation. Moreover, this isn't recursive and second level is "manually forced".
-step 2, a recursive solution is probably a better one.
-May step 1 and 2 be performed in a single step (starting to count when a new property is added)?
I would welcome any idea.
The JSON.parse reviver and JSON.stringify replacer can be used to check all key value pairs :
var counts = {}, json = `[{"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4]]]}},{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[]}} ]`
var features = JSON.parse(json, (k, v) => (isNaN(k) && (counts[k] = counts[k] + 1 || 1), v))
console.log( counts, features )
Consider trying the following. It is just one reduce, with a couple of nested forEach's inside. It checks whether the keys for indicating the count exist in the object to be returned, and if not creates them initialized to 0. Then whether those keys existed or not to begin with, their corresponding values get incremented by 1.
Repl is here: https://repl.it/#dexygen/countobjpropoccur2levels , code below:
const features =
[ {"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},
{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},
{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4]]]}},
{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[]}}
];
const featuresCount = features.reduce((count, feature) => {
Object.keys(feature).forEach(key => {
count[key] = count[key] || 0;
count[key] += 1;
Object.keys(feature[key]).forEach(key2 => {
let count2key = `${key}.${key2}`;
count[count2key] = count[count2key] || 0;
count[count2key] += 1;
});
});
return count;
}, {});
console.log(featuresCount);
/*
{ properties: 4,
'properties.name': 4,
'properties.zip': 3,
geometry: 4,
'geometry.type': 4,
'geometry.coordinates': 4,
'properties.updated': 1 }
*/
Use polymorphic serialization of json using jackson. It will look something like below. Your base interface will have all common properties and for each variation create sub types. Count on each type will give what you need
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="name") #JsonSubTypes({ #JsonSubTypes.Type(value=Lion.class, name="lion"), #JsonSubTypes.Type(value=Tiger.class, name="tiger"), }) public interface Animal { }

How to create a prefilled array of defined length? [duplicate]

This question already has answers here:
Strange behavior of an array filled by Array.prototype.fill()
(9 answers)
Closed 3 years ago.
I am trying to create an array which has a defined length and fill it with empty arrays. I've tried using all possibilities given by #stpoa's answer here but my array does not behave correctly.
Given the code (I simplified it for the sake of example):
const tasksArray = Array(3).fill([])
const tasksArray2 = [[], [], []]
const tasks = ['task1', 'task2']
const fillWithData = (array) => {
tasks.forEach(task => {
array[0].push(task)
})
}
Gives me an incorrect output for tasksArray and a obviously a correct one for tasksArray2 which is hardcoded
fillWithData(tasksArray) // [['task1', 'task2'], ['task1', 'task2'], ['task1', 'task2']] => not OK, duplicates values!
fillWithData(tasksArray2) // [['task1', 'task2'], [], []] => that's OK
In taskArray, the [] you are using is passed as a reference, and the elements in taskArray all reference the same array.
In taskArray2, you have three separate empty arrays, [], each with their own reference. Therefore you do not get duplicated values.
If you wish to create an array of empty arrays programmatically, use Array.from -
const fillEmptyArrays = (count) =>
Array.from(Array(count), _ => [])
const tasks =
fillEmptyArrays(3)
console.log(tasks)
// [ [], [], [] ]
And please don't include type names like Array in your variable names tasksArray; just name it tasks. JavaScript is a dynamically-typed language and this kind of thinking hurts you in the long run.
You need to get independent object references inside of the array, instead of having literally the constant value.
By taking Array.from with an object with a length property and the build in mapping function, you could get an array of independent arrays.
const tasksArray = Array.from({ length: 3 }, _ => [])
const tasks = ['task1', 'task2']
const fillWithData = (array) => {
tasks.forEach(task => {
array[0].push(task)
})
};
fillWithData(tasksArray);
console.log(tasksArray);
fill puts the value you pass it at each index of the array.
So tasksArray has three references to the same array, while tasksArray2 has a reference to each of three different arrays.
If you want to put three different arrays in there, then you need to explicitly create three arrays.
You could approach it with a counter:
const tasksArray2 = [];
let count = 3;
while (count--) {
tasksArray2.push([]);
}

Chaining methods on array display as undefined [duplicate]

This question already has answers here:
How can I create a two dimensional array in JavaScript?
(56 answers)
Closed 5 years ago.
const array = new Array(9).fill([]).forEach(function(value, index, arr) {
arr[index] = Array(9).fill(0);
console.log(index, arr[index]); // This line report properly by creating
}); // new array.
console.log(array); // Reported as underdefined.
However, if redefine as below, it works as expected.
const array = new Array(9).fill([]);
array.forEach( function(value,index,arr){
arr[index] = Array(9).fill(0);
console.log(index,arr[index]);
});
console.log(array);
I would like to define multiple dimensional arrays within one line used as this state command.
But what is the problem for scenario 1 where bounded forEach methods array definitions work fine?
But what is the problem for scenario 1 where bounded forEach methods
array defnitions works fine.
Problem is forEach returns undefined
I woule like to define multiple dimensional arrays within one line
used as this.state command.
You can use map for the same
var output = new Array(9).fill([]).map( function(value,index,arr){
return Array(9).fill(0); //return the inner array
});
Or as #bergi suggested, you can use Array.from as well
var output = Array.from(Array(9)).map( function(value,index,arr){
return Array(9).fill(0);
});
As other answers have already mentioned Array.forEach returns undefined or does not return anything, you cannot use it.
As an alternate approach, you can use Array.from.
Following is a sample:
var output = Array.from({
length: 9
}, () => {
return Array.from({
length: 9
}, () => 0)
});
console.log(output)
Also, one more issue with your code is, you are using an object in Array.fill. What array.fill does is, it will create the value to be filled first and then fill it in all items.
var output = new Array(9).fill([]);
output[0].push(1);
console.log(output)
In you check the output, it says /**ref:2**/, but if you check in console, you will notice that all items will have item 1. So you should avoid using objects with Array.fill.
You want to generate array of 9 arrays filled with 0 and return it to variable.
You can achieve this using map method.
const array =
new Array(9)
.fill([])
.map(function(){
return Array(9).fill(0);
});
console.log(array);
p.s. no value, index and etc needed to pass to map methods argument in Your case
Array.prototype.forEach returns undefined. Just call the forEach like below.
const array = new Array(9).fill([])
array.forEach( function(value,index,arr){
arr[index] = Array(9).fill(0);
});
// [
// [0, 0, 0, ...],
// ...
// ]
Using map would be another choice, but I don't vote for it because it creates yet another array in memory.

Sort Javascript Object by Value [best practices?] [duplicate]

This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 5 years ago.
Since there's no official way to sort an object by values, I'm guessing you either (1) Use an array instead or (2) Convert your object to an array using Object.entries(), sort it, then convert back to an object. But option (2) is technically unsafe since Javascript objects aren't supposed to have order.
Now I have a React app where I'm using Redux. I'm storing my data not as an array but as an object iterated by id values. This is what Redux suggests, and I would do it anyways, because of lookup times. I want to sort this redux data, so what I'm currently doing is option (2) of converting to array and then back to object. Which I don't really like.
My question is: Is this what everyone else does? Is it safe to sort an object?
Example:
const sortObject = (obj) => {
//return sorted object
}
var foo = {a: 234, b: 12, c: 130}
sortObject(foo) // {b: 12, c:130, a:234}
this is what I'm currently doing.
My object data structure looks something like this
obj = {
asjsd8jsadf: {
timestamp: 1234432832
},
nsduf8h3u29sjd: {
timestamp: 239084294
}
}
And this is how I'm sorting it
const sortObj = obj => {
const objArray = Object.entries(obj);
objArray.sort((a, b) => {
return a[1].timestamp < b[1].timestamp ? 1 : -1;
});
const objSorted = {};
objArray.forEach(key => {
objSorted[key[0]] = key[1];
});
return objSorted;
};
If you are using the Redux documentation for reference you should also have an array with all of the id's in it. Wouldn't it be easier to just sort that array and then use insertion sort when you add something to the state. Then you could use the sorted array to access the byId property of the state?

Categories

Resources