How to customize sort an array - javascript

I'm trying to sort an array of objects in JS, but it doesn't work for some reason.
The array I requested from API may contain numbers, upper and lower case letters, and some Chinese characters.
eg:
const arr = [
{ "title": "!Test Segment and CSV" },
{ "title": "&test (SC)" },
{ "title": "1234test (SC)" },
{ "title": "AAShop1 (SC)" },
{ "title": "AAShop2(SC)" },
{ "title": "ABCshop123 (SC)" },
{ "title": "adidas Outlet" },
{ "title": "AIGLE" },
{ "title": "American Eagle" },
{ "title": "Châteraisé" },
{ "title": "Tekcent" },
{ "title": "반찬 & COOK" },
{ "title": "始祖鸟" },
{ "title": "春水堂" },
{ "title": "稻成" }
];
I want it sorted according to the following rules.
sort a after A
sort B after A
sort Ac after ABC
{ "title": "!Test Segment and CSV" },
{ "title": "&test (SC)" },
{ "title": "1234test (SC)" },
{ "title": "AAShop1 (SC)" },
{ "title": "AAShop2(SC)" },
{ "title": "ABCshop123 (SC)" },
{ "title": "AIGLE" },
{ "title": "American Eagle" },
{ "title": "adidas Outlet" },
{ "title": "Châteraisé" },
{ "title": "Tekcent" },
{ "title": "始祖鸟" },
{ "title": "春水堂" },
{ "title": "稻成" },
{ "title": "반찬 & COOK" }
Thanks in advance for any help!

.sort() and .localeCompare(). Note, in the OP, the input array is already sorted correctly so in this answer I have jumbled the order of the input array to demonstrate that the code functions correctly.
const data = [{ "title": "BB" }, { "title": "Ac" }, { "title": "adidas" }, { "title": "AA" }, { "title": "Ba" }, { "title": "ABC" }];
const output = data.sort((a, b) => a.title.localeCompare(b.title));
console.log(output);

You just need to sort it on lower case.
let myArr = [{ "title": "AA" }, { "title": "ABC" }, { "title": "Ac" }, { "title": "adidas" }, { "title": "Ba" }, { "title": "BB" }]
let sortList = myArr.sort(function (a, b) {
return a.title.toLowerCase() > b.title.toLowerCase()
})
console.log(sortList)

You can simply achieve that without manipulating a string into a lowerCase with the only help of a simple Array.sort() method.
Live Demo :
let myArr = [{ "title": "AA" }, { "title": "ABC" }, { "title": "Ac" }, { "title": "adidas" }, { "title": "Ba" }, { "title": "BB" }];
let sortedArr = myArr.sort((a, b) => {
return a.title - b.title
});
console.log(sortedArr);

Related

Filter an array of objects with nested arrays based on another array

I've 2 different APIs. first one returns an array of event objects (this data set is growing and expected to be large). each event has a category array that has a list of strings. The other API returns an array of filter objects. each filter has a "name" property and an array of keywords. any keyword included in the categories array in any event should go under this filter name.
The ultimate goal is to have a list of filters on the screen and when a user click on a filter I should render all events under this filter.
Event Object Example:
{
"text": {
"headline": "Headline example",
"text": "event description "
},
"media": {
"url": "https://www.google.com/",
"caption": "",
"credit": ""
},
"categories": [
"National",
"Canada",
"British Columbia"
]
}
Filters Object Example:
{
"filters": [
{
"keywords": [
"Atlantic",
"New Brunswick",
"Newfoundland and Labrador",
"Prince Edward Island",
"Nova Scotia"
],
"name": "Atlantic"
},
{
"keywords": [
"ontario",
"Quebec"
],
"name": "Central Canada"
},
{
"keywords": [
"Manitoba",
"Saskatchewan",
"Alberta"
],
"name": "Prairie Provinces"
},
{
"keywords": [
"British Columbia"
],
"name": "West Coast"
},
{
"keywords": [
"Nunavut",
"Northwest Territories",
"Yukon Territory"
],
"name": "North"
},
{
"keywords": [
"National"
],
"name": "National"
}
]
}
After a couple of days working on it I came up with this solution.
function filterTimelineData(filtersObj, timelineData) {
if (!timelineData || !filtersObj) return [];
// create a new object with filters "name" as key;
const filters = Object.keys(filtersObj);
const filteredTimelineData = Object.keys(filtersObj).reduce((o, key) => ({ ...o, [key]: [] }), {});
const filteredData = timelineData.events.reduce((acc, current) => {
let filterMatch = false;
let filterMatchName = '';
for (let i = 0; i < filters.length; i++) {
filterMatch = current.categories.some(item => {
return filtersObj[filters[i]].includes(item.toLocaleLowerCase());
});
if (filterMatch && filterMatchName !== filters[i]) { // to avoid duplicated items with different categories under the same filter
filterMatchName = filters[i];
acc[filters[i]].push(current);
}
}
return acc;
}, filteredTimelineData);
return filteredData;
}
export function timelineFiltersObj(filters) {
const filtersObj = filters.filters.reduce((acc, current) => {
const filterName = current.name.replace(/ /g, '_').toLocaleLowerCase();
if (!acc.hasOwnProperty(filterName)) {
acc[filterName] = [];
}
acc[filterName] = [].concat(current.keywords.map(item => item.toLocaleLowerCase()));
return acc;
}, {});
return filtersObj;
}
Desired output:
An object or an array for all filters to be rendered on the screen
An object with filters name as a key and the value would be an array of events that has any keyword that matches any of this filter keywords
check this code example: link
My Questions:
Is there an easier/simpler way to solve this problem?
I'm passing "filteredTimelineData" object as initial value to .reduce function. Is this legitimate? I couldn't find any answers online to this question specifically.
from a time complexity prospective. will this code cause any memory issue if the dataset grows?
This is a simple way to get the above result. I am using JavaScript ES5 features in this solution which is supported by almost all the browsers except IE9
const filters = {
"filters": [
{
"keywords": [
"Atlantic",
"New Brunswick",
"Newfoundland and Labrador",
"Prince Edward Island",
"Nova Scotia"
],
"name": "Atlantic"
},
{
"keywords": [
"ontario",
"Quebec"
],
"name": "Central Canada"
},
{
"keywords": [
"Manitoba",
"Saskatchewan",
"Alberta"
],
"name": "Prairie Provinces"
},
{
"keywords": [
"British Columbia"
],
"name": "West Coast"
},
{
"keywords": [
"Nunavut",
"Northwest Territories",
"Yukon Territory"
],
"name": "North"
},
{
"keywords": [
"National"
],
"name": "National"
}
]
};
const timelineData = {
"events": [
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"New Brunswick"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"National"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": "https://youtu.be/poOO4GN3TN4"
},
"categories": [
"Northwest Territories"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"Ontario"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"National"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": "https://philanthropy.cdn.redcross.ca/timeline/July2020-3.jpg"
},
"categories": [
"British Columbia"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"Alberta"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"Prince Edward Island"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"National"
]
},
{
"text": {
"headline": "headline example",
"text": "event-descriprion"
},
"media": {
"url": ""
},
"categories": [
"National"
]
}
]
};
var categoriesToEventsMap = timelineData.events.reduce((res, event) => {
event.categories.forEach(c=> {
res = {
...res,
[c.toLowerCase()]: [...(res[c.toLowerCase()] || []), event]
}
});
return res;
}, {})
var result = filters.filters.reduce((acc, filter) => {
let events = []
const filterName = filter.name.replace(' ', '_').toLowerCase();
filter.keywords.forEach((key)=>{
events = [...events, ...(categoriesToEventsMap[key.toLowerCase()] || [])];
});
acc[filterName] = [...(acc[filterName] || []), ...events]
return acc;
}, {});
console.log(result);

How to customize sort objects by property?

Starting with a given array of objects, I group those by their classification property into an object keyed by that classification. Then I print out the desired classification effect.
Now I need to sort these classifications. I want the commonly used ones to be printed first, and other classifications to be sorted alphabetically after it.
So I hope the sorting effect is as follows:
common used is the first
others are alphabetic
Here is the expected structure, the actual results in the code run
commonly-used
stackoverflow
github
movie-type
movie site
movie site2
search-type
google
wikipedia
study-type
w3c
vue
react
tool-type
remove bg
Here is my code where the grouping is done, but where I still need to add the sorting somehow:
let ary = [{
"type": "search-type",
"content": {
"title": "google",
"link": "https://",
}
}, {
"type": "study-type",
"content": {
"title": "w3c",
"link": "https://",
}
}, {
"type": "movie-type",
"content": {
"title": "movie site",
"link": "https://",
}
}, {
"type": "commonly-used",
"content": {
"title": "stackoverflow",
"link": "https://",
}
}, {
"type": "tool-type",
"content": {
"title": "remove bg",
"link": "https://www//",
}
}, {
"type": "movie-type",
"content": {
"title": "movie site2",
"link": "https://",
}
}, {
"type": "commonly-used",
"content": {
"title": "github",
"link": "https://",
}
}, {
"type": "search-type",
"content": {
"title": "wikipedia",
"link": "https://xx",
}
}, {
"type": "study-type",
"content": {
"title": "vue",
"link": "https://",
}
}, {
"type": "study-type",
"content": {
"title": "react",
"link": "https://",
}
}];
////////////////////
let tempJson = {};
ary.forEach(data => tempJson[data.type] = [])
ary.forEach(data => {
for (const key in tempJson) {
if (data.type === key) {
tempJson[key].push(data.content);
break;
}
}
})
for (const classification in tempJson) {
const wrapNode = `<div class="item-wrap">
<h3>${classification}</h3>
${(() => {
let contentWrapNode = ``;
tempJson[classification].forEach(item => {
const itemNode = `<div class="items">${item.title}</div>`;
contentWrapNode += itemNode;
})
return contentWrapNode;
})()}
</div>`;
document.querySelector('.container').innerHTML += wrapNode;
}
<div class="container"></div>
The data obtained are without order. I just classify them. How should they be sorted?
You can get a sorted version of tempJson by first extracting the key value pairs from it, and then sorting it with a callback function that deals with "commonly-used" separately:
let ary = [{
"type": "search-type",
"content": {
"title": "google",
"link": "https://",
}
}, {
"type": "study-type",
"content": {
"title": "w3c",
"link": "https://",
}
}, {
"type": "movie-type",
"content": {
"title": "movie site",
"link": "https://",
}
}, {
"type": "commonly-used",
"content": {
"title": "stackoverflow",
"link": "https://",
}
}, {
"type": "tool-type",
"content": {
"title": "remove bg",
"link": "https://www//",
}
}, {
"type": "movie-type",
"content": {
"title": "movie site2",
"link": "https://",
}
}, {
"type": "commonly-used",
"content": {
"title": "github",
"link": "https://",
}
}, {
"type": "search-type",
"content": {
"title": "wikipedia",
"link": "https://xx",
}
}, {
"type": "study-type",
"content": {
"title": "vue",
"link": "https://",
}
}, {
"type": "study-type",
"content": {
"title": "react",
"link": "https://",
}
}];
////////////////////
let tempJson = {};
ary.forEach(data => tempJson[data.type] = [])
ary.forEach(data => {
for (const key in tempJson) {
if (data.type === key) {
tempJson[key].push(data.content);
break;
}
}
})
let sorted = Object.entries(tempJson).sort(([a], [b]) =>
(a != "commonly-used") - (b != "commonly-used") ||
a.localeCompare(b)
);
for (const [classification, items] of sorted) {
const wrapNode = `<div class="item-wrap">
<h3>${classification}</h3>
${(() => {
let contentWrapNode = ``;
items.forEach(item => {
const itemNode = `<div class="items">${item.title}</div>`;
contentWrapNode += itemNode;
})
return contentWrapNode;
})()}
</div>`;
document.querySelector('.container').innerHTML += wrapNode;
}
<div class="container"></div>

add new key/value to each object inside a huge deeply nested objects

I have a huge object, with almost 50k lines.
I need to add in each object new key with the current path of the node
Example:
let obj = {
"title": "RESSONÂNCIA MAGNÉTICA DA COLUNA LOMBAR",
"data": [{
"title": "Método:",
"data": [{
"title": "Procedimento 1",
"data": [{
"title": "CONTRASTE"
},
{
"title": "CONTRASTE 2"
}
]
},
{
"title": "Procedimento 2",
"data": [{
"title": "CONTRASTE 3"
},
{
"title": "CONTRASTE 4"
}
]
}
]
}]
}
And I need to change my object to return this:
obj = {
"path": "$",
"title": "RESSONÂNCIA MAGNÉTICA DA COLUNA LOMBAR",
"data": [{
"path": "$.data.0",
"title": "Método:",
"data": [{
"path": "$.data.0.data.0",
"title": "Procedimento 1",
"data": [{
"path": "$.data.0.data.0.data.0",
"title": "CONTRASTE"
},
{
"path": "$.data.0.data.0.data.1",
"title": "CONTRASTE 2"
}
]
},
{
"path": "$.data.0.data.1",
"title": "Procedimento 2",
"data": [{
"path": "$.data.0.data.1.data.0",
"title": "CONTRASTE 3"
},
{
"path": "$.data.0.data.1.data.1",
"title": "CONTRASTE 4"
}
]
}
]
}]
}
If you notice, i added the key path inside each object for key data, with the current path of the node. This is what I need.
All my object are much bigger then this example, with much more nested objects
Performant version in case you have to deal with large files
const data = {
"title": "RESSONÂNCIA MAGNÉTICA DA COLUNA LOMBAR",
"data": [{
"title": "Método:",
"data": [{
"title": "Procedimento 1",
"data": [{
"title": "CONTRASTE"
},
{
"title": "CONTRASTE 2"
}
]
},
{
"title": "Procedimento 2",
"data": [{
"title": "CONTRASTE 3"
},
{
"title": "CONTRASTE 4"
}
]
}
]
}]
}
function PathUpdate(data, path) {
data.path = path;
const nd = data.data;
if (nd == null) return;
for (let i = 0; i < nd.length; i++) {
PathUpdate(nd[i], `${path}.data.${i}`);
}
}
console.log("before", { data });
PathUpdate(data, "$");
console.log("after", { data });
const data = {
title: "RESSONÂNCIA MAGNÉTICA DA COLUNA LOMBAR",
type: "template",
data: [
{
title: "Método:",
type: "top-level-component",
data: [
{
title: "Procedimento",
type: "navigation",
normal: "CONTRASTE",
checked: true,
data: [
{
type: "single-selection",
title: "CONTRASTE",
},
{
type: "single-selection",
title: "CONTRASTE 2",
},
],
},
{
title: "Procedimento 2",
type: "navigation",
normal: "CONTRASTE",
checked: false,
data: [
{
type: "single-selection",
title: "CONTRASTE 3",
},
{
type: "single-selection",
title: "CONTRASTE 4",
},
],
},
],
},
],
};
function addPathToObj(obj, path) {
obj.path = path;
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
value.forEach((item, index) => {
addPathToObj(item, `${path}.data.${index}`);
});
}
}
}
addPathToObj(data, "$");
console.log(data);

Reorder an array from a specific data

I have absolutely no idea of which title I could write.
Actually, here is what I get from API :
[
{
"order": 1,
"role": {
"label": "singer"
},
"artist": {
"name": "AaRON"
}
},
{
"order": 1,
"role": {
"label": "author"
},
"artist": {
"name": "Simon Buret"
}
},
{
"order": 2,
"role": {
"label": "author"
},
"artist": {
"name": "Olivier Coursier"
}
},
{
"order": 1,
"role": {
"label": "composer"
},
"artist": {
"name": "John Doe"
}
}
]
And here is what I need to send :
"artist": {
"singer": [
"AaRON"
],
"author": [
"Simon Buret",
"Olivier Coursier"
]
}
Of course, the order property must be taken in account.
Example : Simon Buret is the first item because he has the order set to 1.
I have absolutely no idea how to implement that, I just did a map, but don't know what to put inside :/
this.artistControl.controls.map(artistControl => {
...
});
Is there a way to do what I need ?
Does this work for you:
let arr = [
{ "order": 1, "role": { "label": "singer" }, "artist": { "name": "AaRON" } },
{ "order": 1, "role": { "label": "author" }, "artist": { "name": "Simon Buret" } },
{ "order": 2, "role": { "label": "author" }, "artist": { "name": "Olivier Coursier" } },
{ "order": 1, "role": { "label": "composer" }, "artist": { "name": "John Doe" } }
];
let obj = {'artist': {}};
arr.forEach(a => {
obj['artist'][a.role.label] = obj['artist'][a.role.label] || [];
obj['artist'][a.role.label][a.order-1] = a.artist.name;
});
console.log(obj);
You could use reduce method with object as a accumulator param and then check if the key doesn't exist create it with empty array as value and then add names by order.
const data = [{"order":1,"role":{"label":"singer"},"artist":{"name":"AaRON"}},{"order":1,"role":{"label":"author"},"artist":{"name":"Simon Buret"}},{"order":2,"role":{"label":"author"},"artist":{"name":"Olivier Coursier"}},{"order":1,"role":{"label":"composer"},"artist":{"name":"John Doe"}}]
const result = data.reduce((r, {
role: { label },
artist: { name },
order
}) => {
if (name) {
if (!r[label]) r[label] = [];
r[label][order - 1] = name;
}
return r;
}, {})
console.log(result)
const array = [{"order":1,"role":{"label":"singer"},"artist":{"name":"AaRON"}},{"order":1,"role":{"label":"author"},"artist":{"name":"Simon Buret"}},{"order":2,"role":{"label":"author"},"artist":{"name":"Olivier Coursier"}},{"order":1,"role":{"label":"composer"},"artist":{"name":"John Doe"}}];
const result = array
.sort((item1, item2) => item1.order - item2.order)
.reduce((acc, { role, artist }) => ({
...acc,
artist: {
...acc.artist,
[role.label]: [
...(acc.artist[role.label] || []),
artist.name,
],
},
}), { artist: {} });
console.log(result);
Here is another approach with es5
const data = [{ "order": 1, "role": { "label": "singer" }, "artist": { "name": "AaRON" } }, { "order": 1, "role": { "label": "author" }, "artist": { "name": "Simon Buret" } }, { "order": 2, "role": { "label": "author" }, "artist": { "name": "Olivier Coursier" } }, { "order": 1, "role": { "label": "composer" }, "artist": { "name": "John Doe" } }];
var result = data.reduce(function(map, obj) {
map["artist"] = map["artist"] || {};
if (obj.role.label === 'author' || obj.role.label === 'singer') {
map["artist"][obj.role.label] = map["artist"][obj.role.label] || [];
map["artist"][obj.role.label][obj.order - 1] = obj.artist.name;
}
return map;
}, {});
console.log(result)

Filtering array by value from deeply nested array

I'm trying to filter posts by category for a blog that uses React.
Here is what the data looks like:
const posts = [
{
"categories": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag3"
}
],
"title": "First post",
// more stuff here
},
{
"categories": [
{
"title": "tag2"
},
{
"title": "tag3"
},
{
"title": "tag4"
},
{
"title": "tag5"
}
],
"title": "Second post"
// more stuff here
},
{
"categories": [
{
"title": "tag1"
},
{
"title": "tag3"
},
{
"title": "tag4"
}
],
"title": "Third post"
// more stuff here
},
{
"categories": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag4"
},
{
"title": "tag5"
}
],
"title": "Fourth post"
// more stuff here
}
]
I have a piece of state called filter that updated from a <select> menu, which looks like this:
const [filter, setFilter] = useState('all');
How would I write the rest of this code block?
useEffect(() => {
if (filter !== 'all') {
// Not sure what to do here
}
}, [])
Not sure if I use filter on the first array, or do I keep using filter all the way down? I've tried a bunch of things, but haven't been able to get it to work.
This is how you can filter an object like that:
const posts = [
{
"categories": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag3"
}
],
"title": "First post"
},
{
"categories": [
{
"title": "tag2"
},
{
"title": "tag3"
}
],
"title": "Second Post"
}
];
const filter = "tag1";
const filtered = posts.filter(post => {
return post.categories.some(cat => cat.title === filter)
});
console.log(filtered);
Now in a functional component, there's really no need to use an effect, this is simple run on your state every time the component is rendered:
function MyComponent() {
const [filter, setFilter] = useState("all");
const filtered =
filter === "all"
? posts
: posts.filter((post) => {
return post.categories.some((cat) => cat.title === filter);
});
return <>Render stuff here</>
}
If your component updates a lot and the filter is running too much, you can look into using a useMemo hook to memoize the value until the filter changes.

Categories