How can i understand this topic better, to be a better Programmer - javascript

i have a Question. I was thinking long Time about it, but poorly i donĀ“t find a answer.
I know the every method.
My Question is about this code section:
var tr = order.every((i) => stock[i[0]] >= i[1]);
My Questions are:
stock is an Object. Why i must write as an array?
Why it is i[0] in stock and then i[1] ?
Why this code checks the nested Arrays in const order ?
const order = [
["shirt", 5],
["shoes", 2]
];
const stock = {
shirt: 50,
height: 172,
mass: 120,
shoes: 6
};
var tr = order.every((i) => stock[i[0]] >= i[1]); /// return true
console.log(`tr:`,tr)

So, the square brackets can be used to access element inside the array by passing it's index e.g:
const arr = ["first", "second"];
const secondElement = arr[1] // index 1 means seconds element
and also square brackets can be used to access element inside the object by passing it's key e.g:
const obj = { first: 1, second: 2 };
const secondElement = object.second // Normal way to access value in object
const secondElementWithAnotherSyntax = object['second'] // another syntax, same thing
the cool thing about the other syntax shown is that you can pass variable to it, e.g :
const objKey = 'second'
const secondElement = obj[objKey]
Now let's look at your example, i is one element of the array order, which carries arrays itself, so i is also one of the two small arrays, i[0] is the string word in the beginning of the small arrays, so:
i[0] // is either 'shirt' or 'shoes'
and since stocks is an object that has those keys, you can access for example the value 50 by saying stocks['shirt'] or as in your case, stock[i[0]] ;)
now your second question: why should it be >= i[1] ?
because the order second item , aka i[1] is the number of items required/ordered, so this should always be less that your stock, you can't by 5 shirts from a place that has only 3 in the stock :)

1. stock is an Object. Why i must write as an array?
You can access properties of objects using brackets [].
Why do we need this?
To be able to access properties of objects dynamically, e.g. when you are looping though keys and want to get the values
Object.keys(data).forEach(function(key) {
console.log('Key : ' + key + ', Value : ' + data[key])
})
Sometimes there is no other way to access the value:
const json = {
"id":"1",
"some key with spaces": "48593"
};
console.log(json.some key with spaces); // obviously throws error
console.log(json['some key with spaces']); // prints "48593"
2. Why it is i[0] in stock and then i[1] ?
3. Why this code checks the nested Arrays in const order ?
The code goes through the orders, each order is an array so i[0] is the type of the order and i[1] is the quantity. the code checks if there are enough items in stock. To check if there are enough shirts you would do:
console.log(stock["shirts"] >= 5
Thats what the code in your example does, it just passes the key ("shirts") and quantity (5) dynamically.

May I suggest to try to use more expressive naming of the variables ?
An object property can be accessed through bracket notation, as in stock[orderedProductName] when using a variable - Property accessors
A concise but imho more readable version can be written using destructuring assignment
const order = [
["shirt", 5],
["shoes", 2]
];
const stock = {
shirt: 50,
height: 172,
mass: 120,
shoes: 6,
};
// original version
let inStock = order.every((i) => stock[i[0]] >= i[1]); /// return true
// more verbose version
// check if every item in array order satisfies the condition
// let's cycle over the array calling the element we're working on
// orderItem
inStock = order.every( orderItem => {
const orderedProductName = orderItem[0];
const orderedProductQuantity = orderItem[1];
// to access an object property we can use bracket notation
const stockProductQuantity = stock[orderedProductName];
// the condition to check: do we have enough products in stock ?
return stockProductQuantity >= orderedProductQuantity;
});
// a concise variation could make use of destructuring assignment.
// Here, when we take the order item array, we immediately assign
// each of its elements to the appropriate variable
//
// orderItem[0] or first array element -> productName
// orderItem[1] or second array element -> orderedQuantity
inStock = order.every(([productName, orderedQuantity]) =>
stock[productName] >= orderedQuantity
);
if(inStock) {
console.log('pack and ship');
}
else {
console.log('need to restock');
}

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. If you want to read more Array.prototype.every()
In your code snippet you are checking that every item in order array has quantity less than the quantity available in stock.
To access the properties of a object you can use square notation also like arrays. To read more Bracket Notation

If you assigned more meaningful variables to the code you'd probably understand how this works better.
In one order (an array) we have two nested arrays. The first describes shirt/value, the other shoes/value. every is going to see if there is enough stock for both shirt and shoes by checking that the stockValue >= the items in the order.
When you every over the order array the callback for each iteration is one orderItem (['shirt', 5] first, then ['shoes', 2] for the second). We can assign the first element of each array to a variable called itemType, and the second to a variable called itemQty.
So when you see stock[i][0] we can translate that in the new code as stock[orderType] which is using bracket notation to locate the property value associated by that key in the stock object. We then check to see if that value >= than the itemQty.
const order=[["shirt",5],["shoes",2]],stock={shirt:50,height:172,mass:120,shoes:6};
const result = order.every(orderItem => {
const itemType = orderItem[0];
const itemQty = orderItem[1];
return stock[itemType] >= itemQty;
});
console.log(result);

Related

There is a very unusual output I got to see in Javascript loops while working with array [duplicate]

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.

JS reduceRight is causing arrays to become objects, how can I fix the logic?

I have an json object:
json = {
"array": [80, 50]
}
A third party jsoneditor, returns a path variable as a list of strings or index to the value. I use event listeners to detect changes to the value and I store those changes in a separate object to later submit to a form.
For example, if I change 50 to 60, this is what is returned:
node.path = ["array", 1]
node.value = 60
I am using reduceRight() to normally map the changes to the object to the changed object, however when the value is an Array it is converting the array to an object.
let delta = node.path.reduceRight((obj, elem) => ({[elem]: obj}), node.value)
//returns
delta = {array: {1: 60}}
//instead of
delta = {array: [80, 60]}
How can I check the original json and if the field is an Array object, don't reduce this into an object but keep the original Array and update the Array with the changed value within the Array? I need to submit the entire array into the form.
EDIT:
I know that I can do some custom logic within the reduce to check the original json and get the unchanged part of the array. I just dont know how to achieve that
let delta = node.path.reduceRight( function (obj, elem) {
//some logic here to account for arrays and get original array list and change only slected index
else {
return {[elem]: obj}), node.value
}
});
EDIT:
Alternatively, how can can I get the nested keys from the node.path and find the original array and then just update the array index? The path can sometimes be nested sothe path is always going to be the Array-like structure.
//this does not work
let orig_key_val = json[node.path]
Thanks!
This seems to work, finally... I used a regular reduce() and check if the key is an Array if it is store my original Array into a tmp object and save that key. The next time the reduce() comes around, if the index is on the last path element then set the tmp[tmp_key][key] to my target value and return the adjusted for Array object instead.
I can see how this will not work for nested json objects but for now I dont have any... Unless someone can show me how to figure that one out this is my implementation for now.
let tmp = {};
let tmp_key;
let delta = node.path.reduce((val, key, index, array) => {
if (Array.isArray(json[key])) {
tmp[key] = json[key]
tmp_key = key;
} else if (Object.keys(tmp).length !== 0 && index === node.path.length-1) {
tmp[tmp_key][key] = node.value;
return tmp
} else
return {[key]: val}
}, node.value);

Is it bad to have "number strings" as object keys?

I have two APIs to work with and they can't be changed. One of them returns type like this:
{
type: 25
}
and to other API I should send type like this:
{
type: 'Computers'
}
where 25 == 'Computers'. What I want to have is a map of numeric indices to the string value like this:
{
'1': 'Food',
'2': 'Something',
....
'25': 'Computers'
....
}
I am not sure why, but it doesn't feel right to have such map with numeric value to string, but maybe it is completely fine? I tried to Google the answer, but couldn't find anything specific. In one place it says that it is fine, in another some people say that it's better not to have numeric values as object keys. So, who is right and why? Could somebody help me with this question?
Thanks :)
There's nothing wrong with it, but I can understand how it might look a little hinky. One alternative is to have an array of objects each with their own id that you can then filter/find on:
const arr = [ { id: 1, label: 'Food' }, { id: 2, label: 'Something' }, { id: 25, label: 'Computers' } ];
const id = 25;
function getLabel(arr, id) {
return arr.find(obj => obj.id === id).label;
}
console.log(getLabel(arr, id));
You can use the Map object for this if using regular object feels "weird".
const map = new Map()
map.set(25, 'Computers');
map.set(1, 'Food');
// then later
const computers = map.get(25);
// or loop over the map with
map.forEach((id, category) => {
console.log(id, category);
});
Quick Update:
As mentioned by others, using objects with key=value pairs is OK.
In the end, everything in javascript is an object(including arrays)
Using key-value pairs or Map has 1 big advantage( in some cases it makes a huge difference ), and that is having an "indexed" data structure. You don't have to search the entire array to find what you are looking for.
const a = data[id];
is nearly instant, whereas if you search for an id in an array of objects...it all depends on your search algorithm and the size of the array.
Using an "indexed" object over an array gives much better performance if dealing with large arrays that are constantly being updated/searched by some render-loop function.
Map has the advantage of maintaining the insertion order of key-value pairs and it also only iterates over the properties that you have set. When looping over object properties, you have to check that the property belongs to that object and is not "inherited" through prototype chain( hasOwnProperty)
m = new Map()
m.set(5, 'five');
m.set(1, 'one');
m.set(2, 'two');
// some other function altered the same object
m.__proto__.test = "test";
m.forEach((id, category) => {
console.log(id, category);
});
/*
outputs:
five 5
one 1
two 2
*/
o = {};
o[5] = 'five';
o[1] = 'one';
o[2] = 'two';
// something else in the code used the same object and added a new property
// which you are not aware of.
o.__proto__.someUnexpectedFunction = () => {}
for (key in o) {
console.log(key, o[key]);
}
/*
Output:
1 one
2 two
5 five
someUnexpectedFunction () => {}
*/
Map and objects also have 1 very important advantage(sometimes disadvantage - depending on your needs ). Maps/objects/Sets guarantee that your indexed values are unique. This will automatically remove any duplicates from your result set.
With arrays you would need to check every time if an element is already in the array or not.

check if each value in array matches any value in another array using indexOf in google apps scripts

Not sure what I'm doing wrong.
function ccheck(){
var tkhContacts = SpreadsheetApp.openById('##').getSheetByName('contacts');
var emf = ContactsApp.getContactGroup('emf').getContacts();
var fullNames = emf.map( function(contact){ return contact.getFullName() } );
var tkhContacts = tkhContacts.getRange('B2:B').getValues();
for(var i=0;i<fullNames.length;i++){
if(fullNames[i].indexOf(tkhContacts) == -1){
Logger.log('missing')}
}
}
Trying to put all Google contacts in group 'emf' into an array. Then taking contact names stored in column B in sheet and putting that in an array. Then take each name in the 'fullNames' array and check if it matches any of the names in 'tkhContacts' from the sheet. If a name in 'fullNames' does not match any name in 'tkhContacts' set value as false.
I think that you are using the wrong indexOf method. It looks like you are using the String.prototype.indexOf() rather than Array.prototype.indexOf().
This should work for your code, but it is hard to test without any data.
const a = ['Sally', 'Walker', 'Claire', 'Lilly'];
const b = ['Kyle', 'Sally', 'Walker', 'Caroline', 'Claire'];
const d_hash = {};
const d_list = [];
a.forEach(a => {
const i = b.indexOf(a);
if (i === -1) {
// the name is missing
d_hash[a] = {
status: 'missing',
index: null
};
d_list.push(a);
} else {
// the name has been found
d_hash[a] = {
status: 'found',
index: i
}
}
});
console.log(d_hash);
console.log(d_list);
The logic:
I have two arrays of names, array a and array b. I want to find the names that appear in a but not in b.
For each value of a, try to find the index of the element in b. If the index is -1, we know the element could not be found.
Store the results as a hash and also the list of names that could not be found in an array.
JS Bin
Alternatively
What you are really wanting the do is find the difference of Set a and Set b.
A Set is a data structure that contains many elements and an element can only appear once it each set.
And a difference is also known as find the complement of a set.
We can convert each array to a set and then perform a difference to get the elements that appear in one but not the other.
const a = ['Sally', 'Walker', 'Claire', 'Lilly'];
const b = ['Kyle', 'Sally', 'Walker', 'Caroline', 'Claire'];
const set_a = new Set(a);
const set_b = new Set(b);
// code adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Set.prototype.diff = function (b) {
const d = new Set(a);
b.forEach(el => d.delete(el));
return d;
}
console.log(Array.from(set_a.diff(set_b))); // ["Lilly"]
Clarifications:
What is forEach?
For each is a method natively provided on the Array.prototype in newer browsers. More info here.
forEach should be applied to an array, and the method expects a function callback that should handle each element.
What is (...) => { ... } ?
This represents arrow functions which are available in ES6. This arrow syntax provides an alternative (and in my opinion, cleaner and clearer) way to define functions.
Something that which was previously represented as:
function (el) {
d.delete(el);
}
Can be shortened to
(el) => d.delete(el);

Javascript - how to detect difference from two arrays

Sorry for the maybe misleading title.
I have two arrays, one contains the defaults and another one contains products.
What I am trying to do is compare the two so that you can add/remove as many products as you like, but you can't have less products as the default.
Lets say
default = [1,2]
products = [1,1,1,1,2,2,2,3,4,5,6,7,8,9]
this should work.
But you can't have something like this:
default = [1,2]
products = [2,2,2,3,4,5,6,7,8,9]
because at least the same amount of products in the default array is required, and in the last example, 1 is not included in the products array.
I am using this to compare the two arrays:
Array.prototype.containsArray = function ( array /*, index, last*/ ) {
if( arguments[1] ) {
var index = arguments[1], last = arguments[2];
} else {
var index = 0, last = 0; this.sort(); array.sort();
};
return index == array.length
|| ( last = this.indexOf( array[index], last ) ) > -1
&& this.containsArray( array, ++index, ++last );
};
arr1.containsArray( arr2 )
which works. In my function (the one used to add/remove products) I tried to have the check like this:
removeDeviceToZone = function(zone, ID) {
if (products.containsArray(default) {
return products = removeFromArray(products, ID);
}
};
but the problem is that at the time the check is executed, the array is still correct, but it won't be anymore as soon as a product is removed. What's the best way to have the check prevent what the array will be after removing the item without really removing it yet? Is it even possible? is it the best approach to do this? thanks
You should use every function which accepts a callback provided method applied on every item in the array.
The every() method tests whether all elements in the array pass the
test implemented by the provided function.
function containsArray(defaultArray, products){
return defaultArray.every(function(item){
return products.indexOf(item)!=-1;
});
}
let defaultArray = [1,2]
let products = [1,1,1,1,2,2,2,3,4,5,6,7,8,9];
let products2=[2,2,2,3,4,5,6,7,8,9];
let contains=containsArray(defaultArray,products);
let contains2=containsArray(defaultArray,products2);
console.log(products.toString()+'->'+contains);
console.log(products2.toString()+'->'+contains2);
When you delete items you should check if the containsArray keeps to be true. In the other words you have to check if the containsArray function returns true after remove element.If yes, return products. Otherwise, return the old products array.
removeDeviceToZone = function(zone, ID) {
let productsCopy=products;
let products=removeFromArray(products, ID);
if (containsArray(default,products) && containsArray(default,productsCopy) {
return products;
}
else
return productsCopy;
};
Simply putting,
Clone the original array in another variable (clonedArray) and compare the two after you are done deleting.
:)
Simplify the logic:
delete whatever you're going to delete
add the default values back in after each delete operation, e.g.:
for each default, check if it's already in the array and push if not
optionally sort the result if order matters to you
That's a simple, idempotent operation.
Alternatively, create a class that has the defaults as a property and the user-selected items as a separate property, and which merges both together conditionally as needed whenever necessary, e.g. basket.getMergedBasket().
Alternatively, instead of trying to maintain two lists, make those products objects if they aren't already which have an appropriate flag, e.g.:
products = [{ id: 1, mandatory: true }, ...]
That would be a really simple object oriented approach.

Categories