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;
});
});
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 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.
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.
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.
I want to sort an array by a split part of an array.
example_array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"]
I want to so it sorts it like this:
console.log(code here) // prints ["Zebra:Add","Jam:Back","Pay:Cold","And:Vets"]
Note: I want "Zebra:Add","Pay:Cold", etc to stay together. I just want it be sorted by the text after the ":".
From your comment on the question:
I can't even think of a solution
Break the problem into smaller pieces. You want to sort an array by a part of the strings in the array, so you need to figure out / look into
How to sort an array (you've done that, you've found the sort method)
How to isolate the part of the string you want to sort on
How to correctly compare strings for Array#sort
How to do #2 and #3 within the context of doing #1
Re #2, there are various ways to do that. You could find the : via String#indexOf and then use substring to get all characters after it. You could split the string on :, then use the second half (if you know there won't be more than one : in the string). Or you could use a regular expression to isolate everything after the first :.
For instance, someString.match(/:.*$/)[0] isolates all characters starting with the first :. (Including the : is harmless, but you could use .substring(1) if you don't want to include it.)
Re #3: Array#sort expects its callback to return a negative number if the first argument should come before the second, 0 if their order doesn't matter, or a positive number if the second should come before the first. String#localeCompare compares strings according to the current locale and returns exactly that information, so we want to use that.
Re #4: Array#sort accepts a callback function, so you could do all the string splitting and comparison in that callback. But since the callback will be called repeatedly, frequently with either the first or second argument being one that's already been checked before, for larger arrays doing it then may be inefficient. It may make more sense to do all the string splitting / isolation in advance, then do the sort, then get your desired result.
So:
The not-particularly-efficient way (which is fine for data sets like your small array) is to isolate the part you want to sort on within the sort callback:
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort(function(left, right) {
return left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0]);
});
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort(function(left, right) {
return left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0]);
});
console.log(array);
With ES2015+ syntax:
const array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort((left, right) =>
left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0])
);
const array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array.sort((left, right) =>
left.match(/:.*$/)[0].localeCompare(right.match(/:.*$/)[0])
);
console.log(array);
If it's a massive array where doing those splits on every compare is problematic, you could map first, then sort, then unmap:
var array = /*...really big array...*/;
array =
array.map(function(entry) { return {full: entry, key: entry.match(/:.*$/)[0]};})
.sort(function(left, right) { return left.key.localeCompare(right.key); })
.map(function(entry) { return entry.full; });
var array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array =
array.map(function(entry) { return {full: entry, key: entry.match(/:.*$/)[0]};})
.sort(function(left, right) { return left.key.localeCompare(right.key); })
.map(function(entry) { return entry.full; });
console.log(array);
With ES2015+ syntax:
let array = /*...really big array...*/;
array =
array.map(entry => ({full: entry, key: entry.match(/:.*$/)[0] }))
.sort((left, right) => left.key.localeCompare(right.key))
.map(entry => entry.full);
let array = ["Zebra:Add","Pay:Cold","And:Vets","Jam:Back"];
array =
array.map(entry => ({full: entry, key: entry.match(/:.*$/)[0] }))
.sort((left, right) => left.key.localeCompare(right.key))
.map(entry => entry.full);
console.log(array);
I like the simplicity of the previous answer, in comparison My approach is probably too wordy! But here goes...
1.) take the original array and build a new sorting array from it, JSON array with each object having a text1 and text2 value... we'll sort on the text 2 value
2.) run a sort based on the text2 value
3.) empty the original array
4.) loop over the sorting array and re-populate the original array
heres a fiddle example I threw together
// STARTING ARRAY. WE WANT TO SORT BY THE TEXT AFTER THE COLON
example_array = ["Zebra:Add", "Pay:Cold", "And:Vets", "Jam:Back"];
// AN EMPTY ARRAY TO BUILD A JSON ARRAY FROM, THE SORT FROM THE DESIRED TEXT STRING
sorting_array = [];
// LOOP THROUGH THE ORIGINAL ARRAY AND PUSH A NEW OBJECT TO THE SORTING ARRAY
// EACH OBJECT CONTAINS A TEXT1 VALUE AND A TEXT2 VALUE
$.each(example_array, function(i, val){
sorting_array.push({"text1": val.split(':')[0], "text2": val.split(':')[1]})
})
// SORT THE SORTING ARRAY BY THE TEXT2 VALUE
sorting_array.sort(function(a, b){
if (a.text2 < b.text2) return -1;
if (b.text2 < a.text2) return 1;
return 0;
});
// EMPTY OUR ORIGINAL ARRAY
example_array = [];
// FOR DEMO PURPOSES LETS DISPLAY EACH IN THE DOM IN A UL ,
// AND ALSO RE-POPULATE THE ORIGINAL ARRAY WITHT HE NEW ORDER
$.each(sorting_array, function(i, val){
example_array.push(val.text1+':'+val.text2)
})
// TO SHOW THE NEW ORDER, LETS LOOP BACK OVER THE EXAMPLE_ARRAY
$.each(example_array, function(i, val){
$('ul').append('<li>' + val+ '</li>');
})