diff --git a/01-basic-cs/index.js b/01-basic-cs/index.js index 3b302e6..93d3303 100644 --- a/01-basic-cs/index.js +++ b/01-basic-cs/index.js @@ -5,7 +5,17 @@ const assert = require('assert') const database = require('./database.json') -const total = 0 // TODO +const total = _.chain(database) + .map(userFromDB => userFromDB.hats) // Get only the hats + .omitBy(_.isEmpty) // Remove empty array of hats + .values() // Get only the array of hats of every user instead of keys + .flatten() // Get only one array with all hats + .countBy('id') // Count how many of every hat has been purchased + .toPairs() // Convert ever object {id: count} to array [id, count] + .sortBy(hat => hat[1]) // sort by count (second element of every [id, count] array) + .takeRight(3) // Select first three elements + .sumBy(hat => hat[1]) // Sum the top three elements + .value() // Throws error on failure assert.equal(total, 23, `Invalid result: ${total} != 23`) @@ -14,6 +24,25 @@ console.log('Success!') /** * Time and space complexity in O() notation is: - * - time complexity: TODO - * - space complexity: TODO + * - time complexity: + * The methods 'map', 'omitBy', 'flatten', 'countBy' and 'toPairs' have a + * time complexity of O(n) since the have to access every element of the array + * to apply their functions. + * + * The method 'sortBy' has a time complexity of O(n Log n) since it uses a special + * sorting algorithm + * + * the methods 'takeRight' and 'sumBy' have a time complexity of O(k), where k + * is the number of elements to operate. + * + * In conclusion, the time complexity of this algorithm depends on the length of + * the array in the database and the qantity of elements to take into account + * in the final sum. In general the time complexity is O(n Log n) due to the method + * 'sortBy', wich has the higher time complexity. + * + * - space complexity: + * In this case, the space complexity is O(n), where n is the number of elements + * in the database. This is because every operation in the chain returns a new array, + * and, in general (taking worst case scenario), the space required to store these arrays + * is proportional to the size of array in the database. */ diff --git a/02-nodejs/index.js b/02-nodejs/index.js index d5f5e22..a8943ed 100644 --- a/02-nodejs/index.js +++ b/02-nodejs/index.js @@ -3,10 +3,30 @@ const express = require('express') const User = require('./models/User') +const { Transform } = require('stream') // Setup Express.js app const app = express() -// TODO: everything else +app.get('/users', (req, res) => { + const filename = 'users-db.csv' + const users = User.find().batchSize(30).cursor() + + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`) + res.setHeader('Content-Type', 'text/csv') + + res.write('id, email, name\n') + + const streamCSV = new Transform({ + objectMode: true, + transform (user, _, done) { + const row = `${user.id},${user.email},${user.name}\n` + done(null, row) + } + }) + + streamCSV.pipe(res) + users.pipe(streamCSV) +}) app.listen(3000) diff --git a/03-react/package-lock.json b/03-react/package-lock.json index d7eeb02..b5e2b88 100644 --- a/03-react/package-lock.json +++ b/03-react/package-lock.json @@ -4022,6 +4022,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", @@ -20113,9 +20124,11 @@ } }, "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -25411,6 +25424,13 @@ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "requires": { "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + } } }, "ansi-html": { @@ -38045,9 +38065,11 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true, + "peer": true }, "type-is": { "version": "1.6.18", diff --git a/03-react/src/App.js b/03-react/src/App.js index 257f4dc..ea3ae00 100644 --- a/03-react/src/App.js +++ b/03-react/src/App.js @@ -1,3 +1,4 @@ +import React from 'react' import { BrowserRouter, Route, Switch } from 'react-router-dom' import Exercise from './components/pages/Exercise' diff --git a/03-react/src/components/pages/Exercise/index.js b/03-react/src/components/pages/Exercise/index.js index bb36d52..3b0244c 100644 --- a/03-react/src/components/pages/Exercise/index.js +++ b/03-react/src/components/pages/Exercise/index.js @@ -1,45 +1,9 @@ import './assets/styles.css' -import { useState } from 'react' +import { React, useState } from 'react' +import { movies } from '../../../core/data/movies' +import { discountRules } from '../../../core/data/discounts' export default function Exercise01 () { - const movies = [ - { - id: 1, - name: 'Star Wars', - price: 20 - }, - { - id: 2, - name: 'Minions', - price: 25 - }, - { - id: 3, - name: 'Fast and Furious', - price: 10 - }, - { - id: 4, - name: 'The Lord of the Rings', - price: 5 - } - ] - - const discountRules = [ - { - m: [3, 2], - discount: 0.25 - }, - { - m: [2, 4, 1], - discount: 0.5 - }, - { - m: [4, 2], - discount: 0.1 - } - ] - const [cart, setCart] = useState([ { id: 1, @@ -48,27 +12,79 @@ export default function Exercise01 () { quantity: 2 } ]) + const ACTIONS = { + INCREMENT: 'INCREMENT', + DECREMENT: 'DECREMENT' + } + + const getTotal = () => { + const moviesInCartIds = cart.map(movie => movie.id) + const discountRule = discountRules.find(rule => { + return rule.movieIds.length === moviesInCartIds.length && rule.movieIds.every(id => moviesInCartIds.includes(id)) + }) + + const totalWithoutDiscount = cart.reduce((acc, movie) => acc + movie.price * movie.quantity, 0) + const total = discountRule ? totalWithoutDiscount * (1 - discountRule.discountPercentage) : totalWithoutDiscount + + return total + } + + const getMovieIndexFromCart = (id) => { + return cart.findIndex(movieInCart => movieInCart.id === id) + } - const getTotal = () => 0 // TODO: Implement this + const addMovieToCart = (movie) => { + const cartToSet = [...cart] + const existingMovieIndex = getMovieIndexFromCart(movie.id) + + if (existingMovieIndex >= 0) { + cartToSet[existingMovieIndex].quantity++ + } else { + cartToSet.push({ + ...movie, + quantity: 1 + }) + } + setCart(cartToSet) + } + + const updateQuantity = (movieId, action) => { + const cartToUpdate = [...cart] + const movieIndexToUpdate = getMovieIndexFromCart(movieId) + + switch (action) { + case ACTIONS.INCREMENT: + cartToUpdate[movieIndexToUpdate].quantity++ + break + case ACTIONS.DECREMENT: + cartToUpdate[movieIndexToUpdate].quantity > 1 + ? cartToUpdate[movieIndexToUpdate].quantity-- + : cartToUpdate.splice(movieIndexToUpdate, 1) + break + default: + break + } + setCart(cartToUpdate) + } return (