I have a functions which accepts browser types as arrays in an object as argument. I want to return an error message for whether user wanted any browser or not. For this I used a variable named allTypeNumber. I used for returning error in the code below.
I want to check the length of every array and if they're all 0, I know that no browser has been requested, but confused how to do that without using a variable.
async retrievePartners (capabilities) {
const appropriatePartners = { chrome: [], firefox: [], safari: [], ie: [] }
const partners = await this.getAllPartners()
let allTypeNumber = 0
// first check if there is available appropriate Partners
Object.keys(capabilities.type).forEach(key => {
let typeNumber = parseInt(capabilities.type[key])
allTypeNumber = allTypeNumber + typeNumber
for (let i = 0; i < typeNumber; i++) {
partners.forEach((partner, i) => {
if (
key === partner.value.type &&
partner.value.isAvailable &&
appropriatePartners[key].length < typeNumber
) {
appropriatePartners[key].push(partner)
}
})
if (appropriatePartners[key].length < typeNumber) {
throw new Error(
'Sorry there are no appropriate Partners for this session'
)
}
}
})
if (allTypeNumber === 0) {
throw new Error('Please mention at least 1 type of browser !')
and I call it like with this parameter
const capabilities = { type: { chrome: 1 } }
A clean way to check if every value of an array is something, is to use every() (or its inverse, some()). every() will loop through each element in an array and continue as long as it keeps getting a truthy value returned. some() will keep going as long as it keeps getting a falsey. They both return either true or false based on their last return value.
Or, if you need a count (versus just knowing if there is at least one) you can use reduce() to create a sum. reduce() loops through an array with an accumulator and is good for creating singular values from an array of data.
Also, if you need to get the values from an object, using either Object.entries() or Object.values() can be cleaner than using keys. Object.entries() gives you an array of arrays, where each array is [key, value]. Object.values() just gives you an array of values.
Finally, for making sure the partner is appropriate, you can use partner.filter() to filter only the good partners and the bad partners.
Putting some of those together, you can easily do something like:
const total = Object.entries(capabilties.type).reduce(([key, type]) => {
typeNumber = parseInt(type);
const good = partners.filter(partner => key === partner.value.type && partner.value.isAvailable && appropriatePartners[key] < typeNumber);
appropriatePartners[key].concat(good);
if (appropriatePartners[key].length < typeNumber) {
throw new Error('No appropriate partners');
}
return typeNumber;
}, 0);
if (total === 0) {
throw new Error('Please mention at least 1 type of browser');
}
Of course, this isn't the only appropriate. I don't know which bits of your logic are simply so you can keep track and which have business logic behind them. Using some of the functions I mentioned, you can probably reduce it even further if you don't need to keep track of certain things.
Related
How do I search an array for any instances of multiple specified string values?
const arrayOfObjects = [{
name: box1,
storage: ['car', 'goat', 'tea']
},
{
name: box2,
storage: ['camel', 'fox', 'tea']
}
];
arrayOfSearchItems = ['goat', 'car', 'oranges'];
If any one or all of the arrayOfSearchItems is present in one of the objects in my array, I want it to either return false or some other way that I can use to excluded that object that is in my arrayOfObjects from a new, filtered arrayOfObjects without any objects that contained the arrayOfSearchItems string values. In this case I would want an array of objects without box1.
Here is what I have tried to do, based on other suggestions. I spent a long time on this. The problem with this function is that it only works on the first arrayOfSearchItems strings, to exclude that object. It will ignore the second or third strings, and not exclude the object, even if it contains those strings. For example, it will exclude an object with 'goat'. Once that happens though, it will no longer exclude based on 'car'. I have tried to adapt my longer code for the purposes of this question, I may have some typos.
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => {
let incrementArray = [];
let userEffects = arrayOfSearchItems;
let objects = arrayOfObjects;
for (i = 0; i < userEffects.length; i++) {
for (x = 0; x < objects.length; x++) {
if (objects[x].storage.indexOf(userEffects) <= -1) {
incrementArray.push(objects[x]);
}
}
}
return(incrementArray);
}
let filteredArray = excludeItems(arrayOfSearchItems, arrayOfObjects);
console.log(filteredArray);
Thanks for providing some example code. That helps.
Let's start with your function, which has a good signature:
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => { ... }
If we describe what this function should do, we would say "it returns a new array of objects which do not contain any of the search items." This gives us a clue about how we should write our code.
Since we will be returning a filtered array of objects, we can start by using the filter method:
return arrayOfObjects.filter(obj => ...)
For each object, we want to make sure that its storage does not contain any of the search items. Another way to word this is "every item in the starage array does NOT appear in the list of search items". Now let's write that code using the every method:
.filter(obj => {
// ensure "every" storage item matches a condition
return obj.storage.every(storageItem => {
// the "condition" is that it is NOT in the array search items
return arrayOfSearchItems.includes(storageItem) === false);
});
});
Putting it all together:
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => {
return arrayOfObjects.filter(obj => {
return obj.storage.every(storageItem => {
return arrayOfSearchItems.includes(storageItem) === false;
});
});
}
Here's a fiddle: https://jsfiddle.net/3p95xzwe/
You can achieve your goal by using some of the built-in Array prototype functions, like filter, some and includes.
const excludeItems = (search, objs) =>
objs.filter(({storage:o}) => !search.some(s => o.includes(s)));
In other words: Filter my array objs, on the property storage to keep only those that they dont include any of the strings in search.
I keep asking the same question in terms of react and not getting a clear answer, so ive decided to attempt to extrapolate conceptually what i want to do and see if i cant get some answers on how i can proceed in react.
filters= ["Bill Johnson", "hasStartDocs"]
mappedArray =
[
{
name:'Larry',
docs:{
startDocs:[{...}]
},
workers:{
sales:["Bill Johnson"]
}
]
So if filter[i] has a space{' '} check all arrays under workers for strings like filter[i] and then map
filteredMappedArray based on that
if filter[i] does not have a space create a new string slicing the first 3 chars of the string making the first letter of that new string lower case (thatString = "startDocs") eval(resoStatus.${thatString}.length > 0) then map filteredMappedArray like that.
so after that for every instance of [i] you would have a unique map. so if someone clicked 5 filters there would be a filteredMappedArray for each which i guess you would .concat() and .reduce() if they have the same _id.
I dont need someone to help with the string manipulation. I need a nudge in the right direction on how to utilize both filters [] and mappedArray [] to create 1 filteredMappedArray. please and thank you.
for(i=0,i<filters.length,i++){
filters.map(filter => filter.includes(' ') //map mappedArray based on rules above)
filters.map(filter => filter.includes(/^has/ //map mappedArray based on rules above)
}
this gives you
[filteredMappedArray1]
[filteredMappedArray2]
bigArray = filteredMappedArray1.concat(filteredMappedArray2)
smallArray = bigArray.forEach(map //if the map is unique delete it if the map isnt unique keep it but remove all the duplicates)
As best as I can make out, you aren't doing any mapping, just filtering the mappedArray to limit it to entries that match at least one filter in filters.
If so, here's how to do that (see comments) provided you don't have lots more filters than you have entries in mappedArray (if you did, you'd structure it differently, but that seems unlikely to me):
// The `length` check prevents filtering if there are no filters; that's usually how people
// implement filters, but remove it if that's not right in your case
const filteredArray = filters.length === 0 ? mappedArray : mappedArray.filter(entry => {
// `some` returns `true` if the callback returns a truthy value (and
// stops loopihng); it returns `false` if it reaches the end of the
// array without the callback rturning a truthy value. So basically
// this is saying "return true if any filter matches."
return filters.some(filter => {
if (filter.includes(" ")) {
// Check `workers` arrays
for (const array of Object.values(entry.workers)) {
if (array.includes(filter)) {
return true;
}
}
} else {
// Check `docs` for at least one entry for the given type
const key = /*...strip leading three, change case of fourth...*/;
const array = entry.docs.key];
if (array && array.length > 0) { // There's at least one entry
return true;
}
}
return false;
});
});
I have a list that has objects with a varying amount of keys. I want to make sure that I get the index from the list of the object with the most keys OR the reference to the object itself. What is the best way to do this?
My current approach is:
let index = -1;
let numKeys = 0;
for(var i = 0; i < mylistofobjects.length; i++) {
if(Object.keys(mylistofobjects[i]).length > numKeys) {
index = i;
}
}
// by the end, index has the most keys
Is there a smarter/shorter way to do this that would require less code in this day and age? If the way to get the object reference is shorter than the way to get the index number.. I would prefer the object reference.
One option is to reduce, keeping in the accumulator the object with the most keys found so far:
const objWithMostKeys = mylistofobjects.reduce((bestSoFar, thisObj) => (
Object.keys(bestSoFar).length >= Object.keys(thisObj).length ? bestSoFar : thisObj
));
It's not entirely efficient because it checks the accumulator's number of keys on every iteration, rather than caching it, but caching it will require a bit more code:
let maxKeyCount = Object.keys(mylistofobjects[0]).length;
const objWithMostKeys = mylistofobjects.reduce((bestSoFar, currObj) => {
const currKeyCount = Object.keys(currObj).length;
if (currKeyCount > maxKeyCount) {
maxKeyCount = currKeyCount;
return currObj;
}
return bestSoFar;
});
This assumes that the mylistofobjects isn't empty. If that's a possibility, probably add a .length check beforehand, and return early / throw an error (or whatever you need to do) instead of proceeding.
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.
It should be quite easy to implement array.map() that is defined in ECMA-262, which takes a function and this function will be called by 3 arguments: element value, index, the array.
But what about for sparse array? Obviously we don't want to iterate from index 0 to 100,000 if only index 0, 1, 2, and 100,000 has an element and otherwise is sparse from index 3 to 99,999. I can think of using arr.slice(0) or arr.concat() to clone the array, and then put in the replaced values, but what if we don't use slice or concat, is there another way to do it?
The solution I came up with using slice() is:
Array.prototype.collect = Array.prototype.collect || function(fn) {
var result = this.slice(0);
for (var i in this) {
if (this.hasOwnProperty(i))
result[i] = fn(this[i], i, this); // 3 arguments according to ECMA specs
}
return result;
};
(collect is used to try out the code, as that's another name for map in some language)
It should be easy, but there are a few peculiar points.
The callback function is allowed to modify the array in question. Any elements it adds or removes are not visited. So it seems we should use something like Object.keys to determine which elements to visit.
Also, the result is defined to be a new array "created as if by" the array constructor taking the length of the old array, so we might as well use that constructor to create it.
Here's an implementation taking these things into account, but probably missing some other subtleties:
function map(callbackfn, thisArg) {
var keys = Object.keys(this),
result = new Array(this.length);
keys.forEach(function(key) {
if (key >= 0 && this.hasOwnProperty(key)) {
result[key] = callbackfn.call(thisArg, this[key], key, this);
}
}, this);
return result;
}
I am assuming Object.keys returns the keys of the array in numerical order, which I think is implementation defined. If it doesn't, you could sort them.
You don't need to use this.slice(0). You can just make result an array and assign values to any index:
Array.prototype.collect = Array.prototype.collect || function(fn) {
var result = [];
for(var i in this) {
if (this.hasOwnProperty(i)) {
result[i] = fn(this[i]);
}
}
return result;
}