I have an array of products and I'm looking to create a new array if the product title or product tags list contains a search term.
Below is a simplified version.
const products = [{title: 'apple-green', tags: [{colour: 'red', tagType: 'colour'}]},
{title: 'orange', tags: [{colour: 'orange', tagType: 'colour'}]},
{title: 'cherry', tags: [{colour: 'red', tagType: 'colour'}]},
{title: 'pear', tags: [{colour: 'green', tagType: 'colour'}]}]
const searchTerm = "green"
const result = [{title: 'apple-green', tags: [{colour: 'red', tagType: 'colour'},
{title: 'pear', tags: [{colour: 'green', tagType: 'colour'}]}]
I thinking that using reduce would be the best way to achieve this.
I have tried the below without success.
const result = products.reduce((acc, product) => {
if (product.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
product.tags.map((tag) =>
tag.title.toLowerCase().includes(searchTerm.toLowerCase())))
return product
return [acc, ...product]
}, [])
Any help would be greatly received.
Array#filter is probably the correct function for this rather than Array#reduce, since no transformation appears to be applied to the output structure (and even if it were, map/filter may be clearer than reduce).
After writing a helper function to perform a case-insensitive substring/includes check, you can use Array#some on product.tags to determine if any of the tag.colour properties from a product's tags array match the search term.
const products = [{title: 'apple-green', tags: [{colour: 'red', tagType: 'colour'}]}, {title: 'orange', tags: [{colour: 'orange', tagType: 'colour'}]}, {title: 'cherry', tags: [{colour: 'red', tagType: 'colour'}]}, {title: 'pear', tags: [{colour: 'green', tagType: 'colour'}]}];
const includesAnyCase = (s, t) => s.toLowerCase().includes(t.toLowerCase());
const filterProducts = (products, searchTerm) =>
products.filter(product =>
includesAnyCase(product.title, searchTerm) ||
product.tags.some(tag => includesAnyCase(tag.colour, searchTerm))
)
;
console.log(filterProducts(products, "green"));
you can use :
result = products.filter(product => {
return product.title.toLowerCase()includes.(searchTerm.toLowerCase())
|| product.tags.includes({colour: searchTerm.toLowerCase(), tagType: 'colour'})
}
Using reduce twice for brevity, though filter seems a more natural fit:
const products = [
{ title: 'apple-green', tags: [{ colour: 'green', tagType: 'colour' }] },
{ title: 'apple-red', tags: [{ colour: 'red', tagType: 'colour' }] },
{ title: 'orange', tags: [{ colour: 'orange', tagType: 'colour' }] },
{ title: 'cherry', tags: [{ colour: 'red', tagType: 'colour' }] },
{ title: 'pear', tags: [{ colour: 'green', tagType: 'colour' }] }
];
const searchTerm = "green";
const results = products.reduce((matchingProducts, currentProduct) => {
const matchingColoured = currentProduct.tags.reduce((p, c) => {
return c.colour === searchTerm ? [c, ...p] : null;
}, []);
return matchingColoured ? [currentProduct, ...matchingProducts] : matchingProducts;
}, []);
console.log(JSON.stringify(results, {}, 4));
Outputs:
[
{
"title": "pear",
"tags": [
{
"colour": "green",
"tagType": "colour"
}
]
},
{
"title": "apple-green",
"tags": [
{
"colour": "green",
"tagType": "colour"
}
]
}
]```
You can use the Array.prototype.filter() function to accept only the products with titles that contain the search term or with tags that === the searchTerm. Of course you can use .includes on the tags as well if that is what you wish.
JSFiddle: https://jsfiddle.net/DariusM/y1u4cedk/1/
const products = [
{title: 'apple-green', tags: [{colour: 'red', tagType: 'colour'}]},
{title: 'orange', tags: [{colour: 'orange', tagType: 'colour'}]},
{title: 'cherry', tags: [{colour: 'red', tagType: 'colour'}]},
{title: 'pear', tags: [{colour: 'green', tagType: 'colour'}]}
];
const searchTerm = "green"
const result = products.filter((product) => {
if(product.title.toLowerCase().includes(searchTerm.toLowerCase())){
return true;
}
for(i = 0; i < product.tags.length; i++){
if(product.tags[i][product.tags[i].tagType] === searchTerm){
return true;
}
}
});
console.log(result);
Related
My mind is boggled, maybe because i've been stuck on this issue for a bit.
I have an array (redacted for readability):
variants = [
{title: 'color', children: [{title: 'red'}, {title: 'blue'}]
{title: 'size', children: [{title: 'large'}]
]
But need the following output:
variants = [
'color/red/size/large',
'color/blue/size/large',
]
or if the initial array is:
variants = [
{title: 'color', children: [{title: 'red'}, {title: 'blue'}]
{title: 'size', children: [{title: 'large'}, {title: 'medium'}]
]
the new array would be:
variants = [
'color/red/size/large',
'color/blue/size/large',
'color/red/size/medium',
'color/blue/size/medium',
]
Here is a fairly succinct reduce, but it has a nested flatMap(() => map()) call in its midst so I can't vouch for its efficiency.
const variants = [
{ title: 'color', children: [{ title: 'red' }, { title: 'blue' }] },
{ title: 'size', children: [{ title: 'large' }, { title: 'medium' }] },
]
variants.sort((a, b) => b.children.length - a.children.length);
const out = variants.reduce((acc, { title, children }) => {
const props = children.map(({ title: child }) => `${title}/${child}`);
acc = acc.length === 0 ? props : acc.flatMap(a => props.map(p => `${a}/${p}`));
return acc;
}, [])
console.log(out)
I have an array of arrays which I want to marge into one and remove duplicate values.
let arr = [ {
label :'XYZ',
colors:['black','white','blue']
},
{
label :'PQR',
colors:['orange','yellow','white']
},
{
label :'ABC',
colors:['black','pink','blue']
},
]
let updatedArr = []
for(let i=0 i< arr.length ; i++ ){
updatedArr.push(
arr[i].reduce((a, b) => [...a, ...b], [])
)
}
I want all the colors array value into one array and remove duplicate values as well.
Any help would be great.
To get a concatenated list of all values you can use flatMap().
And to deduplicate you can use [...new Set(duplicates)], which creates a Set of unique elements and spreads it into an array.
let arr = [{
label: 'XYZ',
colors: ['black', 'white', 'blue']
},
{
label: 'PQR',
colors: ['orange', 'yellow', 'white']
},
{
label: 'ABC',
colors: ['black', 'pink', 'blue']
},
]
let colors = [...new Set(arr.flatMap(obj => obj.colors))]
console.log(colors)
You can use reduce and forEach. Inside reduce call back use forEach to iterate the colors array & if accumulator does not include the color then push that color to accumulator
let arr = [{
label: 'XYZ',
colors: ['black', 'white', 'blue']
},
{
label: 'PQR',
colors: ['orange', 'yellow', 'white']
},
{
label: 'ABC',
colors: ['black', 'pink', 'blue']
},
]
let mergedColor = arr.reduce((acc, curr) => {
curr.colors.forEach((item) => {
if (acc.indexOf(item) === -1) {
acc.push(item)
}
})
return acc;
}, []);
console.log(mergedColor)
Use Array.prototype.flat for flatting the array, and then use a Set for distincting values and then create an array from the set using the Spread operator.
let arr = [{
label: 'XYZ',
colors: ['black', 'white', 'blue']
},
{
label: 'PQR',
colors: ['orange', 'yellow', 'white']
},
{
label: 'ABC',
colors: ['black', 'pink', 'blue']
},
]
const vals = [...new Set(arr.map(i => i.colors).flat())]
console.log(vals)
I have 2 arrays that contain objects. Each object has a key/value pair of "color" and "number". I want to check against these 2 arrays to find the objects that match the "number". After finding this, I want to add a key value pair to all the objects in the original array.
I have the below example which I believe is on the right track, but am struggling to find out how to proceed.
Basically, if the object matches, I want to change the K/V pair of "match" to either true or false.
const array1 = [ {color: 'red', number: 1, match: ''}, {color: 'red', number: 2, match: ''}, {color: 'red', number: 3, match: ''} ]
const array2 = [ {color: 'red', number: 3, match: ''}, {color: 'blue', number: 5, match: ''}, {color: 'blue', number: 6, match: ''} ]
async function findMatchingObjects(array1, array2){
const matchCheck = array1.filter(matchedObj => array2.includes(matchedObj));
console.log(matchCheck);
}
findMatchingObjects(array1, array2);
expected output would be:
const array3 = [{
color: 'red',
number: 1,
match: 'false'
}, {
color: 'red',
number: 2,
match: 'false'
}, {
color: 'red',
number: 3,
match: 'true'
},
{
color: 'red',
number: 3,
match: 'true'
}, {
color: 'blue',
number: 5,
match: 'false'
}, {
color: 'blue',
number: 6,
match: 'false'
}]
You can use map and some
Here idea is
First merge both the array's in a temp varibale.
Get the length of first array in a variable.
Now map on merged array and for each index less than length1 match it with array2 else match it with array1
const array1 = [ {color: 'red', number: 1, match: ''}, {color: 'red', number: 2, match: ''}, {color: 'red', number: 3, match: ''} ]
const array2 = [ {color: 'red', number: 3, match: ''}, {color: 'blue', number: 5, match: ''}, {color: 'blue', number: 6, match: ''} ]
let temp = [...array1,...array2]
let length1 = array1.length
let op = temp.map((inp,index)=> ({...inp, match: (index < length1 ?array2 : array1).some(({number})=> number === inp.number)}))
console.log(op)
You can use map() and find()
const array1 = [ {color: 'red', number: 1, match: ''}, {color: 'red', number: 2, match: ''}, {color: 'red', number: 3, match: ''} ]
const array2 = [ {color: 'red', number: 3, match: ''}, {color: 'blue', number: 5, match: ''}, {color: 'blue', number: 6, match: ''} ]
function checkMatches(arr1,arr2){
return arr1.map(x => arr2.find(a => a.number === x.number) ? ({...x,match:'true'}) : ({...x,match:'false'}));
}
let res = [...checkMatches(array1,array2),...checkMatches(array2,array1)]
console.log(res)
I can't think in another way besides checking array1 in array2 and vice-versa. Concating these two arrays before can handle false positives.
const array1 = [ {color: 'red', number: 1, match: ''}, {color: 'red', number: 2, match: ''}, {color: 'red', number: 3, match: ''} ]
const array2 = [ {color: 'red', number: 3, match: ''}, {color: 'blue', number: 5, match: ''}, {color: 'blue', number: 6, match: ''} ]
function checkMatchesByNumbers(arr1, arr2) {
return arr1.map(x => (arr2.filter(y => y.number === x.number).length > 0 ? x.match = true : x.match = false, x));
}
let resp = [].concat(checkMatchesByNumbers(array1, array2), checkMatchesByNumbers(array2, array1));
console.log(resp)
Assuming you actually want to preserve the duplicates, as in your provided expected output, that's how I would do it:
let t = [...array1, ....array2]
return t.map(item => {
item.match = t.filter(i => { i.number === item.number }).length > 1
})
const data1 = [
{ name: 'one' },
{ name: 'two' },
{ name: 'three' },
{ name: 'four' },
{ name: 'five'},
{ name: 'six'}
];
const colors = ['red', 'purple', 'violet', 'blue'];
I want output in this format---
render(){...
<Button color="from array colors">//Values from data1 here</Button>
These are the two arrays I have.Now my question is how do I access the colors in wraparound fashion i.e., 'five' of data1 should be colored with colors[0].Pls help coz I am a React newbie.
If by wraparound, you mean to choose color[0], on the 5th element since there are only 4 colors, You can use modulo %
Like:
const data1 = [
{ name: 'one' },
{ name: 'two' },
{ name: 'three' },
{ name: 'four' },
{ name: 'five'},
{ name: 'six'}
];
const colors = ['red', 'purple', 'violet', 'blue'];
data1.forEach((v, i) => {
let color = colors[i % colors.length];
//Construct HTML and console for testing
console.log('<Button color="' + color + '">' + v.name + '</Button>');
});
Suppose if I have two arrays - one as the preference in order and another the data set and I want to return first element from data set matching first matching preference.
For example
const userPref = ['banana', 'apple', 'peach'];
const givenFruits = [
{ name: 'apple', color: 'red' },
{ name: 'orange', color: 'orange' },
{ name: 'pear', color: 'yellow' },
{ name: 'cherry', color: 'red' },
{ name: 'grape', color: 'red' },
{ name: 'peach', color: 'red' },
{ name: 'coconut', color: 'brown' }
];
function findFavFruit() {
userPref.forEach((pref) => {
givenFruits.forEach((fruit) => {
if(pref === fruit.name) {
return fruit;
}
});
});
}
console.log('findFavFruit(): ' + JSON.stringify(findFavFruit(), null, 2));
This is always returning undefined. It supposed to return apple ONLY as it is user first matching preference and is found first in givenFruits.
What I am doing wrong in above code? And is there a cleaner way (avoid double forEach) in Javascript?
You can loop over the givenFruits using for...of and use Array.includes to test if the current fruit is inside the array of favorite fruits..
Example:
function findFavoriteFruit(preferences, arrayOfFruits) {
for (let fruit of arrayOfFruits) {
if (preferences.includes(fruit.name)) {
return fruit;
}
}
}
const userPref = ['apple', 'banana', 'peach'];
const givenFruits = [
{ name: 'apple', color: 'red' },
{ name: 'orange', color: 'orange' },
{ name: 'banana', color: 'yellow' },
{ name: 'pear', color: 'yellow' },
{ name: 'cherry', color: 'red' },
{ name: 'grape', color: 'red' },
{ name: 'peach', color: 'red' },
{ name: 'coconut', color: 'brown' }
];
const favoriteFruit = findFavoriteFruit(userPref, givenFruits);
console.log(favoriteFruit);
This implementation is the fastest (comparing with other answers) as you can see here.
Select the first element of userPref array to compare fruit.name to within .find() function, return result.
To return only the property value, for example, "name", you can pass the property as a string to the function and use bracket notation to reference and return the property
const userPref = ['apple', 'banana', 'peach'];
const [preference] = userPref;
const givenFruits = [
{ name: 'apple', color: 'red' },
{ name: 'orange', color: 'orange' },
{ name: 'banana', color: 'yellow' },
{ name: 'pear', color: 'yellow' },
{ name: 'cherry', color: 'red' },
{ name: 'grape', color: 'red' },
{ name: 'peach', color: 'red' },
{ name: 'coconut', color: 'brown' }
];
function findFavFruit(pref, prop, arr) {
return arr.find(fruit => pref === fruit[prop])[prop];
}
let res = findFavFruit(preference, "name", givenFruits);
console.log(res);
Of course you should provide some checks whether given keys exist, but here is the basic:
(givenFruits.find((v)=>{return v.name == userPref[0]})).name
Don't know why to iterate over userPref if you only need first key.
All of the above solutions are correct, I just wanted to clarify what the issue with your code was. See below:
function findFavFruit() {
let userFruit;
userPref.forEach((pref) => {
givenFruits.forEach((fruit) => {
if(pref === fruit.name) {
userFruit = fruit;
}
});
});
return userFruit;
}
This would return the fruit if it was found or undefined if it wasn't.