I am creating a graphql server using express, and I have a resolver that can transform my fields as per input from the user query.
The transformer that I am using is returning a function, which is the cause of my issues.
I want to sort my result by some user determined field, but since the field is a function, it won't work.
So the resolver looks like this:
const resolver = (req, param) => {
return {
history: async input => {
let size = input.pageSize || 3;
let start = (input.page || 0) * size;
let end = start + size;
let sortField = (input.sort || {}).field || 'timestamp';
return fs.promises.readFile("./history/blitz.json", "utf8").then(data =>
JSON.parse(data)
.slice(start, end)
.map(job => historyTransformer(job))
.sort((a,b) => a[sortField] > b[sortField] ? 1 : a[sortField] < b[sortField] ? -1 : 0)
);
}
};
};
and the transformer:
const historyTransformer = job => {
return {
...job,
timestamp: input =>
dateFormat(job.timestamp, input.format || "mm:hh dd-mm-yyyy")
};
};
I am not sure if I am missing something but is there an easy way of resolving the function call before starting the sorting?
GraphQL fields are resolved in a hierarchal manner, such that the history field has to resolve before any of its child fields (like timestamp) can be resolved. If the child field's resolver transforms the underlying property and your intent is to somehow use that value in the parent resolver (in this case, to do some sorting), that's tricky because you're working against the execution flow.
Because you're working with dates, you should consider whether the format of the field even matters. As a user, if I sort by timestamp, I expect the results to be sorted chronologically. Even if the response is formatted to put the time first, I probably don't want dates with the same times but different years grouped together. Of course, I don't know your business requirements and it still doesn't solve the problem if we're working with something else, like translations, which would cause the same problem.
There's two solutions I can think of:
Update your schema and lift the format argument into the parent field. This is easier to implement, but obviously not as nice as putting the argument on the field it applies to.
Keep the argument where it is, but parse the info parameter passed to the resolver to determine the value of the argument inside the parent resolver. This way, you can keep the argument on the child field, but move the actual formatting logic into the parent resolver.
Related
In my case minimum JSON data which I am using 90k [...] and currently I am using .filter method. Nothing is wrong working everything perfectly without any issue, but just for the performance point of view I am wondering and, need suggestion as well, which way we can use for improving the permeance do you agree or we have better way to improve the performance.
All the request coming from backend can not modify and split.
For the reference adding a 5k data which takes around 1sec.
I value all the developer times, I added a code snippet as well.
Appreciate any help and suggestion.
const load5kData = async () => {
let url = 'https://jsonplaceholder.typicode.com/photos';
let obj = await (await fetch(url)).json();
const filteredValue = obj.filter(item => item.albumId == 36);
console.log(filteredValue)
}
load5kData();
<h1>5k data</h1>
It looks like the response is returned with the albumId ordered in ascending order. You could make use of that by using a traditional for loop and short circuiting once you reach id 37.
In my opinion, if you aren't having performance issues just using the filter method, I would say just leave it and dont over-optimize!
Another option is that there are only 50 items with albumId == 36. You could just make your own array of all those objects in a json file. However you obviously lose out on fetching the latest images if the results of the api ever change
filter is really your only solution which will involve iterating over each element.
If you need to do multiple searches against the same data you can index data by the key you will be using so finding data with a specific albumId requires no additional filtering but would still require iterating over each element when initially indexing.
const indexByAlbumId = data =>
data.reduce((a, c) => {
if (a[c.albumId] === undefined) {
a[c.albumId] = [c];
} else {
a[c.albumId].push(c);
}
return a;
}, {});
const load5kData = async () => {
const url = 'https://jsonplaceholder.typicode.com/photos';
const data = await (await fetch(url)).json();
const indexedData = indexByAlbumId(data);
console.log('36', indexedData[36]);
console.log('28', indexedData[28]);
};
load5kData();
Another optimisation is that if the data is sorted by the index you are searching you can take advantage of this by doing a divide and conquer search where you first try to find an entry where the value is what you need, then from there you find where the chunk begins/ends by doing the same divide and conquer to the left/right of that element.
Currently using: obj.filter(item => item.albumId == 36);
My task is:
Implement the function duplicateStudents(), which gets the variable
"students" and filters for students with the same matriculation
number. Firstly, project all elements in students by matriculation
number. After that you can filter for duplicates relatively easily. At
the end project using the following format: { matrikelnummer:
(matrikelnummer), students: [ (students[i], students[j], ... ) ] }.
Implement the invalidGrades() function, which gets the variable "grades"
and filters for possibly incorrect notes. For example, in order to
keep a manual check as low as possible, the function should determine
for which matriculation numbers several grades were transmitted for
the same course. Example: For matriculation number X, a 2. 7 and a 2.
3 were transmitted for course Y. However, the function would also take
into account the valid case, i. e. for matriculation number X, once a
5,0 and once a 2,3 were transmitted for course Y.
In this task you should only use map(), reduce(), and filter(). Do not
implement for-loops.
function duplicateStudents(students) {
return students
// TODO: implement me
}
function invalidGrades(grades) {
return grades
.map((s) => {
// TODO: implement me
return {
matrikelnummer: -1/* put something here */,
grades: []/* put something here */,
};
})
.filter((e) => e.grades.length > 0)
}
The variables students and grades I have in a separate file. I know it might be helpful to upload the files too, but one is 1000 lines long, the other 500. That’s why I’m not uploading them. But I hope it is possible to do the task without the values. It is important to say that the values are represented as an array
I'll give you an example of using reduce on duplicateStudents, that's not returning the expected format but you could go from there.
const duplicateStudents = (students) => {
const grouping = students.reduce((previous, current) => {
if (previous[current.matrikelnummer]) previous[current.matrikelnummer].push(current); // add student if matrikelnummer already exist
else previous[current.matrikelnummer] = [current];
return previous;
}, {});
console.log(grouping);
return //you could process `grouping` to the expected format in here
};
here's preferences for you:
map
filter
reduce
I have a javascript array of nested data that holds data which will be displayed to the user.
The user would like to be able to apply 0 to n filter conditions to the data they are looking at.
In order to meet this goal, I need to first find elements that match the 0 to n filter conditions, then perform some data manipulation on those entries. An obvious way of solving this is to have several filter statements back to back (with a conditional check inside them to see if the filter needs to be applied) and then a map function at the end like this:
var firstFilterList = _.filter(myData, firstFilterFunction);
var secondFilterList = _.filter(firstFilterList, secondFilterFunction);
var thirdFilterList = _.filter(secondFilterList, thirdFilterFunction);
var finalList = _.map(thirdFilterList, postFilterFunction);
In this case however, the javascript array would be traversed 4 times. A way to get around this would be to have a single filter that checks all 3 (or 0 to n) conditions before determining if there is a match, and then, inside the filter at the end of the function, doing the data manipulation, however this seems a bit hacky and makes the "filter" responsible for more than one thing, which is not ideal. The upside would be that the javascript Array is traversed only once.
Is there a "best practices" way of doing what I am trying to accomplish?
EDIT: I am also interested in hearing if it is considered bad practice to perform data manipulation (adding fields to javascript objects etc...) within a filter function.
You could collect all filter functions in an array and check every filter with the actual data set and filter by the result. Then take your mapping function to get the wanted result.
var data = [ /* ... */ ],
filterFn1 = () => Math.round(Math.random()),
filterFn2 = (age) => age > 35,
filterFn3 = year => year === 1955,
fns = [filterFn1, filterFn2, filterFn2],
whatever = ... // final function for mapping
result = data
.filter(x => fns.every(f => f(x)))
.map(whatever);
One thing you can do is to combine all those filter functions into one single function, with reduce, then call filter with the combined function.
var combined = [firstFilterFunction, seconfFilterFunction, ...]
.reduce((x, y) => (z => x(z) && y(z)));
var filtered = myData.filter(combined);
I need some assistance developing a search query to give a button in my Electron application some functionality.
This is how far I have gotten:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
Artist.sort()
.find()
.offset()
.limit();
};
I am skipping criteria for now so feel free to ignore that. The user should be able to sort by artist name, age and albums released. The sortProperty will go in ascending fashion so I know I need to sort with a value of 1.
The real challenge behind the sorting is that I need to specify different sortProperties but only one at any given time.
I want to be able to pass in a different sortProperty depending on what the user has selected. I would like to have a key that is whatever the sortProperty is.
So if sortProperty is equal to age, I need to ensure I have a statement that says age.
It seems you already have all the required parts to do the query. You just need to create the sort selector based on your sortProperty. This will be an object with a key equal to the value held by sortProperty for example age. The result will look like this
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
return Artist
.find()
.sort({ [sortProperty]: 1 })
.skip(offset)
.limit(limit);
};
Note
To illustrate the dynamic key assignation, here's a snippet
const sortProperty = 'age';
const selector = { [sortProperty]: 1 };
console.log(selector);
So the above answer is the best solution as it is ES6 and more elegant. I wanted to share the ES5 old school way that I learned after I got this answer and studied it more in-depth.
In the ES5 way I learned that you can define an object like so:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
const sortOrder = {};
Artist.find({})
.sort()
.skip(offset)
.limit(limit);
};
I made it an empty object and then look at it and add sortProperty equals 1 like so:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
const sortOrder = {};
sortOrder[sortProperty] = 1;
Artist.find({})
.sort()
.skip(offset)
.limit(limit);
};
And then I can pass in the sortOrder like so:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
const sortOrder = {};
sortOrder[sortProperty] = 1;
Artist.find({})
.sort(sortOrder)
.skip(offset)
.limit(limit);
};
Okay, so that is the ES5 approach if anyone was interested in learning about it, but as you can see above the ES6 solution looks better.
The whole problem with creating an object and simultaneously adding a property to it is that its not well supported in ES5, we have to first declare an object, then add a property to it and set that property equal to 1.
The square brackets mean look at the sortProperty variable:
sortOrder[sortProperty] = 1;
which is a string and I am trying to add on a sortOrder property of name to this object and set it equal to 1 and so the end result if you ran this in a Chrome console or in code snippet is { name: 1 }.
The learning curve for me and what the selected answer helped me learn was ES6 interpolated properties or keys. This thing:
.sort({ [sortProperty]: 1 })
What is happening here is at runtime look at the sortProperty variable and whatever its equal to, add that property to this object and give it a value of 1.
So to be clear, yes the original answer given was the elegant solution I went with:
module.exports = (criteria, sortProperty, offset = 0, limit = 20) => {
// write a query that will follow sort, offset, limit options only
// do not worry about criteria yet
Artist.find()
.sort({ [sortProperty]: 1 })
.skip(offset)
.limit(limit);
};
I was just sharing what I learned along the way in studying and implementing it.
(My first post, I apologise for any mistakes)
I'm working with a small set of data in CSV files, which I need to read, process, and then export as a text file.
The format of the CSV data is:
REGO,STATUS,SHIFT,LOCATION,LOADED
CCA4110,StatusON,5:43,Brisbane,1
CCA4112,StatusON,5:44,Syndey,0
CCA4118,StatusON,6:11,Melbourne,1
I want to be able to take each line after the header row, and check
a) if the 'LOADED' value equals 0 or 1 (skip to next row if 1).
b) If 'LOADED' is 0, then check if the 'REGO' value matches a pre-defined list of 'REGO' values.
c) If a match, change the 'SHIFT' time.
d) If no match, move on to next row.
After that, I want to export all of the rows, with only the 'REGO' and 'SHIFT' values, to look like:
CCA4110,5:43
CCA4112,5:33
...
Because this feels a little complex to me, I'm having trouble visualising the best way to approach this problem. I was wondering if someone could help me think about this in a way that isn't just hacking together a dozen nested for loops.
Thanks very much,
Liam
Edit: a question about checking multiple conditions:
Say I have two CSV files:
List_to_Change.csv
REGO,STATUS,SHIFT,LOCATION,LOADED
CCA2420,StatusOn,11:24,BRISBANE,1
CCA2744,StatusOn,4:00,SYDNEY,1
CCA2009,StatusOn,4:00,MELBOURNE,0
List_to_Compare.csv
REGO,CORRECT_SHIFT
CCA2420,6:00
CCA2660,6:00
CCA2009,5:30
An algorithm:
1. Check value in 'List_to_Check.csv' 'LOADED' column
A. If value equals '0' go to step 2.
B. If value equals '1' skip this row and go to next.
2. Check if 'REGO' value in 'List_to_Check.csv' shows up in 'List_to_Compare.csv'
A. If true go to step 3.
B. If false skip this row and go to next.
3. Change 'SHIFT' value in 'List_to_Change.csv' with value shown in 'List_to_Compare.csv'
4. Stringify each row that was changed and export to text file.
My advice would be to split the work flow in to three steps:
Parse all rows to javascript objects
Perform the logic on the array of objects
Stringify the objects back to CSV
// This creates an object based on an order of columns:
const Entry = ([rego, status, shift, location, loaded]) =>
({ rego, status, shift, location, loaded });
// Which entries are we interested in?
const shouldHandleEntry = ({ loaded }) => loaded === "1";
// How do we update those entries?
const updateEntry = entry => ({
...entry,
shift: ["CCA4118"].includes(entry.rego)
? "5:33"
: entry.shift
});
// What do we export?
const exportEntry = ({ rego, shift }) => `${rego},${shift}`;
// Chain the steps to create a new table:
console.log(
csvBody(getCSV())
.map(Entry)
.filter(shouldHandleEntry)
.map(updateEntry)
.map(exportEntry)
.join("\n")
)
// (This needs work if you're using it for production code)
function csvBody(csvString) {
return csvString
.split("\n")
.map(line => line.trim().split(","))
.slice(1);
};
function getCSV() { return `REGO,STATUS,SHIFT,LOCATION,LOADED
CCA4110,StatusON,5:43,Brisbane,1
CCA4112,StatusON,5:44,Sydney,0
CCA4118,StatusON,6:11,Melbourne,1`; }