What is empty inside an array? e.g. [empty X 5] - javascript

Sorry for misleading title here, I wasn't able to frame any proper one.
I am confused in array when there is nothing inside them (prints by empty X n) but they have length.
e.g. I create array by const a = [,,,]. This creates an array whose length is 3 but nothing inside it. If i print it in browser console, it prints the following:
What does empty mean here? If I run map or forEach function and try to console something, I get nothing.
Have added some code.
const a = [,,,]
console.log("print a: ",a)
console.log("print a.length: ",a.length)
console.log("print typeof a[0]: ", typeof a[0])
console.log("a.forEach((data, index) => { console.log(data, index) }): ", a.forEach((data, index) => { console.log(data, index) }))
console.log("")
const b = [undefined, undefined, undefined]
console.log("print b: ", b)
console.log("print b.length: ", b.length)
console.log("print typeof b[0]: ", typeof b[0])
console.log("b.forEach((data, index) => { console.log(data, index) }): ", b.forEach((data, index) => { console.log(data, index) }))
console.log("")
console.log("compare a[0] and b[0]: ", a[0] === b[0])
The only thing which differs is when I print a and b (though stackoverflow console prints them same but browser console prints differently) and when I try to loop through the array. Also momentjs isEqual gives them equal (jsfiddle here)
My main doubts are:
What type of array is it?
What does empty mean here?
How is it different from array which has all undefined values or empty array? or is it not?
Do we use it or any sample use case for this one
I have read about null and undefined array values and have understood it. But for this one, I haven't find anything proper. Most of the search I found were related to const a = [] is an empty array or how to check if array is empty and so on.
So, if someone can explain or give any proper links to read, it will be very helpful.
Please let me know, if I should add anything else.

Intro to sparse arrays
First a clarification what you've created is called a sparse array. To put it simply, sparse arrays are similar to normal arrays but not all of their indexes have data. In some cases, like JavaScript, this leads to slightly more significant handling of them. Other languages simply have a normal array of fixed length with some values that are "zero" in some sense (depends on what value can signify "nothing" for a specific array - might be 0 or null or "", etc).
Empty slots
The empty slot in a sparse array is exactly what it sounds like - slot that is not filled with data. JavaScript arrays unlike most other implementations, are not fixed size and can even have some indexes simply missing. For example:
const arr = []; // empty array
arr[0] = "hello"; // index 0 filled
arr[2] = "world"; // index 2 filled
You will get an array with no index 1. It's not null, nor it's empty, it's not there. This is the same behaviour you get when you have an object without a property:
const person = {foo: "hello"};
You have an object with a property foo but it doesn't have, for example, a bar property. Exactly the same as how the array before doesn't have index 1.
The only way JavaScript represents a "value not found" is with undefined, however that conflates
"the property exists and the value assigned to it is undefined"
"the property does not exist at all"
Here as an example:
const person1 = { name: "Alice", age: undefined };
const person2 = { name: "Bob" };
console.log("person1.age", person1.age);
console.log("person2.age", person2.age);
console.log("person1.hasOwnProperty('age')", person1.hasOwnProperty('age'));
console.log("person2.hasOwnProperty('age')", person2.hasOwnProperty('age'));
You get undefined when trying to resolve age in either case, however the reasons are different.
Since arrays in JavaScript are objects, you get the same behaviour:
const arr = []; // empty array
arr[0] = "hello"; // index 0 filled
arr[2] = "world"; // index 2 filled
console.log("arr[1]", arr[1]);
console.log("arr.hasOwnProperty(1)", arr.hasOwnProperty(1));
Why it matters
Sparse arrays get a different treatment in JavaScript. Namely, array methods that iterate the collection of items will only go through the filled slots and would omit the empty slots. Here is an example:
const sparseArray = []; // empty array
sparseArray[0] = "hello"; // index 0 filled
sparseArray[2] = "world"; // index 2 filled
const arr1 = sparseArray.map(word => word.toUpperCase());
console.log(arr1); //["HELLO", empty, "WORLD"]
const denseArray = []; // empty array
denseArray[0] = "hello"; // index 0 filled
denseArray[1] = undefined; // index 1 filled
denseArray[2] = "world"; // index 2 filled
const arr2 = denseArray.map(word => word.toUpperCase()); //error
console.log(arr2);
As you can see, iterating a sparse array is fine, but if you have an explicit undefined, in the array, then word => word.toUpperCase() will fail because word is undefined.
Sparse arrays are useful if you have numerically indexed data that you want to run .filter, .find, .map, .forEach and so on. Let's illustrate again:
//some collection of records indexed by ID
const people = [];
people[17] = { id: 17, name: "Alice", job: "accountant" , hasPet: true };
people[67] = { id: 67, name: "Bob" , job: "bank teller", hasPet: false };
people[3] = { id: 3 , name: "Carol", job: "clerk" , hasPet: false };
people[31] = { id: 31, name: "Dave" , job: "developer" , hasPet: true };
/* some code that fetches records */
const userChoice = 31;
console.log(people[userChoice]);
/* some code that transforms records */
people
.map(person => `Hi, I am ${person.name} and I am a ${person.job}.`)
.forEach(introduction => console.log(introduction));
/* different code that works with records */
const petOwners = people
.filter(person => person.hasPet)
.map(person => person.name);
console.log("Current pet owners:", petOwners)

its just what it is empty its neither undefined or null
const a = [,,,,] is same as const a = new Array(4)
here a is an array with no elements populated and with only length property
do this, let arr1 = new array() and then console.log(arr1.length) you'll get 0 as output. and if you do console.log(arr1) you'll get [ <4 empty items> ]
if you change the length property of arr1 like this arr1.length = 4 you will have an empty array with it's length property = 4, but no items are populated so those slot will be empty and if you do console.log(typeof(arr1[0]) you get undefined only because there is no other possible types to show. And no methods of Array will be applied with an array with empty elements
so,
Empty array means an array with length property and with unpopulated slots
this is different from arrays with undefined because in JS undefined is a type and you can execute and have results by calling all array methods on it, whereas an array with empty elememts have no type and no array methods can be applied on it.

Related

How to filter array of arrays of array of objects?

I am trying to filter an array of arrays of objects, but it does not work.
constructNewGrid(filterText){
if(searchText == ""){
this.constructedGrid = this.fullGrid;
return;
}
this.constructedGrid = [];
this.constructedGrid = this.fullGrid.filter(array => array.filter(obj => {
if(obj.name == filterText)
return obj;
}));
console.log(this.constructedGrid);
}
I want to return an array of arrays with the correct object if found one but when I console log it constructedGridis the same array but why?
The code should go to the first array should look up whether there is an object with the name equals to the filter text it should return the array with the corrosponding object and then the next array should be checked and so on.
This, would be the format:
[
[[{name: string}], empty, empty], // array of lenth 3
[empty, empty, [{name: string}], empty], // array of length 4
...
]
If one object is found it should push it in an array separately such that if two objects where found in the same array, it should put them both in separately push them in two separat arrays and those should be in one single array:
Result should be
[
[[obj1]],
[[obj2]],
...
]
It seems in possible for me. I got sometime an error that I am out of memory haha...
You'll need map in addition to filter, because filter just decides whether to keep what's there, it doesn't change what's there. map does. Something like this:
this.constructedGrid = this.fullGrid
// Map the inner array to one that only has matching entries
.map(array => array.filter(obj => obj && obj.name === filterText))
// Remove blank inner arrays from the overall array
.filter(array => array.length);
Live Example:
const fullGrid = [
[[{name: "target"}], , ],
[, null, [{name: "target"}], undefined],
];
const filterText = "target";
const constructedGrid = fullGrid
// Map the inner array to one that only has matching entries
.map(array => array.filter(obj => obj && obj[0] && obj[0].name === filterText))
// Remove blank inner arrays from the overall array
.filter(array => array.length);
console.log(constructedGrid);
.as-console-wrapper {
max-height: 100% !important;
}
Note that you only need that second filter if you want to remove arrays that are completely empty from the outer array. If you want to leave them, just remove that call. Edit: From your reply to my question on the question, it sounds like you want to remove that second .filter.
Note the guard in the first filter, the obj && obj[0] && part. It's there because you said sometimes array entries are "empty." I don't know if you literally meant empty — a sparse array — or entries that are undefined. If you literally meant empty (a sparse array), you don't need the guard, but it's probably best to have it.
As of ES2020, you could use the optional chaining operator instead:
.filter(obj => obj?.[0]?.name === filterText)
obj?.[0]?.name evaluates to undefined if obj is null or undefined, or obj[0] is null or undefined; it evaluates to obj[0].name otherwise. Since undefined === filterText will be false, entries with a null or undefined obj will be left out. Again, though, new feature, you'll need to check support on your target browsers if you're not transpiling.

Possible to push empty slot to an array?

I'm building my own map method to be as close as the native map method.
Since the native map pushes(i think) the changed values into a new array, it still keeps the empty slots. I wasn't able to find a solution to push an empty slot into an array, like this example below.
[1, 2, 3].push(some code) // [1, 2, 3, empty]
I tried pushing an array with one empty item prefixed with a spread operator arr.push(...(new Array(1))) or arr.push(...[,]) but that just pushes undefined.
I solved my problem by not using push and instead assigning values to the array index that way skipped indices will be set to empty.
But I'm writing this post to see if anyone knows that if it's possible to use the push method to push an empty slot to an array.
No, it's not possible, not with the push method. empty can only exist if the array has a certain length, but a whole number property of the array does not exist at some index. This is called a sparse array, and cannot be created with push (or other array methods, if they're called on and with non-sparse arrays).
The only way to do so would be to assign to an index for which a lower index doesn't exist yet.
Look at the results for the below two snippets in your browser console, not the snippet console:
const arr = [];
arr[1] = 'a';
console.log(arr);
Or to set the .length of the array above the last index that the array has:
const arr = [];
arr.length = 1;
console.log(arr);
But the two approaches above are very weird to do and probably have no good reason to be used. Better to avoid sparse arrays entirely.
Keep in mind that an empty slot is different from undefined, which is perfectly possible to have as an array value:
const arr = [];
arr.push(undefined);
console.log(arr);
You can create an empty slot in an array by incrementing the array length:
var a = []
a.push(1)
a.length++
a.push(3)
console.log(a)
console.log(1 in a) // anything at index 1?
Alternatively, you can push something and then delete it:
var a = []
a.push(1)
a.push(2)
a.push(3)
delete a[1]
console.log(a)
console.log(1 in a) // anything at index 1?
There is no need to actually push to a new array in your implementation. You can simply do new Array(this.length) where this.length is the array you are mapping through length.
For example consider this map implementation:
if (!Array.prototype.mapIt) {
Object.defineProperty(Array.prototype, "mapIt", {
value: function(fn) {
if (this === null) {
throw new TypeError('Array.prototype.mapIt called on null or undefined');
}
if (typeof fn !== 'function') {
throw new TypeError('predicate must be a function');
}
let _array = this.filter(x => x != null) // remove empty values
let result = new Array(_array.length) // the new array we will return
for (var i = 0; i < _array.length; i++) {
result[i] = fn.call(arguments[1], _array[i], i, _array) // call the predicate
}
return result;
}
});
}
let arr = [1, 2, , , 3] // the test array
let result = arr.mapIt((c, i, a) =>
console.log(`current: ${c}`, `index: ${i}`, `array: ${a}`) || c + 2)
console.log('result: ', result)
console.log('original array: ', arr)
Hope this helps you with an gives you an idea about a possible map implementation.

How to edit value of an object using the reference?

I have a big object array persons
persons = [{name:'john1'}, {name:'john2'},...]
I iterated the array and found the object I am interested to edit
objectToEdit = persons .find((person)=>person.name==='john1')
Now I created an edited object in immutable way (someOtherPerson = {name:'johnxx'})
objectFinal = {...objectToEdit, someOtherPerson}
Now I want to replace this objectFinal with objectToEdit in persons array, without having to traverse the array again. But doing objectToEdit =objectFinal , will just assign objectToEdited's reference to objectToEdit , without making any change in the persons array
Is there a clean way to achieve this without traversing the array?
Edit:
In this example, the object in persons jave just one key (i.e, name). This is to make question minimal. In my project, I have more than 30 keys.
If you want to edit an object in a list,in place, use Array.prototype.some
var persons = [{
name: 'john1'
}, {
name: 'jack5'
}]
var someOtherPerson = {
name: 'johnxx'
}
persons.some(function(person) {
// if condition, edit and return true
if (person.name === 'john1') {
// use Object.keys to copy properties
Object.keys(someOtherPerson).forEach(function(key) {
person[key] = someOtherPerson[key]
})
// or use assign to merge 2 objects
Object.assign(person, someOtherPerson);
return true // stops iteration
}
})
console.log(JSON.stringify(persons))
If you want to avoid mutating the objects in the original array, you might use .findIndex instead, and reassign the item in the array at that index:
const persons = [{name:'john1'}, {name:'john2'}];
const objectToEditIndex = persons.findIndex((person) => person.name === 'john1');
const someOtherPerson = {name:"johnxx"};
persons[objectToEditIndex] = {
...persons[objectToEditIndex],
...someOtherPerson
};
console.log(persons);
Doing this:
objectFinal = {...objectToEdit, someOtherPerson}
you lose the reference to the original objectToEdit object. To edit it, you can do
`objectToEdit.value = otherValue`.
PS: Having said that, you only lose reference to the first level object. If that object, contains another object or array, you have reference to it:
objectFinal.innerObject.value = otherValue;
{name:'john1'} should be replace with {name:"johnxx"} in persons
Reassign persons to the persons-array mapped with the new value:
let persons = [{name:'john1'}, {name:'john2'}];
persons = persons.map( v => v.name === "john1" ? {name: "johnxx"} : v );
console.log(persons);
Or, if you're worried about performance, use Array.splice in combination with Array.findIndex
The findIndex method executes the callback function once for every
array index 0..length-1 (inclusive) in the array until it finds one
where callback returns a truthy value (a value that coerces to true).
If such an element is found, findIndex immediately returns the index
for that iteration.
let persons = [{name:'john1'}, {name:'john2'}];
persons.splice(persons.findIndex( v => v.name==="john1" ), 1, {name:'johnxx'});
console.log(persons);
Or use a utility method (snippet for a large array (1,000,000 elements) and with performance timer)
// replacer method
const replace = (obj, [key, value], replacement) => {
obj.splice(persons.findIndex( v => v[key] === value ), 1, replacement);
return obj;
}
// create a large array (this may take some extra time)
let persons = Array.from({length: 1000000}).map(v =>
({name: Math.floor(100000 + Math.random() * 1000000000).toString(16)}) );
// pick an entry
const someEntry = Math.floor(persons.length * Math.random());
const entry = Object.entries(persons[someEntry])[0];
console.log("before:", persons[someEntry]);
const t = performance.now();
// replacement call here
console.log("after:", replace(persons, entry, {name: "johnxx"})[someEntry]);
console.log("replacement took:", `${((performance.now() - t)/1000).toFixed(3)} sec`);

Forcing javascript key integer fills array with null

I am creating an array in Javascript where the product id is used for the key. As the key is numeric, the array is filling gaps with null.
So for example, if I had only two products and their ids were 5 and 7, I would do something like:
var arr = []
arr[5] = 'my first product';
arr[7] = 'my second product';
This array is then passed to a PHP script but upon printing the array, I get the following;
Array (
[0] = null
[1] = null
[2] = null
[3] = null
[4] = null
[5] = My first product
[6] = null
[7] = My second product
)
my ID numbers are actually 6 digits long, so when looping over the array, there are 100,000 iterations, even if I actually only have two products.
How can I create the array so the null values are not entered? I thought of making the key a string instead but as the array is build dynamically, I am not sure how to do that.
var arr = [];
for(var i=0; i<products.length; i++)
{
array[products[i].id] = products[i].name;
}
Thanks
For iterating the array, you could use Array#forEach, which skips sparse items.
var array = [];
array[5] = 'my first product';
array[7] = 'my second product';
array.forEach(function (a, i) {
console.log(i, a);
});
For better organisation, you could use an object, with direct access with the given id.
{
5: 'my first product',
7: 'my second product'
}
Forcing javascript key integer fills array with null
You are declaring an empty array and then setting values into 6th and 8th element in the array. That leaves the values of other elements as null.
If you don't intend to push items into array, i.e. use objects. Something like,
var obj = {};
obj[5] = "my first product";
obj[7] = "my second product";
This means the object created is:
obj = {"5":"my first product","7":"my second product"}
var arr = []
arr[5] = 'my first product';
arr[7] = 'my second product';
let result = arr.filter(ar => ar != null);
console.log(result);

javascript / es6 - how to get index of first object value whos array length is greater than 0

I have an object which is full of arrays:
const errors = { name: [], date: ['invalid format'], ... }
I want to find the index (or object key, if I can't get an index) of the first value in the errors object where the array length is greater than one. So in the example above, the date array is the first array in the object that has a length, so I would just return, ideally, 1, or date if necessary.
Anybody know the most concise / fastest way to do this in javascript / es6?
You can use find() on Object.keys() and it will return first result that matches condition or undefined.
const errors = { name: [], date: ['invalid format']}
var result = Object.keys(errors).find(e => errors[e].length);
console.log(result)
JavaScript objects have no inherent order to their properties, so if an index is truly salient you probably want to use an array instead.
At that point it's just something like errors.findIndex(e => e.length > 1), adjusted as you see fit.
You can use for ..in to loop through the object and Object.prototype.toString to check if the value is an array.
Also to find the index you may need to use Object.keys which will create an array of keys from the object. Js Object does not have index
const errors = {
name: [],
test: 1,
date: ['invalid format'],
test2: 2
}
for (var keys in errors) {
if (Object.prototype.toString.call(errors[keys]) === '[object Array]' && errors[keys].length > 0) {
console.log(errors[keys])
}
}

Categories

Resources