When working on Sator Backend, I was working with a way to answer form related to a website that the user wanted and get the quoted price for that website. In order to make this happen, I needed to work on a way to sum and accumulate price/price range.
For context, I have 3 relevant entities, Form Response, Form Option, Form Attempt each works in it corresponsed name. The price is located in the Form Option, and my idea is to allow user to query their Form Attempt and see the quoted price which is the total sum of all the option that they've selected.
model FormOption {
id String @id @default(uuid())
question_id String
created_at DateTime @default(now())
updated_at DateTime @updatedAt
option_text String
type QuestionType @default(SINGLE_CHOICE)
price Float[]
metadata Json?
form_question FormQuestion @relation(fields: [question_id], references: [id], onDelete: Cascade)
form_response FormResponse[]
}My initial attempt is like this. When user query for the attempt, I would db get the attempt, then th db get the option from the response, do a calculation, and then join them together. This present many challenges like how to sum 2 arrays? precisely how to sum price range?, etc. Let go through the whole process and walk through the problem, bad and then solution.
There are many way to sum the element in the array but not 2 array. More specifically I am working with price range and thus it has to abide by these 3 rules:
The element cannot be more 2
If the sum between 2 array, one is full (have 2 element) and the other is half (have only 1 element), then when doing sum, the half element has to be add to both element. Example: [1, 2] + [1] = [2,3]
If the sum is between full and full then it will sum 1 to 1. Example: [1, 1] + [1, 1] = [2, 2]
My attempt is to first find the min and max of the 2 arrays, then do a loop to the maxlength, then add the element of the min length - 1, doing this will ensure that it follow the 2 rules.
const sumArray = (arr1: number[], arr2: number[]) => {
const maxLength = Math.max(arr1.length, arr2.length);
if (maxLength > 2) return [];
const minLength = Math.min(arr1.length, arr2.length);
const result = [];
for (let i = 0; i < maxLength; i++) {
const sum =
(arr1[i] || arr1[minLength - 1]) + (arr2[i] || arr2[minLength - 1]);
result.push(sum);
}
return result;
};This works great, but there is some downside that needed refinement with the way I was setting up the entity.
So the way I set up thing required me to basically do 2 db call in one request, which slow down the query time quite a bit. Both the db call and the function run time slow down the query perfomance. Not to mention, It is a nightmare to work with when doing the Form Attempt pagination.
Well it is quite simple, add another field to the Form Attempt and when we creating the form attempt which in turn we will need to create the Form Response which again will have the form option. But, there is another challenge when trying to do this, and that is the Form Response is an array, how do we attract the price of the option in the response and add them together?. Well, we will need to somehow iterate through the array object, then accumulate it into a single value. This call for the use of reduce. We go through the array object then accumulate it with the sumArray function so that in the final step we are left with a single price range.
const price = responses.reduce(
(prev, curr) => {
return sumArray(prev, curr.form_option.price);
},
[0, 0]
);