Sort by multiple strings in Array using Javascript - javascript

I am trying to sort an array by multiple strings but coming up short. Is this possible or is writing something below the only to achieve this?
Doesn't work:
const colorArr = ["Red", "Blue", "Green"]
const colorMap = colorArr.map((a) => a);
const newArr = data.sort((a, b) => {
return (
(a.credits.credit.value !== colorMap) - (b.credits.credit.value !== colorMap)
);
});
console.log("newArr========", newArr)
This works, but can get very lengthy the more conditions...
const data = [
{
credits: {
credit: {
value: "Red",
},
},
},
{
credits: {
credit: {
value: "Blue",
},
},
},
{
credits: {
credit: {
value: "Green",
},
},
},
{
credits: {
credit: {
value: "Red",
},
},
},
{
credits: {
credit: {
value: "Red",
},
},
},
{
credits: {
credit: {
value: "Blue",
},
},
},
];
const nameActor = "Red";
const nameEp = "Blue";
const nameDirector = "Green";
const newArr = data.sort((a, b) => {
return (
(a.credits.credit.value !== nameActor) - (b.credits.credit.value !== nameActor) ||
(a.credits.credit.value !== nameEp) - (b.credits.credit.value !== nameEp) ||
(a.credits.credit.value !== nameDirector) - (b.credits.credit.value !== nameDirector)
);
});

const colorOrder = ['Red', 'Blue', 'Green'];
const order = data.sort(
(a, b) =>
colorOrder.indexOf(a.credits.credit.value) -
colorOrder.indexOf(b.credits.credit.value)
);

Related

How to Filter a Nested Object with array of String

let products = [
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
{
name: "B",
color: "Blue",
size: {
size1: 5,
size2: 19,
size3: 22,
},
},
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
filters = ['Blue','2'];
the result must be the object that checks all strings in the array for example
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
the research must be accepted whatever the value in the
You can resolve the nest via using a stack in some manner, either by recursion or iteratively using a stack explicitly. Here's a recursive solution:
function getFiltered(obj, filters, found = null) {
let outermostCall = (found === null);
if (outermostCall) { //outermost call
found = [];
for (let index = 0; index < filters.length; index++) {
found[index] = false;
}
}
for (let key in obj) {
if (typeof obj[key] === 'object') {
let tempFound = getFiltered(obj[key], filters, found);
for (let index = 0; index < found.length; index++) {
if (tempFound[index]) found[index] = true;
}
} else {
let foundIndex = -1;
for (let index = 0; index < filters.length; index++) {
if (filters[index] == obj[key]) {
foundIndex = index;
index = filters.length;
}
}
if (foundIndex >= 0) {
found[foundIndex] = true;
}
}
}
if (outermostCall) {
return !found.filter(item => !item).length;
}
return found;
}
function getAllFiltered(array, filters) {
let output = [];
for (let obj of array) {
if (getFiltered(obj, filters)) output.push(obj);
}
return output;
}
let products = [
{
name: "A",
color: "Blue",
size: {
size1: 1,
size2: 2,
size3: 3,
},
},
{
name: "B",
color: "Blue",
size: {
size1: 5,
size2: 19,
size3: 22,
},
},
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
let filters = ['Blue','2'];
console.log(getAllFiltered(products, filters));
You could take a closure over any of the search values and check if all of them are in the object or nested objest for filtering.
const
has = f => {
const check = o => o && typeof o === 'object'
? Object.values(o).some(check)
: f === o;
return check;
},
products = [{ name: "A", color: "Blue", size: { size1: 1, size2: 2, size3: 3 } }, { name: "B", color: "Blue", size: { size1: 5, size2: 19, size3: 22 } }, { name: "C", color: "Black", size: 70 }, { name: "D", color: "Green", size: 50 }],
search = ['Blue', 2],
result = products.filter(o => search.every(f => has(f)(o)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array.every to check if all the filters are present in the object, if that's what you mean.
const products = [
{ name: "A", color: "Blue", size: { size1:1, size2:2, size3:3 } },
{ name: "B", color: "Blue", size: { size1:5, size2:19, size3:22 } },
{ name: "C", color: "Black", size: 70 },
{ name: "D", color: "Green", size: 50 },
];
const filters = ['Blue','2'];
const filtered = products.filter(product => {
return Object.values(product).every(value => {
return filters.includes(value);
});
});
console.log(filtered);
The function strings returns all nested strings within an object, converting any numbers to strings. Then, we just filter any product where the list of strings includes all necessary matches.
const products = [{"name":"A","color":"Blue","size":{"size1":1,"size2":2,"size3":3}},{"name":"B","color":"Blue","size":{"size1":5,"size2":19,"size3":22}},{"name":"C","color":"Black","size":70},{"name":"D","color":"Green","size":50}];
const filters = ['Blue','2'];
const subsetMatch=(a,s)=>s.every(i=>a.includes(i));
const strings=i=>typeof i==='object'?Object.values(i).flatMap(strings):i+'';
console.log(products.filter(i=>subsetMatch(strings(i),filters)));

How to order an object based on the given values in javascript [duplicate]

I would like to get your help about this little problem.
I have like to order this array depending on the code value but not in alphabetical order.
(I specified this in bold but got eventually flagged anyway, people don't even care about reading the question)
For example I would like to have all the green objects, then all the blue ones and then all the red ones. What is the best way to do that?
[
{ code: "RED", value: 0},
{ code: "BLUE", value: 0},
{ code: "RED", value: 0},
{ code: "GREEN", value: 0},
{ code: "BLUE", value: 0},
{ code: "RED", value: 0},
{ code: "GREEN", value: 0},
{ code: "BLUE", value: 0}
]
Is it possible to do that with the sort function? What would the condition be in that case?
You could take an object for the wanted order.
var array = [{ code: "RED", value: 0 }, { code: "BLUE", value: 0 }, { code: "RED", value: 0 }, { code: "GREEN", value: 0 }, { code: "BLUE", value: 0 }, { code: "RED", value: 0 }, { code: "GREEN", value: 0 }, { code: "BLUE", value: 0 }],
order = { GREEN: 1, BLUE: 2, RED: 3 };
array.sort(function (a, b) {
return order[a.code] - order[b.code];
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For unknow colors/values, you could use a default value with
0, for sorting to top or
Infinity or as some suggest because of the ability for caculating Number.MAX_VALUE for sorting to the end,
or any other value for sorting inbetween the other groups.
At last you could sort the special treated items with an other sorting part, chained with logical OR ||.
var array = [{ code: "YELLOW", value: 0 }, { code: "BLACK", value: 0 }, { code: "RED", value: 0 }, { code: "BLUE", value: 0 }, { code: "RED", value: 0 }, { code: "GREEN", value: 0 }, { code: "BLUE", value: 0 }, { code: "RED", value: 0 }, { code: "GREEN", value: 0 }, { code: "BLUE", value: 0 }],
order = { GREEN: 1, BLUE: 2, RED: 3, default: Infinity };
array.sort(function (a, b) {
return (order[a.code] || order.default) - (order[b.code] || order.default) || a.code.localeCompare(b.code);
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Set the custom priority first
var codePriority = [ "GREEN", "BLUE", "RED" ];
Now use the same in sorting as
arr.sort( function(a,b){
if ( a.code == b.code ) return a.value - b.value;
return codePriority.indexOf( a.code ) - codePriority.indexOf( b.code ) ; notice this line
})
Demo
var arr = [
{ code: "RED", value: 0},
{ code: "BLUE", value: 0},
{ code: "RED", value: 0},
{ code: "GREEN", value: 0},
{ code: "BLUE", value: 0},
{ code: "RED", value: 0},
{ code: "GREEN", value: 0},
{ code: "BLUE", value: 0}
];
var codePriority = [ "GREEN", "BLUE", "RED" ];
arr.sort( function(a,b){
if ( a.code == b.code ) return a.value - b.value;
return codePriority.indexOf( a.code ) - codePriority.indexOf( b.code )
});
console.log( arr );
You could implement a schema array and sort according to element's index inside that schema array.
const a = ['GREEN', 'BLUE', 'RED'];
const o = [{code:"RED",value:0},{code:"BLUE",value:0},{code:"RED",value:0},{code:"GREEN",value:0},{code:"BLUE",value:0},{code:"RED",value:0},{code:"GREEN",value:0},{code:"BLUE",value:0}];
const r = o.slice().sort(({ code: q }, { code: w }) => a.indexOf(q) - a.indexOf(w));
console.log(r);
this is a function for sorting by a specific color.
const colors = [{
name: "T-shirt",
color: "red",
price: 20
},
{
name: "Shoes",
color: "yellow",
price: 20
},
{
name: "Pants",
color: "red",
price: 20
},
{
name: "Cap",
color: "yellow",
price: 20
},
{
name: "Skirt",
color: "red",
price: 15
},
]
let sortByColor = color => colors.sort((a, b) => a.color === color ? -1 : 1)
console.log(sortByColor('red'))
You can use the sort function i.e Array.prototype.sort() Visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Firstly you have to decide whether you want to sort in ascending order or in descending order.
To sort in ascending order you can do something like this:
arr.sort((a, b) => {
return a.color < b.color ? -1 : a.color > b.color ? 1 : 0;
});
An to do it in descending order:
arr.sort((a, b) => {
return a.color > b.color ? -1 : a.color < b.color ? 1 : 0;
});
Now suppose you want a certain color sorted and displayed first. You can have a function like this:
const sortByKeyAsc = (arr,color) =>
arr.sort((a, b) => {
if(a.color < b.color || (a.color === color && b.color !== color)){
return -1;
}
if(a.color > b.color || (a.color !== color && b.color === color)){
return 1
}
return 0;
});
and you can use it like this:
const array1 = sortByKeyAsc(arr,'pink');
I was dealing with a case where I had to match a string status and sort by the various statuses specifically. .findIndex and .includes ended up being the best approach.
statusPriority sets the intended order. Once the indexes are found they can be compared and returned to the .sort
let records = [
{status: 'Record is ready to submit.', active: true, name: 'A'},
{status: 'Record has been sent.', active: true, name: 'B'},
{status: 'Activation is required.', active: true, name: 'C'},
{status: 'Record submission failed.', active: true, name: 'D'},
{status: 'Creation is pending.', active: true, name: 'E'},
]
console.log(records.map(r => r.status))
let statusPriority = ['creation', 'activation', 'sent', 'ready', 'failed'];
records.sort((a, b) => {
let aIndex = statusPriority.findIndex(status => a.status.toLowerCase().includes(status));
let bIndex = statusPriority.findIndex(status => b.status.toLowerCase().includes(status));
return aIndex - bIndex;
})
console.log(records.map(r => r.status))
You can sort color alphabetically by using this function
var sortedArr = arr.sort((first, second)=>{
return first.color < second.color ? -1 : 1
})

Reduce JSON object containing JSON objects into an array of the form [Path, Value]

I'm trying to reduce a JSON object of nested objects, arrays, and strings into a single array of the paths and values.
Expected input:
appleObjects = {
'apples': {
'colors': ['red', 'green', 'yellow'],
'owner': 'Person1',
'types': [
{name: 'fuji', flavor: 'good'}
]
},
'pears': {
'colors': ['red', 'green', 'yellow'],
'owner': 'Person1',
'types': [
{name: 'small', flavor: 'surprising'},
{name: 'large', flavor: 'bold'}
]
}
}
Expected output:
appleValues = [
{path: 'apples.colors', value: 'red'},
{path: 'apples.colors', value: 'green'},
{path: 'apples.colors', value: 'yellow'},
{path: 'apples.owner', value: 'Person1'},
{path: 'apples.types', value: {name: 'fuji', flavor: 'sweet?'}}
...
]
So far, I'm working towards using nested reduce functions and/or recursion but as it's in a web app I'm wondering if there is a more efficient way or even a library out there that already does something similar.
Here's what I'm working with so far. Currently react is complaining of too much recursion so clearly this is not the best way to go about accomplishing this:
myReducer = (p, obj) => {
Object.entries(obj ?? []).reduce((acc, currVal, currIdx) => {
if(typeof currVal === undefined) {
return acc
}
if(typeof currVal === "string") {
return {
basePath: acc.basePath,
outputArr: acc.outputArr.push({
path: acc.basePath + '.' + currVal[0],
value: currVal[1]
})
}
}
if(typeof currVal === "object") {
return {
basePath: acc.basePath,
outputArr: acc.outputArr.concat(this.myReducer(acc.basePath + '.' + currVal[0], currVal[1]))
}
}
return acc
}, {basePath: p, outputArr: []})
}
getArrayOfApplesValues = () => {
const {
applesObjects
} = this.state
if (applesObjects === null) return []
Object.entries(applesObjects).reduce((acc, currVal, currIdx) => {
if(typeof currVal[1] === "object") {
return {
path: acc.basePath,
outputArr: acc.outputArr.concat(this.myReducer (acc.basePath + '.' + currVal[0], currVal[1]))
}
}
return acc
}, {basePath: '', outputArr: []})
}
Forgive my lack of apple knowledge in the examples.
Recursive approach, Use reduce and Object.etnries
const appleObjects = {
apples: {
colors: ["red", "green", "yellow"],
owner: "Person1",
types: [{ name: "fuji", flavor: "good" }]
},
pears: {
colors: ["red", "green", "yellow"],
owner: "Person1",
types: [
{ name: "small", flavor: "surprising" },
{ name: "large", flavor: "bold" }
]
}
};
const getKeysArr = (obj, prefix) => {
var arr = Object.entries(obj).reduce((acc, [key, value]) => {
const temp_key = prefix ? `${prefix}.${key}` : key;
if (typeof value === "string" || typeof value === "number") {
acc.push({ path: temp_key, value: value });
} else if (Array.isArray(value)) {
value.forEach(item => acc.push({ path: temp_key, value: item }));
} else {
acc.push(getKeysArr(value, key));
}
return acc;
}, []);
return arr;
};
console.log(getKeysArr(appleObjects, ""));
Instead of running reduce you have to run your code recursively and try to flatten your object into an array using below function:
let appleObjects = {
'apples': {
'colors': ['red', 'green', 'yellow'],
'owner': 'Person1',
'types': [
{name: 'fuji', flavor: 'good'}
]
},
'pears': {
'colors': ['red', 'green', 'yellow'],
'owner': 'Person1',
'types': [
{name: 'small', flavor: 'surprising'},
{name: 'large', flavor: 'bold'}
]
}
};
let flatten = (obj, prefix, result) => {
result = result || [];
for(let key of Object.keys(obj)){
let keyExpr = prefix ? `${prefix}.${key}` : `${key}`;
if(Array.isArray(obj[key])){
obj[key].forEach(x => result.push({path: keyExpr, value: x}));
}
else if(typeof obj[key] === "object"){
flatten(obj[key], keyExpr, result);
}
else {
result.push({path: keyExpr, value: obj[key]})
}
}
return result;
}
let result = flatten(appleObjects);
console.log(result);
#mickl's answer is great but you might as well try this.
let fruits = {
apples: {
"colors": ["red", "green", "yellow"],
"owner": "Person1",
"types": [
{name: "fuji", flavor: "good"}
]
},
pears: {
"colors": ["red", "green", "yellow"],
"owner": "Person1",
"types": [
{name: "small", flavor: "surprising"},
{name: "large", flavor: "bold"}
]
}
}
let arr = []
let fruitsArr = Object.entries(fruits)
fruitsArr.forEach(fruit => {
let properties = fruit[1]
let keys = Object.entries(fruit[1]).map(x => x[0])
keys.forEach(key => {
if (!Array.isArray(properties[key])) {
return arr.push({
path: `${fruit[0]}.${key}`,
value: properties[key]
})
}
if (typeof properties[key][0] === 'string') {
properties[key].forEach(x => {
return arr.push({
path: `${fruit[0]}.${key}`,
value: x
})
})
}
if (typeof properties[key][0] === 'object') {
return arr.push({
path: `${fruit[0]}.${key}`,
value: properties[key][0]
})
}
})
})

Replace value in one array with matching properties from second array

I've got two arrays:
arrayOne = ["green","blue","purple"]
and
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
I want the return array to be [1, 8, 9], with "purple" being pushed as an object at the end of arrayTwo (with a new id).
What's the most efficient way to go about this?
The following code uses map to either retrieve the id of the element in the second array or, if that element doesn't exist, create a new one by incrementing the last id in that array by 1.
arrayOne = ["green","blue","purple"]
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
const newArr = arrayOne.map(color => {
const found = arrayTwo.find(el => el.name === color);
if (found) {
return found.id;
}
const newId = arrayTwo[arrayTwo.length - 1].id + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
console.log(newArr);
console.log(arrayTwo);
Edit: Note that it may be brittle to assume the last item in arrayTwo contains the highest id. In that case, you can always find the max ID:
const newArr = arrayOne.map(color => {
let maxId = 0;
const found = arrayTwo.find(el => {
if (el.id > maxId) {
maxId = el.id;
}
return el.name === color
});
if (found) {
return found.id;
}
const newId = maxId + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
A thought on efficiency
If you have any concerns about efficiency if this is going to be a large (hundreds/thousands/more) element arrays, you can consider changing arrayTwo to an object with color as a key:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let maxId = 0;
// Create map
const arrayTwoMap = arrayTwo.reduce((acc, el) => {
if (el.id > maxId) maxId = el.id;
acc[el.name] = el.id;
return acc;
}, {});
// Find elements
const newArr = arrayOne.map(el => {
const found = arrayTwoMap[el];
if (found !== undefined) {
return found;
}
const newId = maxId + 1;
arrayTwoMap[el] = newId;
return newId;
});
console.log(newArr);
this code can achieve what you want:
let arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let indexes = {}, lastId = 0;
arrayTwo.forEach(({name, id}) => {
if(indexes[name] = id, id > lastId) lastId = id
});
function getResult(a){
return a.map(e => indexes[e] || (arrayTwo.push({name: e, id: ++lastId}), indexes[e] = lastId))
}
// arrayOne contents
let result = getResult(["green", "blue", "purple"]);
console.log(arrayTwo, result);
// with other data
let result2 = getResult(["cyan", "blue", "purple", "azure"]);
console.log(arrayTwo, result2);
Hope it helps
You can convert arrayTwo into a Map (for efficiency), where the name is the key and the id is the value. Then, once you have done that, you can .map() arrayOne into an array of id by using each name element as a look-up key to get its associated id. If you find a name which is not in the Map, then you can add a new object, to your arrayTwo array, and increment the id counter:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(name => lut.get(name) || (arrayTwo.push({name, id: ++l_id}), l_id));
console.log(res);
console.log(arrayTwo);
If you're only concerned about the return value (and not changing array 2), you can simplify the code down:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(
name => lut.get(name) || ++l_id
);
console.log(res);

Fetching Objects from Array by passing key & value in javascript

This is my Array of objects. I want to filter the objects by passing query in a function.
const products = [{
name: "A",
color: "Blue",
size: 50
},
{
name: "B",
color: "Blue",
size: 60
},
{
name: "C",
color: "Black",
size: 70
},
{
name: "D",
color: "Green",
size: 50
}
];
My desired output which will filter from the query which I am passing in function which can be anything
{
name: "A",
color: "Blue",
size: 50
}, {
name: "C",
color: "Black",
size: 70
}
This is my query object which I will pass to function
const filter = {
color: ["Blue", "Black"],
size: [70, 50]
};
This is my function which I can assign to other variable and use it for further operations
const filteredData = filterIt(products, filter);
Here is another option:
function filterIt(products, filter) {
return products.filter(p => {
for (const k in filter) {
if (filter[k].indexOf(p[k]) === -1) {
return false;
}
}
return true;
})
}
See a Demo
If you want to make an OR instead of an AND in the logic the previous function modified would be:
function filterIt(products, filter) {
return products.filter(p => {
for (const k in filter) {
// if a value of any property of the product is contained in a filter array, include it
if (filter[k].indexOf(p[k]) !== -1) {
return true;
}
}
return false;
})
}
See a Demo for this one too :)
You could get the entries of the filter object and take key and value for checking with includes.
const
filterBy = filter => o => Object.entries(filter).every(([k, v]) => v.includes(o[k])),
filterIt = (array, filter) => array.filter(filterBy(filter)),
products = [{ name: "A", color: "Blue", size: 50 }, { name: "B", color: "Blue", size: 60 }, { name: "C", color: "Black", size: 70 }, { name: "D", color: "Green", size: 50 }],
filter = { color: ["Blue", "Black"], size: [70, 50] },
filteredData = filterIt(products, filter);
console.log(filteredData);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const products = [{
name: "A",
color: "Blue",
size: 50
},
{
name: "B",
color: "Blue",
size: 60
},
{
name: "C",
color: "Black",
size: 70
},
{
name: "D",
color: "Green",
size: 50
}
];
const filter = {
color: ["Blue", "Black"],
size: [70, 50]
};
let filterIt = (products, filter) => {
return products.filter(p => {
//get all filter keys
let keys = Object.keys(filter);
let validkeys = [];
keys.forEach(k=>{
// check if filter property exist and includes filter value
if(p.hasOwnProperty(k) && filter[k].includes(p[k])){
validkeys.push(true);
}
})
//check all filter matches
return validkeys.length === keys.length;
});
}
const filteredData = filterIt(products, filter);
console.log("...filteredData", filteredData);

Categories