Just started diving into the array of objects sorting in JavaScript, having come up with the following snippet. Is there a chance this code can somehow be optimised for performance or does this look generally legit?
const products = [
{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: 'hot_dishes',
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
},
{
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: 'dessert',
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const sorted = products
.map(({ category }) => category)
.sort((a, b) => parseFloat(a.priority) - parseFloat(b.priority))
.map(({ title }) => (
products.filter(({ category: { title: cTitle } }) => title === cTitle)
));
console.log(sorted);
Workflow:
Destructure category from each product
Sort the in ascending order
Filter in separate arrays, based on the category title
My current biggest concern is about the following line:
.map(({ title }) => (
products.filter(({ category: { title: cTitle } }) => title === cTitle)
));
You are indirectly sorting the array using priority, But you don't have to go through all the steps. You can just use sort on the products also
const sorted = products.sort((a, b) => a.category.priority - b.category.priority);
const products = [
{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: "hot_dishes",
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}, {
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: "dessert",
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const sorted = products.sort((a, b) => a.category.priority - b.category.priority);
console.log(sorted);
/* This is not a part of answer. It is just to give the output full height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can optimize this further, and there are some edge cases to watch out for based on the shape of your input.
On thing you could do overall is to normalize categories into a separate collection and reference the category id in the product. This would remove the possibility of having the same category title with different priorities in different objects.
You also haven't handled the sorting behavior of multiple categories with the same priority in a way that guarantees sort order, you should sort by priority and alpha by title to fix that. This sort call relies on the browser's implementation of sort() and therefore should be well optimized.
Your algorithm maps over product and filters product for each iteration of the map, which is O(n^2) time complexity. Making this a O(n) algorithm will save you a ton of time on a big dataset.
I've added a snippet that de-duplicates the categories using a Map before sorting, and caches the category array index before grouping using a Map there too. Those optimizations are admittedly pretty small compared to the overall improvement of finding a single pass algorithm for grouping your products.
An empty 2 dimensional array is created from the deduped and sorted category array. Then we iterate over the products array and add the products to the proper category array.
We've made the sort a little faster and more accurate; everything else is now accomplished in amortized constant time.
const products = [{
title: "Test product1",
description: "Test description",
price: 5,
category: {
title: 'hot_dishes',
priority: 2
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
},
{
title: "Test product2",
description: "Test description",
price: 5,
category: {
title: 'dessert',
priority: 1
},
ru: {
title: "Тестовый продукт",
description: "пошел на хуй"
}
}
];
const categoryComparator = (a, b) => {
const priorityComparison = parseFloat(a.priority) - parseFloat(b.priority)
if ( priorityComparison !== 0 ) return priorityComparison
return a.title.localeCompare(b)
}
const categoryMap = new Map()
products.forEach(product =>
categoryMap.set(product.category.title, product.category)
)
const sortedCategories = Array.from(categoryMap, ([title, category]) => category)
.sort(categoryComparator)
.map(category => category.title)
const categoryIndexMap = new Map([...new Set(sortedCategories)].map((category, index) => [category, index]))
const categorizedProductArrays = Array.from({
length: categoryIndexMap.size
}, i => [])
products.forEach((product) =>
categorizedProductArrays[categoryIndexMap.get(product.category.title)].push(product))
console.log(categorizedProductArrays)
Related
I have this data
var data = [
{
title: "App development summary",
category: [],
},
{
title: "to experiment 2",
category: [],
},
{
title: "Some of these books I have read",
category: [
{
_id: "5f7c99faab20d14196f2062e",
name: "books",
},
{
_id: "5f7c99faab20d14196f2062f",
name: "to read",
},
],
},
{
title: "Quora users and snippets",
category: [
{
_id: "5f7c99feab20d14196f20631",
name: "quora",
},
],
},
{
title: "Politics to research",
category: [
{
_id: "5f7c9a02ab20d14196f20633",
name: "politics",
},
],
},
];
Say I want to get all the entries with the category of books. I tried doing this:
var bookCat = data.map(note => {
return note.category.map(cat => {
if(cat.name === "books" ) return note
})
})
But the result comes back with some empty and undefined arrays.
I was able to filter the empty arrays (at one point) but not the undefined
Edit
In plain English "if the object ha category.name "books", give me the title"
Filter by whether the array's category contains at least one name which is books:
var data=[{title:"App development summary",category:[]},{title:"to experiment 2",category:[]},{title:"Some of these books I have read",category:[{_id:"5f7c99faab20d14196f2062e",name:"books"},{_id:"5f7c99faab20d14196f2062f",name:"to read"}]},{title:"Quora users and snippets",category:[{_id:"5f7c99feab20d14196f20631",name:"quora"}]},{title:"Politics to research",category:[{_id:"5f7c9a02ab20d14196f20633",name:"politics"}]}];
const output = data
.filter(item => item.category.some(
({ name }) => name === 'books'
));
console.log(output);
If there's guaranteed to be only a single matching object, use .find instead:
var data=[{title:"App development summary",category:[]},{title:"to experiment 2",category:[]},{title:"Some of these books I have read",category:[{_id:"5f7c99faab20d14196f2062e",name:"books"},{_id:"5f7c99faab20d14196f2062f",name:"to read"}]},{title:"Quora users and snippets",category:[{_id:"5f7c99feab20d14196f20631",name:"quora"}]},{title:"Politics to research",category:[{_id:"5f7c9a02ab20d14196f20633",name:"politics"}]}];
const output = data
.find(item => item.category.some(
({ name }) => name === 'books'
));
console.log(output);
Task
There is an array of objects of the form:
{
title: 'Macbook Air',
offers: [
{
seller: 'Avic',
price: 1200
},
{
seller: 'Citrus',
price: 1600
}
]
}
It is necessary to Convert to an array of objects of the form:
{
title: 'Macbook Air',
bestOffer: {
name: 'Avic',
price: 1200
}
}
Where bestOffer is an offer with a minimum price value.
Write in one line using map() and find().
Code
"use_strict";
var productList = [
{
title: "Product X1",
offers: [
{
seller: "Company X1",
price: 400
},
{
seller: "Company X2",
price: 200
},
{
seller: "Company X3",
price: 300
}
]
},
{
title: "Product Y1",
offers: [
{
seller: "Company Y1",
price: 1700
},
{
seller: "Company Y2",
price: 1600
},
{
seller: "Company Y3",
price: 1500
},
{
seller: "Company Y4",
price: 1400
}
]
},
{
title: "Product Z1",
offers: [
{
seller: "Company Z1",
price: 50
},
{
seller: "Company Z2",
price: 60
},
{
seller: "Company Z3",
price: 10
},
{
seller: "Company Z4",
price: 90
},
{
seller: "Company Z5",
price: 70
}
]
}
];
const destinations = productList.map(item =>
({
title: item.title,
bestOffer: ({
name: item.offers[0].seller, // TODO: How to implement find() method????
price: item.offers[0].price
})
})
)
console.log(destinations)
My code work for restructuring objects for new array, BUT i dont know how to implement find() method for searching minimal value. Is it possible using find() method in this case??
You can use Array.reduce() to find the best offer by price. On each iteration check if the current item's (o) price is less than the accumulator's (r) price, and take the one with the lowest.
const productList = [{"title":"Product X1","offers":[{"seller":"Company X1","price":400},{"seller":"Company X2","price":200},{"seller":"Company X3","price":300}]},{"title":"Product Y1","offers":[{"seller":"Company Y1","price":1700},{"seller":"Company Y2","price":1600},{"seller":"Company Y3","price":1500},{"seller":"Company Y4","price":1400}]},{"title":"Product Z1","offers":[{"seller":"Company Z1","price":50},{"seller":"Company Z2","price":60},{"seller":"Company Z3","price":10},{"seller":"Company Z4","price":90},{"seller":"Company Z5","price":70}]}]
const findBestOffer = ({ offers = [] }) => offers
.reduce((r, o) => o.price < r.price ? o : r)
const formatOffer = item => item ? ({
name: item.seller,
price: item.price
}) : 'none'
const destinations = productList.map(item => ({
title: item.title,
bestOffer: formatOffer(findBestOffer(item))
}))
console.log(destinations)
If you need to use Array.map() and Array.find() - map the array to the price numbers, and get the lowest one using Math.min(), and then find the item with that price:
const productList = [{"title":"Product X1","offers":[{"seller":"Company X1","price":400},{"seller":"Company X2","price":200},{"seller":"Company X3","price":300}]},{"title":"Product Y1","offers":[{"seller":"Company Y1","price":1700},{"seller":"Company Y2","price":1600},{"seller":"Company Y3","price":1500},{"seller":"Company Y4","price":1400}]},{"title":"Product Z1","offers":[{"seller":"Company Z1","price":50},{"seller":"Company Z2","price":60},{"seller":"Company Z3","price":10},{"seller":"Company Z4","price":90},{"seller":"Company Z5","price":70}]}]
const findBestOffer = ({ offers = [] }) => {
const min = Math.min(...offers.map(o => o.price))
return offers.find(o => o.price === min)
}
const formatOffer = item => item ? ({
name: item.seller,
price: item.price
}) : 'none'
const destinations = productList.map(item => ({
title: item.title,
bestOffer: formatOffer(findBestOffer(item))
}))
console.log(destinations)
Since you prefer to use FIND and MAP, I have a clearer solution for you then.
// product list
const productList = [{"title":"Product X1","offers":[{"seller":"Company X1","price":400},{"seller":"Company X2","price":200},{"seller":"Company X3","price":300}]},{"title":"Product Y1","offers":[{"seller":"Company Y1","price":1700},{"seller":"Company Y2","price":1600},{"seller":"Company Y3","price":1500},{"seller":"Company Y4","price":1400}]},{"title":"Product Z1","offers":[{"seller":"Company Z1","price":50},{"seller":"Company Z2","price":60},{"seller":"Company Z3","price":10},{"seller":"Company Z4","price":90},{"seller":"Company Z5","price":70}]}]
// Search text from Collection
function searchProduct(niddle, haystack){
const searchResult = haystack.find(item=> item['title'] === niddle);
let bestOffer;
if(searchResult){
bestOffer = getBestOffer(searchResult);
}
return bestOffer;
}
// Search for best Offer
function getBestOffer(dataInfo){
let bestOffer = {seller: null,price: null};
dataInfo['offers'].map((item, index)=>{
if(bestOffer.price === null || bestOffer.price > item.price){
bestOffer = item;
}
})
return bestOffer;
}
//run
console.log( searchProduct("Product Z1", productList) );
//output is
// Object {seller: "Company Z3", price: 10}
I hope you like it.
I need to create a Graphql query that outputs data from two arrays of objects. The arrays are:
const authors = [
{
name: 'Robert Martin',
id: 'afa51ab0-344d-11e9-a414-719c6709cf3e',
born: 1952
},
{
name: 'Martin Fowler',
id: 'afa5b6f0-344d-11e9-a414-719c6709cf3e',
born: 1963
},
{
name: 'Fyodor Dostoevsky',
id: 'afa5b6f1-344d-11e9-a414-719c6709cf3e',
born: 1821
},
{
name: 'Joshua Kerievsky', // birthyear not known
id: 'afa5b6f2-344d-11e9-a414-719c6709cf3e'
},
{
name: 'Sandi Metz', // birthyear not known
id: 'afa5b6f3-344d-11e9-a414-719c6709cf3e'
}
];
And:
const books = [
{
title: 'Clean Code',
published: 2008,
author: 'Robert Martin',
id: 'afa5b6f4-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring']
},
{
title: 'Agile software development',
published: 2002,
author: 'Robert Martin',
id: 'afa5b6f5-344d-11e9-a414-719c6709cf3e',
genres: ['agile', 'patterns', 'design']
},
{
title: 'Refactoring, edition 2',
published: 2018,
author: 'Martin Fowler',
id: 'afa5de00-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring']
},
{
title: 'Refactoring, edition 3',
published: 2018,
author: 'Martin Fowler',
id: 'afa5de00-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring']
},
{
title: 'Refactoring, edition 4',
published: 2018,
author: 'Martin Cowler',
id: 'afa5de00-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring']
},
{
title: 'Refactoring to patterns',
published: 2008,
author: 'Joshua Kerievsky',
id: 'afa5de01-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring', 'patterns']
},
{
title: 'Practical Object-Oriented Design, An Agile Primer Using
Ruby',
published: 2012,
author: 'Sandi Metz',
id: 'afa5de02-344d-11e9-a414-719c6709cf3e',
genres: ['refactoring', 'design']
},
{
title: 'Crime and punishment',
published: 1866,
author: 'Fyodor Dostoevsky',
id: 'afa5de03-344d-11e9-a414-719c6709cf3e',
genres: ['classic', 'crime']
},
{
title: 'The Demon ',
published: 1872,
author: 'Fyodor Dostoevsky',
id: 'afa5de04-344d-11e9-a414-719c6709cf3e',
genres: ['classic', 'revolution']
}
];
The desired output format for a query like this:
query {
allAuthors {
name
bookCount
}
}
is like so:
"data": {
"allAuthors": [
{
"name": "Robert Martin",
"bookCount": 2
},
{
"name": "Martin Fowler",
"bookCount": 1
},
{
"name": "Fyodor Dostoevsky",
"bookCount": 2
},
{
"name": "Joshua Kerievsky",
"bookCount": 1
},
{
"name": "Sandi Metz",
"bookCount": 1
}
]
}
I've found a way to count the amount of books for each author and output the data in the desired format (a good example of that here: Summarize count of occurrences in an array of objects with Array#reduce). However this approach ignores other fields in the data, such as "born" and "genres". If I was to expand the query like so:
query {
allAuthors {
name
bookCount
born
}
}
It wouldn't output anything for the field "born". What would be the smart way to create the query resolver? Spread operator? Reduce?
* EDIT *
My unnecessarily complicated solution for counting the books here:
const newBooks = books.reduce((acc, cv) => {
const arr = acc.filter(obj => {
return obj.author === cv.author;
});
if (arr.length === 0) {
acc.push({ name: cv.author, born: cv.born, bookCount: 1 });
} else {
arr[0].bookCount += 1;
}
return acc;
}, []);
const array = [];
books.forEach(book => {
const object = {
name: book.author
};
array.push(object);
return array;
});
const unique = array.map(a => a.name);
result = {};
for (var i = 0; i < unique.length; ++i) {
if (!result[unique[i]]) result[unique[i]] = 0;
++result[unique[i]];
}
const entries = Object.entries(result);
const finalAnswer = [];
entries.forEach(entry => {
const object = {
name: entry[0],
bookCount: entry[1]
};
finalAnswer.push(object);
return finalAnswer;
});
console.log(finalAnswer);
You could map the authors and use filter to get the bookCount for each author
const authors=[{name:'Robert Martin',id:'afa51ab0-344d-11e9-a414-719c6709cf3e',born:1952},{name:'Martin Fowler',id:'afa5b6f0-344d-11e9-a414-719c6709cf3e',born:1963},{name:'Fyodor Dostoevsky',id:'afa5b6f1-344d-11e9-a414-719c6709cf3e',born:1821},{name:'Joshua Kerievsky',id:'afa5b6f2-344d-11e9-a414-719c6709cf3e'},{name:'Sandi Metz',id:'afa5b6f3-344d-11e9-a414-719c6709cf3e'}],
books=[{title:'Clean Code',published:2008,author:'Robert Martin',id:'afa5b6f4-344d-11e9-a414-719c6709cf3e',genres:['refactoring']},{title:'Agile software development',published:2002,author:'Robert Martin',id:'afa5b6f5-344d-11e9-a414-719c6709cf3e',genres:['agile','patterns','design']},{title:'Refactoring, edition 2',published:2018,author:'Martin Fowler',id:'afa5de00-344d-11e9-a414-719c6709cf3e',genres:['refactoring']},{title:'Refactoring, edition 3',published:2018,author:'Martin Fowler',id:'afa5de00-344d-11e9-a414-719c6709cf3e',genres:['refactoring']},{title:'Refactoring, edition 4',published:2018,author:'Martin Cowler',id:'afa5de00-344d-11e9-a414-719c6709cf3e',genres:['refactoring']},{title:'Refactoring to patterns',published:2008,author:'Joshua Kerievsky',id:'afa5de01-344d-11e9-a414-719c6709cf3e',genres:['refactoring','patterns']},{title:'Practical Object-Oriented Design, An Agile Primer Using Ruby ',published:2012,author:'Sandi Metz',id:'afa5de02-344d-11e9-a414-719c6709cf3e',genres:['refactoring','design']},{title:'Crime and punishment',published:1866,author:'Fyodor Dostoevsky',id:'afa5de03-344d-11e9-a414-719c6709cf3e',genres:['classic','crime']},{title:'The Demon ',published:1872,author:'Fyodor Dostoevsky',id:'afa5de04-344d-11e9-a414-719c6709cf3e',genres:['classic','revolution']}];
const output = authors.map(({ born, name }) => {
const bookCount = books.filter(b => b.author === name).length;
return { name, born, bookCount }
})
console.log(output)
I think you can add a statement to your reducer function to add the desired fields. I added the single line, and annotated the rest of the method so you can see what's going on:
const newBooks = books.reduce((acc, cv) => {
// acc is an "accumulation" of the results so far.
// cv is the next item that hasn't been processed.
// Search for author in "accumulator" array acc. Put results in arr.
const arr = acc.filter(obj => {
return obj.author === cv.author;
});
if (arr.length === 0) {
// Haven't seen this author, yet. Add new item to "accumulator" array.
acc.push({ name: cv.author, born: cv.born, bookCount: 1 });
} else {
// This author already exists in "accumulator" array, referenced by arr[0].
// Update pre-existing item.
arr[0].bookCount += 1;
arr[0].born = cv.born; // <-- This is the new code that is required.
}
return acc;
}, []);
I have a react live search dropdown component that filters through an array of objects by a search term. It filters my objects by title and then returns a list of all the related objects. This works fine.
Current:
Data Structure
data: [
{ id: 1, title: 'Some title here' },
{ id: 2, title: 'Another title' },
{ id: 3, title: 'last title' },
]
Component
<LiveSearch
term={term}
data={data} />
Inside Live search component
Filter data by term and render list
return data
.filter(item => item.title.toLowerCase().includes(term.toLowerCase())
.map((item, idx) => <li key={idx}>{item.title}</li>
My objects to search by are getting more advanced and what I would like to be able to do is pass into my component an array of property names I would like to compare to the search term.
My thinking process behind it is to loop through the object properties and if on of the properties matches the term the loop breaks and returns true adding that object to the list of items to be displayed.
Goal
Data Structure
data: [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
]
Component
<LiveSearch
searchFields={['country', 'title']}
term={term}
data={data} />
Inside Component filtering
return data
.filter(item => {
// Dynamic filtering of terms here
})
.map((item, idx) => <li key={idx}>{item.title}</li>
Inside the filter I'm trying to get a loop through the array and dynamically produce logic similar to this
item.searchFields[0].toLowerCase().includes(term.toLowerCase()) ||
item.searchFields[1].toLowerCase().includes(term.toLowerCase())
But obviously could loop over an infinite number of searchfields/properties
Use Array#some()
Something like
term = term.toLowerCase()
return data
.filter(item => {
return searchFields.some(field => item[field].toLowerCase().includes(term))
}).map(...
Check if some of the searchFields match:
// Checks wether a value matches a term
const matches = (value, term) => value.toLowerCase().includes(term.toLowerCase());
// Checks wether one of the fields in the item matcues the term
const itemMatches = (fields, term) => item => fields.some(field => matches(item[field], term);
// Filter the data to only contain items where on of the searchFields matches the term
const result = props.data.filter( itemMatches(props.searchFields, props.term) );
return result.map(item => <li key={idx}>{item.title}</li>);
You can use Array .some combined with .filter
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
let data = [
{ id: 1, country: 'Canada', title: 'Some title here' },
{ id: 2, country: 'Australia', title: 'Another title' },
{ id: 3, country: 'Netherlands', title: 'last title' },
], searchFields = ["country", "title"], term = "canada";
let result = data.filter(obj =>
searchFields.some(s =>
obj[s] != undefined && obj[s].toLowerCase() === term
));
console.log(result);
I am trying to spread array values into an object after a certain index 3 (column_4).
const array = ['Column_5', 'Column 6', 'Column 7']
const object = {
column_1: '',
column_2: 'Status',
column_3: 'Master',
column_4: 'Locale',
...array
}
At the moment, Column 5/6/7 appear at the start:
{
0: "Column_5",
1: "Column 6",
2: "Column 7",
column_1: "",
column_2: "Status",
column_3: "Master",
column_4: "Locale"
}
But I need them to appear in numerical order. Any ideas? I've tried using Ramda's insert method without any success.
const array = ['column_5', 'column_6', 'column_7']
const object = {
column_1: '',
column_2: 'Status',
column_3: 'Master',
column_4: 'Locale',
...array.reduce((acc, item) => {
acc[item] = item;
return acc;
}, {}),
}
console.log(object);