Quicksort for array of objects fails - javascript

I couldn't find the answer for this on Stack Overflow.
I have a function called quickSort:
function quickSort(array, prop) {
if (array.length <= 1) return array;
const pivot = array[0]; // I've tried array[0][prop] but it doesn't work
const left = [];
const right = [];
for (let i = 1; i < array.length; i++) {
if (array[i][prop] < pivot) {
left.push(array[i]);
} else {
right.push(array[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(quickSort(data, 'motor'));
And I want sort this array of objects by motor:
let data = [
{
"color": "A",
"door": 1,
"wheel": 3,
"year": 1963,
"brand": "GMC",
"sold": false,
"owner": "Chalmers Boobyer",
"motor": 2.6,
"assembled": "20/08/2021"
},
{
"color": "B",
"door": 2,
"wheel": 2,
"year": 1980,
"brand": "Ford",
"sold": false,
"owner": "Angelia Cromett",
"motor": 2.5,
"assembled": "02/05/2021"
},
{
"color": "C",
"door": 3,
"wheel": 1,
"year": 1999,
"brand": "Audi",
"sold": false,
"owner": "Barth Pickring",
"motor": 2.0,
"assembled": "15/02/2021"
},
{
"color": "D",
"door": 4,
"wheel": 1,
"year": 2008,
"brand": "Hyundai",
"sold": true,
"owner": "Aurore Soaper",
"motor": 1.2,
"assembled": "02/01/2019"
}
];
Can someone explain how I can make array[0][prop] work?

I suggest reading How to Debug Small Programs. Adding a couple of strategic console.log statements before the comparison and at the start of the function to display the parameters is enough to show the problems. I did it for you this time, but you'll need to be able to debug your own programs if you want to make progress as a programmer.
A couple issues:
Your recursive calls quickSort(left) and quickSort(right) are missing the second parameter prop.
array[i][prop] < pivot is wrong if pivot is defined as array[0] because it compares different types. That should be const pivot = array[0][prop] as you attempted, but then you'd have to change your returned array to be
[...quickSort(left, prop), array[0], ...quickSort(right, prop)];
Here are the fixes applied:
const quickSort = (array, prop) => {
if (array.length <= 1) return array;
// warning: bad pivot
const pivot = array[0][prop];
const left = []; // warning: allocations
const right = [];
for (let i = 1; i < array.length; i++) {
if (array[i][prop] < pivot) {
left.push(array[i]);
}
else {
right.push(array[i]);
}
}
return [
...quickSort(left, prop),
array[0],
...quickSort(right, prop)
]; // warning: copies
};
const data = [
{
color: "A",
door: 1,
wheel: 3,
year: 1963,
brand: "GMC",
sold: false,
owner: "Chalmers Boobyer",
motor: 2.6,
assembled: "20/08/2021",
},
{
color: "B",
door: 2,
wheel: 2,
year: 1980,
brand: "Ford",
sold: false,
owner: "Angelia Cromett",
motor: 2.5,
assembled: "02/05/2021",
},
{
color: "C",
door: 3,
wheel: 1,
year: 1999,
brand: "Audi",
sold: false,
owner: "Barth Pickring",
motor: 2.0,
assembled: "15/02/2021",
},
{
color: "D",
door: 4,
wheel: 1,
year: 2008,
brand: "Hyundai",
sold: true,
owner: "Aurore Soaper",
motor: 1.2,
assembled: "02/01/2019",
},
];
console.log(quickSort(data, "motor"));
After the bug fixes, the sort still has issues:
Recursive sorts shouldn't be allocating memory and copying arrays. Use indices to determine the recursive subarrays and swaps to move elements into the correct subarrays. This makes the code harder to write and read, but provides significant performance improvements.
Choosing the pivot as 0 means you'll hit quadratic complexity and risk blowing the call stack on already-sorted data. Research and choose a better pivot-selection method.
Specifying prop is pretty limited. Better to provide a callback similar to the built-in sort, so you can sort any objects rather than just a top-level prop.

Related

how to change JSON Nested array property "project_id" with "project_name"

Trouble:
[
{
"project_id": 1,
"project_name": "CDP",
"role": "PL"
},
{
"project_id": 2,
"project_name": "Admincer",
"role": "PM"
},
I want to add the "project_id" property from the above three properties to another array using some method.
My idea is: 1. First of all, if I could copy the "project_id" property of this array to the second Nested JSON array, it would be fine.
What I looked up:
const obj = {
"project_id": 1,
"project_name": "CDP",
"role": "PL"
};;
const objCopy = {
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": 1, 1,
"work_day_id": 45,
"time_cards": [
{
... obj
}
]
};;
console.log (objCopy);
I found that I could copy it this way.
I tried the above code in Chrome Console.
The array was copied, but the entire object was copied. I just want to copy the properties of project_id.
I want to create a new property called "prj_name" in this array and display only that property in Vuetify.
async fetchWorkerTimeCard() {
try {
this.worker_data = []
await this.$axios.$get('/worker_time_card', {
params: {
work_date: this.calendarVal
}
}).then(data => {
this.worker_data = data
})
var projects = await this.fetch_worker_projects()
console.log(projects)
} catch (error) {
console.log(error)
this.worker_data = []
}
},
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.5/vue.js"></script>
<v-card>
<v-data-table v-if="worker_data.time_cards" :headers="headers2" :items="worker_data.time_cards"></v-data-table>
</v-card>
You can simply change your object data like any other object in JS.
const obj = {
"project_id": 1,
"project_name": "CDP",
"role": "PL"
};
const objCopy = {
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": 1,
"work_day_id": 45
}
console.log({...obj, ...objCopy})
This will create 1 object that merged.
Or if you just want to project_id value then just change it like:
objCopy.project_id = obj.project_id
If I'm understanding your first question correctly, you might be interested in the map function, which allows you to create a new array from an existing array. So, for example, if the first snippet you posted is an array of objects we call projects, you could use:
var projectIds = projects.map(p => p.project_id), where projectIds would now be an array of just project ids.
It seems like you might be asking more than this though, so I second Bravo's request for more clarification/reorganization in your question.
I'm not pretty sure if you want either of the following results:
{
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": [
1,
1
],
"work_day_id": 45,
"time_cards": [
{
"project_id": 1
},
{
"project_id": 2
}
]
}
or this
{
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": [
1,
1
],
"work_day_id": 45,
"time_cards": [
"project_id": [1, 2]
}
In case you need the first scenario, the following code may help you:
// This function return an array with: [{project_id: Number}]
function onlyIds(obj) {
const ids = [];
// Iterate the obj Array
obj.forEach(element => {
// Push a new JSON: "{project_id: 1}" or whatever
ids.push({ project_id: element.project_id });
});
// return an array that only contains the project_id
return ids;
}
const obj = [
{
project_id: 1,
project_name: 'CDP',
role: 'PL',
},
{
project_id: 2,
project_name: 'Admincer',
role: 'PM',
},
];
const objCopy = {
start_time: '09:00:00',
end_time: '18:00:00',
rest_time: '01:00:00',
worked_time: '08:00:00',
is_wfh: true,
id: [1, 1],
work_day_id: 45,
time_cards: onlyIds(obj),
};
console.log(onlyIds(obj));
console.log(objCopy);
I'm pretty sure there should be any more elegant/optimal way (as using any kind of higher-order function I may be missing right now) but as far as I understood, this should do the job.

Taking JSON data, grouping by a property, and aggregating using multiple aggregation functions

Here is a sample of JSON data of fruit weights and prices:
let fruitData = [{"fruit":"apple","weight":12,"price":1.80},
{"fruit":"apple","weight":15,"price":2.00},
{"fruit":"apple","weight":10,"price":1.60},
{"fruit":"banana","weight":22,"price":3.00},
{"fruit":"banana","weight":24,"price":3.20}]
If I want to group by the "fruit" property and return mean "weight" and "price" values for each fruit, what do I do to achieve this? The end result would be something like:
aggFruitData = [{"fruit":"apple","weight":12.3333,"price":1.8},
{"fruit":"banana","weight":23,"price":3.1}]
The big hope is that the data can still be in an easy to manipulate form like JSON following the transformation. I know that SQL provides a groupby method, but I'm wondering if that is more efficient or if using native JS is more effective. Ideally, this could be something that could be scaled up, such as including another property to group by (maybe like month sold/month price was recorded). I'm open to using either vanilla JS methods or a library/framework meant to parse in this way— I just want efficiency with the project's execution
Using vanilla js:
let fruitData = [{
"fruit": "apple",
"weight": 12,
"price": 1.80
},
{
"fruit": "apple",
"weight": 15,
"price": 2.00
},
{
"fruit": "apple",
"weight": 10,
"price": 1.60
},
{
"fruit": "banana",
"weight": 22,
"price": 3.00
},
{
"fruit": "banana",
"weight": 24,
"price": 3.20
}
];
const aggregated = Object.values(fruitData.reduce((current, item) => {
if (!current[item.fruit]) {
current[item.fruit] = {
...item,
count: 1
};
} else {
const i = current[item.fruit];
i.weight = (i.weight * i.count + item.weight) / (i.count + 1);
i.price = (i.price * i.count + item.price) / (i.count + 1);
i.count++;
}
return current;
}, {})).map(({
fruit,
weight,
price
}) => ({
fruit,
weight,
price
}));
console.log(aggregated);
// [ { fruit: 'apple', weight: 12.333333333333334, price: 1.8 },
// { fruit: 'banana', weight: 23, price: 3.1 } ]
You could also use the lodash library (_.groupBy etc) functions for this. Note that the last .map is to strip out the count field only, but you may actually find it useful to have that!

Sort max date from Object

Trying to sort the object with the max date. One id may have multiples dates. Below is the format of the object where id:123 has two dates. So I am trying to take the max date for the user 123. I used the sort method and storing the array[0] but still there is something missing.
var arr = [
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "3212"
},
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
},
{
"score": 20,
"date": "2021-05-05T00:00:00"
}
],
"id": "123"
},
{
"scores": [
{
"score": 5,
"date": "2021-05-05T00:00:00"
}
],
"id": "321"
}
]
What I tried is
_.each(arr, function (users) {
users.scores = users.scores.filter(scores => new Date(Math.max.apply(null, scores.date)));
return users;
});
Expecting the output to look like the following with the max date selected.
[
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "3212"
},
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "123"
},
{
"scores": [
{
"score": 5,
"date": "2021-05-05T00:00:00"
}
],
"id": "321"
}
]
Your filter callback function is not performing a comparison to filter the correct element. Also, although applying the "maximum" algorithm on the dates as string would be fine in your case (because of the date format you have), it would be much safer to transform the date strings into date objects to consistantly get correct results regardless of the format.
In the solution below, you can use a combination of Array.map() and Array.sort() to copy and process your data in the correct result.
const data = [{
'scores': [{
'score': 10,
'date': '2021-06-05T00:00:00'
}],
'id': '3212'
}, {
'scores': [{
'score': 10,
'date': '2021-06-05T00:00:00'
}, {
'score': 20,
'date': '2021-05-05T00:00:00'
}],
'id': '123'
}, {
'scores': [{
'score': 5,
'date': '2021-05-05T00:00:00'
}],
'id': '321'
}];
// map the data and return the updated objects as the result
const result = data.map((user) => {
// copy the scores array to not mutate the original data
const sortedScores = user.scores.slice();
// sort the scores array by date descending
sortedScores.sort((a, b) => (new Date(b.date) - new Date(a.date)));
// return the same user with the first score from the sorted array
return {
...user,
scores: [sortedScores[0]]
};
});
console.log(result);

Convert object with nested objects into array of objects,re- groupped by inner object's keys

I have the Following JavaScript Object JSON1
{
"1": {
"Average": 32.31,
"Count": 19,
"Sum": 32.6,
"Color": "red"
},
"2": {
"Average": 32.72,
"Count": 18,
"Sum": 32.96,
"Color": "blue"
},
"3": {
"Average": 31.4,
"Count": 18,
"Sum": 31.48,
"Color": "green"
}
}
and I want to convert into the following format using javascript ES6 methods. JSON2
[{
"title": "Average",
"val1": 32.31,
"val2": 32.72,
"val3": 31.4
}, {
"title": "Count",
"val1": 19,
"val2": 18,
"val3": 18
}, {
"title": "Sum",
"val1": 32.6,
"val2": 32.96,
"val3": 31.48
}, {
"title": "Color",
"val1": "red",
"val2": "blue",
"val3": "green"
}]
Object.keys(json1).forEach((item, index) => {
let statsList = [];
Object.keys(json1[item]).forEach(objItem => {
statsList.push({
title: objItem,
val1: boxObj[1][objItem],
val2: boxObj[2][objItem],
val3: boxObj[3][objItem]
});
});
console.log(statsList)
});
var json1 = {
"1": {
"Average": 32.31,
"Count": 19,
"Sum": 32.6,
"Color": "red"
},
"2": {
"Average": 32.72,
"Count": 18,
"Sum": 32.96,
"Color": "blue"
},
"3": {
"Average": 31.4,
"Count": 18,
"Sum": 31.48,
"Color": "green"
}
};
Object.keys(json1).forEach((item, index) => {
let statsList = [];
Object.keys(json1[item]).forEach(objItem => {
statsList.push({
title: objItem,
val1: boxObj[1][objItem],
val2: boxObj[2][objItem],
val3: boxObj[3][objItem]
});
});
console.log(statsList)
});
Here in the JSON1, the Number of objects can be any number. It has to format it Dynamically. In the JSON2, Instead of val1,val2 anything can be used uniquely identifiable keys in all the objects present in the array. I have tried using forEach, I was able to achieve it with static keys provided, and with the multiple looping statements. I just want with dynamic keys and avoiding multiple loops and I want to know what is the best and easiest way to do this formatting in Javascript. Advance Thanks for your help.
You may traverse source Object.values() with Array.prototype.reduce() to make up an object that will map each category to all possible values.
Then, you may Array.prototype.map() resulting Object.entries() to return an array of objects with desired structure:
const src = {"1":{"Average":32.31,"Count":19,"Sum":32.6,"Color":"red"},"2":{"Average":32.72,"Count":18,"Sum":32.96,"Color":"blue"},"3":{"Average":31.4,"Count":18,"Sum":31.48,"Color":"green"}},
resultMap = Object
.values(src)
.reduce((r,o,i) => (
Object
.entries(o)
.forEach(([key,value]) =>
(r[key]=r[key]||{}, r[key][`value${i+1}`] = value))
,r),{}),
result = Object
.entries(resultMap)
.map(([name,{...values}]) => ({name,...values}))
console.log(result)
.as-console-wrapper{min-height:100%;}
If only unique items in each category are required, you may somewhat modify above solution, making use of Set():
const src = {"1":{"Average":32.31,"Count":19,"Sum":32.6,"Color":"red"},"2":{"Average":32.72,"Count":18,"Sum":32.96,"Color":"blue"},"3":{"Average":31.4,"Count":18,"Sum":31.48,"Color":"green"}},
resultMap = Object
.values(src)
.reduce((r,o,i) => (
Object
.entries(o)
.forEach(([key,value]) =>
(r[key]=r[key]||(new Set()), r[key].add(value)))
,r),{}),
result = Object
.entries(resultMap)
.map(([name,values]) => ({
name,
...([...values].reduce((r,v,i) => (r[`value${i+1}`]=v, r),{}))
}))
console.log(result)
.as-console-wrapper{min-height:100%;}
You may see your data as a matrix: each row is a feature and each column a dimension
What you have is the data expressed a rows
And what you would like is the data expressed as columns
So what you want is the transpose of your matrix
Let's recall that taking the transpose can be done as: T[j][i] = M[i][j] forall i,j
In your case
for each index of row i in M
for each index of column j in M
// T[j] is your aggregated record
// i being the index of the row has to be renamed 'val'+i
// and you add the property: title: columnOfJ to record T[j]
T[j]['val' + i] = M[i][j]
T[j].title = col corresponding to index j
const M = {"1":{"Average":32.31,"Count":19,"Sum":32.6,"Color":"red"},"2":{"Average":32.72,"Count":18,"Sum":32.96,"Color":"blue"},"3":{"Average":31.4,"Count":18,"Sum":31.48,"Color":"green"}}
const T = []
Object.entries(M).forEach(([i, Mi]) => {
Object.keys(Mi).forEach((col, j) => {
T[j] = T[j] || {}
T[j].title = col // we just put the title before so it is the first entry in your record...
T[j]['val' + i] = Mi[col]
})
})
console.log(T)

Node js queue return elements from an array

I'm trying to implement a queue in node js.I have an array with elements and i want at every 1 sec to insert a new one.I want to return every time only two elements from queue(at every 5 seconds).I want the queue to continue returning values from where remains.An example.I have an array [1,2,3,4].At every 2 sec i insert a new elemt in array.I return 2 elements from array at every 5 sec.Does anyone know how to make this work? Here is my code:
var queue = require('queue');
var posts=["aaa","bbbb","ccc",'ddd'];
var n=posts.length;
function populateQueue(q) {
for (var i = 0; i < posts.length; i++) {
(function(index) {
q.push(function(done) {
console.log('done', posts[index]);
setTimeout(done, 5000);
posts.splice(0,2);
});
})(i);
}
}
function insert() {
posts.push({"name":haiku()});
cxc();
}
function cxc() {
populateQueue(q2);
}
setInterval(insert,2000);
var q2 = queue({concurrency: 2});
populateQueue(q2);
q2.start();
function haiku(){
var adjs = ["autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", "summer", "icy", "delicate", "quiet", "white", "cool", "spring",
"winter", "patient", "twilight", "dawn", "crimson", "wispy", "weathered",
"blue", "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
"long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
"red", "rough", "still", "small", "sparkling", "throbbing", "shy",
"wandering", "withered", "wild", "black", "young", "holy", "solitary",
"fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
"polished", "ancient", "purple", "lively", "nameless"]
, nouns = ["waterfall", "river", "breeze", "moon", "rain", "wind", "sea",
"morning", "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn",
"glitter", "forest", "hill", "cloud", "meadow", "sun", "glade", "bird",
"brook", "butterfly", "bush", "dew", "dust", "field", "fire", "flower",
"firefly", "feather", "grass", "haze", "mountain", "night", "pond",
"darkness", "snowflake", "silence", "sound", "sky", "shape", "surf",
"thunder", "violet", "water", "wildflower", "wave", "water", "resonance",
"sun", "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
"frog", "smoke", "star"];
return adjs[Math.floor(Math.random()*(adjs.length-1))]+"_"+nouns[Math.floor(Math.random()*(nouns.length-1))];
}
I have a quick answer to this. Specifically involving your question, but not your code. You have to tell me what your code does, otherwise I can't help you with it. But to do with your question, I have this:
First, setup your initial queue and index.
let queue = [1, 2, 3, 4, 5],
index = 0;
Now you have to declare two functions, (1) The one that enqueues values in your array. In my case, I enqueue a random number
enqueue = () => {
queue.push(Math.floor(Math.random()*10));
console.log(queue);
}
The other one gets two values at the current index, and increments the index by 2, once the storing is done. The values are then, returned.
get_two_elements = () => {
let pointed_slice = queue.slice(index, index + 2);
index += 2;
console.log(pointed_slice);
return pointed_slice;
};
Finally, to make the contraption work, you have to put timeouts on them like this
setInterval(enqueue, 2000);
setInterval(get_two_elements, 5000);
Set interval is non-blocking, thus both will happen at almost the sametime with a difference ranging between nanoseconds and milliseconds. This will successfully do what your question sounds like to me.
For reference, I placed a gist on the whole code for you here:
https://gist.github.com/jekku/012eed5ea9c356f8fa29

Categories