├── .gitignore ├── Readme.md ├── exercises ├── async_loops │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_call │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_every_some │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_filter │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_inheritance_with_objectcreate │ ├── exercise.js │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_inheritance_without_objectcreate │ ├── exercise.js │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_map │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── basic_recursion │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── basic_reduce │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── blocking_event_loop │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ ├── solution_fr │ │ └── solution.js │ ├── wrapper.js │ └── wrapper_fr.js ├── currying │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── function_call │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── function_spies │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── hello_world │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── higher_order_functions │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── implement_map_with_reduce │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── menu.json ├── partial_application_with_bind │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── wrapper.js ├── partial_application_without_bind │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── wrapper.js ├── prototypical_inheritance_by_hand │ ├── exercise.js │ ├── problem.md │ └── solution │ │ └── solution.js ├── randomizer.js ├── recursion │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── runner.js └── trampoline │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ └── solution.js ├── functional-javascript.js ├── i18n ├── en.json ├── fr.json └── ko.json ├── input.js ├── logo.png └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | TODO.md 5 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Functional Javascript Workshop 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/nodeschool/discussions?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | NodeSchool Discussions 5 | 6 | ### Teaching fundamental functional programming features of Javascript. 7 | 8 | #### No libraries required (i.e. no underscore), just ES5. 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | [![Gittip](http://img.shields.io/gittip/timoxley.png)](https://www.gittip.com/timoxley/) 17 | ## Mission 18 | 19 | Many functional programming learning resources will teach you to write functional code, but it's often highly indirect, 20 | deeply abstracted, requires understanding complex relationships between custom library calls, and doesn't represent 21 | the reality of how people actually write JavaScript. 22 | 23 | The goal of this workshop is to create realistic problems that can be solved using terse, vanilla, idiomatic JavaScript. 24 | 25 | **Please read the exercises thoroughly and obey all conditions, they are there to help you learn!** 26 | 27 | ## Installation & Update 28 | 29 | ``` 30 | $ npm install -g functional-javascript-workshop@latest 31 | ``` 32 | 33 | Some npm installations require use of `sudo` in the above command. Recommend to instead [reinstall node/npm so you don't need sudo](https://gist.github.com/isaacs/579814). 34 | 35 | ## Usage Instructions 36 | 37 | #### 1. Selecting a problem to work on 38 | 39 | Once the workshop is installed, run `functional-javascript-workshop` to print a menu 40 | where you can select a problem to work on. 41 | 42 | ``` 43 | $ functional-javascript-workshop 44 | ``` 45 | 46 | Problems are listed in rough order of difficulty. You are advised to complete them in order, as later problems 47 | will build on skills developed by solving previous problems. 48 | 49 | #### 2. Writing your solution 50 | 51 | Once you have selected a problem, the workshop will remember which problem you are working on. 52 | Using your preferred editor, simply create a file to write your solution in. Most problems will 53 | supply some boilerplate with which to get started. Copy this from the problem description to your 54 | solution file. 55 | 56 | #### 3. Testing your solution 57 | 58 | Use the workshop's `run` command to point the workshop at your solution file. Your solution will be loaded 59 | and passed the problem input. This usually won't perform any validation, it will only show the program output. 60 | 61 | ``` 62 | $ functional-javascript-workshop run mysolution.js 63 | ``` 64 | 65 | #### 4. Verifying your solution 66 | 67 | Your solution will be verified against the output of the 'official' solution. 68 | If all of the output matches, then you have successfully solved the problem! 69 | 70 | ``` 71 | $ functional-javascript-workshop verify mysolution.js 72 | ``` 73 | 74 | ## Stuck? 75 | 76 | Feedback and criticism is welcome, please log your troubles in [issues](https://github.com/timoxley/functional-javascript-workshop/issues). 77 | 78 | Full curriculum reviews [like this one](https://github.com/timoxley/functional-javascript-workshop/issues/7) are incredibly helpful. More feedback like this please! 79 | 80 | We're looking for more practical problems, so if you come across a problem in your day-to-day work which was solved simply and elegantly with some functional JavaScript techniques, please help us create an exercise out of it. 81 | 82 | 83 | ## Screenshots 84 | 85 | ![screen shot 2013-09-27 at 5 18 45 pm](https://f.cloud.github.com/assets/43438/1225514/08c87a70-276a-11e3-8db7-485e3c760373.png) 86 | ![screen shot 2013-09-23 at 9 13 02 pm](https://f.cloud.github.com/assets/43438/1191466/f289f38a-2451-11e3-9ba5-a3c224b5ca97.png) 87 | 88 | ## Resources 89 | 90 | [A growing collection of quality functional javascript resources can be found in the wiki](https://github.com/timoxley/functional-javascript-workshop/wiki). 91 | 92 | 93 | ## Thanks rvagg 94 | 95 | This tutorial was built using rvagg's [workshopper](https://github.com/rvagg/workshopper) framework. 96 | 97 | ## Summary 98 | 99 | ``` 100 | project : functional-javascript 101 | repo age : 7 months 102 | active : 50 days 103 | commits : 152 104 | files : 70 105 | authors : 106 | 69 Tim 45.4% 107 | 67 Tim Oxley 44.1% 108 | 3 Sequoia McDowell 2.0% 109 | 2 ZJ 1.3% 110 | 1 Naor Biton 0.7% 111 | 1 Pavel Kornilov 0.7% 112 | 1 Steve Teo 0.7% 113 | 1 Wei Lu 0.7% 114 | 1 Yoshua Wuyts 0.7% 115 | 1 flakyfilibuster 0.7% 116 | 1 Arvid Andersson 0.7% 117 | 1 tim walker 0.7% 118 | 1 Brendon Murphy 0.7% 119 | 1 Lorcan Coyle 0.7% 120 | 1 Matthew Hokanson 0.7% 121 | ``` 122 | 123 | ## License 124 | 125 | MIT 126 | -------------------------------------------------------------------------------- /exercises/async_loops/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var deepEqual = require('deep-eql') 4 | var inspect = require('util').inspect 5 | var loremIpsum = require('lorem-ipsum') 6 | var random = require('../randomizer') 7 | var runner = require('../runner') 8 | 9 | // All deepEqual impls (assert, deep-eql…) seem to b0rk when multiple objects 10 | // in an array share the same `id` value (wtf?!), so we make sure they're unique. 11 | var userCount = random.int(10, 20) 12 | var userIds = [] 13 | while (userIds.length < userCount) { 14 | var id = random.int(0, 1000) 15 | if (-1 !== userIds.indexOf(id)) continue 16 | userIds.push(id) 17 | } 18 | 19 | var users = random.arrayOf(userCount, function() { 20 | return { 21 | id: userIds.shift(), 22 | name: random.words(2, { capitalized: true }) 23 | } 24 | }) 25 | 26 | var fx 27 | 28 | module.exports = runner.custom(function(f) { 29 | fx = f 30 | }).wrapUp(function(callback) { 31 | var self = this 32 | var ids = users.map(function(user) {return user.id }) 33 | var load = function(id, fn) { 34 | setTimeout(function() { 35 | var match = users.filter(function(user) {return user.id === id}) 36 | if (match.length) fn(match[0]) 37 | else fn(null) 38 | }, random.int(0, 1000)) 39 | } 40 | var done = function(submittedUsers) { 41 | clearTimeout(tooLong) 42 | console.log(submittedUsers) 43 | 44 | if (!deepEqual(submittedUsers, users)) { 45 | self.emit('fail', self.__('bad_result', inspect(users), inspect(submittedUsers))) 46 | return callback(null, false) 47 | } 48 | 49 | console.log(self.__('all_loaded', submittedUsers.length)) 50 | callback(null, true) 51 | } 52 | 53 | fx.call(fx, ids, load, done) 54 | var tooLong = setTimeout(function() { 55 | self.emit('fail', self.__('took_too_long')) 56 | callback(null, false) 57 | }, 1000) 58 | }).quiet(users) 59 | -------------------------------------------------------------------------------- /exercises/async_loops/problem.fr.md: -------------------------------------------------------------------------------- 1 | Le code ci-dessous est cassé ! 2 | 3 | Un développeur Java a ajouté le code atroce ci-dessous à notre projet et ne l’a pas testé ! 4 | 5 | ```js 6 | function loadUsers(userIds, load, done) { 7 | var users = [] 8 | for (var i = 0; i < userIds.length; i++) { 9 | users.push(load(userIds[i])) 10 | } 11 | return users 12 | } 13 | 14 | module.exports = loadUsers 15 | ``` 16 | 17 | ## Défi 18 | 19 | Corrigez ce code ! La fonction de rappel `done()` devrait être appelée une fois que tous les utilisateurs ont été chargés. L’ordre des utilisateurs doit correspondre à celui des IDs reçus. Vu que cette fonction est asynchrone, on se fiche de sa valeur de retour. 20 | 21 | ## Arguments 22 | 23 | * `userIds` : un tableau d’IDs numériques d’utilisateurs. 24 | * `load` : une fonction asynchrone de chargement d’un objet utilisateur. Reçoit un ID et une fonction de rappel. Celle-ci sera appelée avec le résultat du chargement pour l’utilisateur avec l’ID indiqué (soit un objet utilisateur, soit `null`). 25 | * `done` : une fonction de rappel finale, qui attend comme argument un tableau des objets utilisateurs chargés. 26 | 27 | ## Conditions 28 | 29 | * N’utilisez pas de boucles `for`/`while` (mais `Array#forEach()` reste autorisé). 30 | * L’ordre des utilisateurs dans le tableau passé à `done()` doit correspondre à celui des IDs dans le tableau `userIds` que vous aurez reçu. 31 | * Les utilisateurs doivent être chargés en parallèle, donc la séquence entière ne devrait pas dépasser une seconde. 32 | * Ne créez aucune fonction superflue 33 | 34 | ## Conseils 35 | 36 | * Vous n’avez pas besoin d’un tri pour maintenir l’ordre du résultat. 37 | * Si vous utilisez `console.log()`, ça va impacter notre vérification. Ne vous en servez que pendant votre phase de mise au point avec `{appname} run`. 38 | 39 | ## Base de travail 40 | 41 | ```js 42 | function loadUsers(userIds, load, done) { 43 | var users = [] 44 | for (var i = 0; i < userIds.length; i++) { 45 | users.push(load(userIds[i])) 46 | } 47 | return users 48 | } 49 | 50 | module.exports = loadUsers 51 | ``` 52 | -------------------------------------------------------------------------------- /exercises/async_loops/problem.ko.md: -------------------------------------------------------------------------------- 1 | 이 코드는 동작하지 않습니다! 2 | 3 | 어떤 자바 개발자가 이 끔찍한 코드를 우리에 코드베이스에 테스트도 안 하고 커밋했습니다! 4 | 5 | ```js 6 | function loadUsers(userIds, load, done) { 7 | var users = [] 8 | for (var i = 0; i < userIds.length; i++) { 9 | users.push(load(userIds[i])) 10 | } 11 | return users 12 | } 13 | 14 | module.exports = loadUsers 15 | ``` 16 | 17 | # 해야할 일 18 | 19 | 이 코드를 고치세요! 콜백은 모든 사용자가 로드된 후에 호출되어야만 합니다. 20 | 사용자의 순서는 공급된 사용자의 아이디의 순서와 일치해야 합니다. 이 함수는 비동기적이기 때문에, 반환 값을 걱정하지 않아도 됩니다. 21 | 22 | ## 인자 23 | 24 | * userIds: 사용자 아이디(숫자)의 배열 25 | * load: 사용자 객체를 불러올 때 사용하는 함수. 숫자 아이디와 콜백을 받음. 콜백은 특정 아이디로 로딩된 사용자(사용자거나 널)와 함께 호출됩니다. 26 | * done: (`load`로 검색된) 사용자 객체의 배열을 받는 함수. 27 | 28 | ## 조건 29 | 30 | * for/while 반복문을 사용하지 마세요. (Array#forEach는 괜찮습니다.) 31 | * `done`의 결과의 순서는 `userIds`의 순서와 같아야 합니다. 32 | * 사용자들은 병렬로 로드되어야 합니다. 전체 작업은 1초 이상 걸리면 안 됩니다. 33 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 34 | 35 | ## 힌트 36 | 37 | * 순서를 관리하기 위해 정렬할 필요는 없습니다. 38 | * `console.log`를 사용하면 확인에 영향을 줍니다. `console.log`는 `functional-javascript run` 할 때만 사용하세요. 39 | 40 | ## 템플릿 41 | 42 | ```js 43 | function loadUsers(userIds, load, done) { 44 | var users = [] 45 | for (var i = 0; i < userIds.length; i++) { 46 | users.push(load(userIds[i])) 47 | } 48 | return users 49 | } 50 | 51 | module.exports = loadUsers 52 | ``` 53 | -------------------------------------------------------------------------------- /exercises/async_loops/problem.md: -------------------------------------------------------------------------------- 1 | This code is broken! 2 | 3 | A Java developer has committed this terrible code to our codebase and didn't test it! 4 | 5 | ```js 6 | function loadUsers(userIds, load, done) { 7 | var users = [] 8 | for (var i = 0; i < userIds.length; i++) { 9 | users.push(load(userIds[i])) 10 | } 11 | return users 12 | } 13 | 14 | module.exports = loadUsers 15 | ``` 16 | 17 | # Task 18 | 19 | Fix this code! The callback should be called with all the users loaded. 20 | The order of the users should match the order of supplied user ids. Because this function is asynchronous, we do not care about its return value. 21 | 22 | ## Arguments 23 | 24 | * userIds: an Array of numeric user ids. 25 | * load: a Function used to load user objects. Expects a numeric id and a callback. The callback will be called with the result of loading the user with the specified id (either a user object or null). 26 | * done: a Function that expects an Array of user objects (as retrieved from `load`). 27 | 28 | ## Conditions 29 | 30 | * Do not use for/while loops (Array#forEach ok). 31 | * The order of the results in `done` must be the same as they were specified in `userIds`. 32 | * Users should be loaded in parallel i.e. the entire job should not take more than 1 second. 33 | * Do not create any unnecessary functions e.g. helpers. 34 | 35 | ## Hint 36 | 37 | * You don't need to use a sort to maintain ordering. 38 | * Using `console.log` will affect verification. Only use `console.log` when using `functional-javascript run`. 39 | 40 | ## Boilerplate 41 | 42 | ```js 43 | function loadUsers(userIds, load, done) { 44 | var users = [] 45 | for (var i = 0; i < userIds.length; i++) { 46 | users.push(load(userIds[i])) 47 | } 48 | return users 49 | } 50 | 51 | module.exports = loadUsers 52 | ``` 53 | -------------------------------------------------------------------------------- /exercises/async_loops/solution/solution.js: -------------------------------------------------------------------------------- 1 | function loadUsers(userIds, load, done) { 2 | var completed = 0 3 | var users = [] 4 | userIds.forEach(function(id, index) { 5 | load(id, function(user) { 6 | users[index] = user 7 | if (++completed === userIds.length) return done(users) 8 | }) 9 | }) 10 | } 11 | 12 | module.exports = loadUsers 13 | -------------------------------------------------------------------------------- /exercises/basic_call/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var random = require('../randomizer') 4 | var runner = require('../runner') 5 | 6 | var input = random.arrayOfInts(20, 0, 10) 7 | 8 | var exercise = module.exports = runner.custom(function(fx, numbers) { 9 | var valid = 1 10 | var objects = [{quack: true}].concat(numbers.map(function(num) { 11 | switch(num) { 12 | case 0: 13 | valid++ 14 | return {quack: true} 15 | break; 16 | case 1: 17 | valid++ 18 | return {quack: false} 19 | case 2: 20 | valid++ 21 | var obj = Object.create(null) 22 | obj.quack = true 23 | return obj 24 | case 3: 25 | valid++ 26 | var obj = Object.create(null) 27 | obj.quack = undefined 28 | return obj 29 | case 3: 30 | var proto = { quack: true } 31 | return Object.create(proto) 32 | case 4: 33 | var proto = Object.create(null) 34 | proto.quack = true 35 | return Object.create(proto) 36 | case 5: 37 | valid++ 38 | var proto = Object.create(null) 39 | proto.quack = true 40 | var obj = Object.create(proto) 41 | obj.quack = undefined 42 | return obj 43 | case 6: 44 | valid++ 45 | return { 46 | quack: true, 47 | hasOwnProperty: function() { 48 | return false 49 | } 50 | } 51 | default: 52 | return {} 53 | } 54 | })) 55 | 56 | return exercise.__('matched_objects', fx.apply(null, objects), valid, objects.length) 57 | }).hideInput(input) 58 | -------------------------------------------------------------------------------- /exercises/basic_call/problem.fr.md: -------------------------------------------------------------------------------- 1 | JavaScript supporte le « duck typing », une méthode dynamique de test des types d’objet. Elle repose sur l’analyse des méthodes et propriétés d’un objet pour déterminer sa sémantique, plutôt que de se fier à un héritage de classe particulière ou à l’implémentation d’une interface abstraite… Le nom de ce concept vient du « test du canard », attribué à James Whitcomb Riley, qu’on peut formuler ainsi : 2 | 3 | > « Quand je vois un volatile qui marche comme un canard, nage comme un canard, et cancane comme un canard, alors j’appelle ce volatile un canard. » 4 | 5 | En JavaScript, pour écrire des programmes robustes, nous avons parfois besoin de vérifier qu’un objet est conforme au type dont nous avons besoin. 6 | 7 | Nous pouvons utiliser `Object#hasOwnProperty()` pour détecter qu’un objet « a » une propriété définie sur lui-même, ce qu’on appelle une *propriété propre* (par opposition à une propriété hérité du prototype) : 8 | 9 | ```js 10 | var duck = { 11 | quack: function() { 12 | console.log('quack') 13 | } 14 | } 15 | 16 | duck.hasOwnProperty('quack') // => true 17 | ``` 18 | 19 | Nous n’avons toutefois pas équipé `duck` d’une méthode `hasOwnProperty()`, alors d’où vient-elle ? 20 | 21 | `duck` a été créé avec la syntaxe littérale `{…}`, qui définit un objet, de sorte qu’il hérite automatiquement de `Object.prototype` : 22 | 23 | ```js 24 | var object = {quack: true} 25 | 26 | Object.getPrototypeOf(object) === Object.prototype // => true 27 | object.hasOwnProperty('quack') // => true 28 | ``` 29 | 30 | Mais qu’en serait-il pour un objet qui n’hérite pas de `Object.prototype` ? 31 | 32 | ```js 33 | // Créons un objet avec un prototype `null` 34 | var object = Object.create(null) 35 | object.quack = function() { 36 | console.log('quack') 37 | } 38 | 39 | Object.getPrototypeOf(object) === Object.prototype // => false 40 | Object.getPrototypeOf(object) === null // => true 41 | 42 | object.hasOwnProperty('quack') 43 | // => TypeError: Object object has no method 'hasOwnProperty' 44 | ``` 45 | 46 | Nous pouvons toujours appeler la `hasOwnProperty()` de `Object.prototype`, ceci dit, du moment que nous l’appelons avec un `this` qui « ressemble à un objet ». `Function#call` nous permet d’appeler n’importe quelle fonction avec un `this` que nous contrôlons. 47 | 48 | ```js 49 | // Le premier argument de `call` sera le `this` 50 | // Le reste des arguments est passé à la fonction 51 | 52 | Object.prototype.hasOwnProperty.call(object, 'quack') // => true 53 | ``` 54 | 55 | # Défi 56 | 57 | Écrivez une fonction `duckCount()` qui inspecte les arguments qu’on lui passe et renvoie le nombre de ceux qui ont une propriété propre `quack` définie. Ignorez les propriétés hérités des prototypes. 58 | 59 | Exemple : 60 | 61 | ```js 62 | var notDuck = Object.create({quack: true}) 63 | var duck = {quack: true} 64 | duckCount(duck, notDuck) // 1 65 | ``` 66 | ## Arguments 67 | 68 | Vous recevrez un nombre variable d’arguments, d’un appel à l’autre. Chaque argument pourra être d’un type quelconque, avec des propriétés quelconques. Certains arguments auront une propriété `quack`, parfois héritée du prototype. Certains pourrons ne pas être équipés de `hasOwnProperty()`. 69 | 70 | ## Conditions 71 | 72 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 73 | * Ne maintenez pas de variable pour le compteur / l’accumulateur. 74 | * Ne créez aucune fonction superflue 75 | 76 | ## Conseil 77 | 78 | La variable automatique `arguments`, disponible dans toute fonction, est un *objet* qui ressemble à un tableau sans en être vraiment un : 79 | 80 | ```js 81 | { 82 | 0: 'argument0', 83 | 1: 'argument1', // etc. 84 | length: 2 85 | } 86 | ``` 87 | 88 | ## Ressources 89 | 90 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/call 91 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/hasOwnProperty 92 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/L_op%C3%A9rateur_in 93 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/slice#Array-like 94 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/arguments 95 | 96 | ## Base de travail 97 | 98 | ```js 99 | function duckCount() { 100 | // VOTRE SOLUTION ICI 101 | } 102 | 103 | module.exports = duckCount 104 | ``` 105 | -------------------------------------------------------------------------------- /exercises/basic_call/problem.ko.md: -------------------------------------------------------------------------------- 1 | JavaScript는 'duck' 타이핑을 구현합니다. 덕 타이핑은 동적 타이핑의 형식입니다. 이는 특정 클래스에서 상속받는 것이나 특정 인터페이스의 구현 방식이 아닌, 객체의 메소드와 속성을 통해 바른 형식이 무엇인지 결정합니다. 이 개념의 이름은 James Whitcomb Riley가 만든 덕 데스트에 기인하고 다음 문장으로 요약할 수 있습니다. 2 | 3 | "오리처럼 걷고, 오리처럼 수영하고, 오리처럼 꽥꽥대는 새가 있다면, 나는 그 새를 오리라 부르겠다" 4 | 5 | JavaScript에서 탄탄한 프로그램을 작성하려다보면 종종 객체가 우리가 원하는 형인지 확인할 필요가 있습니다. 6 | 7 | 객체가 직접 구현한 프로퍼티를 '가지고' 있는지를 확인하려면 (즉, 프로토타입에서 상속되지 않았는지 확인하려면) Object#hasOwnProperty를 사용할 수 있습니다. 8 | 9 | ```js 10 | var duck = { 11 | quack: function() { 12 | console.log('quack') 13 | } 14 | } 15 | 16 | duck.hasOwnProperty('quack') // => true 17 | ``` 18 | 19 | duck에 .hasOwnProperty 메소드를 구현한 적이 없는데 어디서 왔을까요? 20 | 21 | duck은 `{}` 구문으로 만들어졌으므로 Object.prototype을 상속받습니다. 22 | 23 | ```js 24 | var object = {quack: true} 25 | 26 | Object.getPrototypeOf(object) === Object.prototype // => true 27 | object.hasOwnProperty('quack') // => true 28 | ``` 29 | 30 | Object.prototype에서 상속되지 않은 객체라면 어떨까요? 31 | 32 | ```js 33 | // 'null' 프로토타입으로 객체를 생성. 34 | var object = Object.create(null) 35 | object.quack = function() { 36 | console.log('quack') 37 | } 38 | 39 | Object.getPrototypeOf(object) === Object.prototype // => false 40 | Object.getPrototypeOf(object) === null // => true 41 | 42 | object.hasOwnProperty('quack') 43 | // => TypeError: Object object has no method 'hasOwnProperty' 44 | ``` 45 | 46 | '객체처럼 보이는 것'에 `this` 값과 같이 호출할 수 있다면 `Object.prototype`를 통해 `hasOwnProperty`를 사용할 수 있습니다. Function#call을 사용하면 변경된 `this` 값으로 어떤 함수든 호출할 수 있습니다. 47 | 48 | ```js 49 | // 호출할 때 넘기는 첫 번째 인자는 `this`의 값이 됩니다 50 | // 나머지 인자는 함수에 넘기는 인자가 됩니다 51 | 52 | Object.prototype.hasOwnProperty.call(object, 'quack') // => true 53 | ``` 54 | 55 | # 해야할 일: 56 | 57 | 'quack' 프로퍼티가 직접 정의된 인자의 개수를 반환하는 `duckCount` 함수를 작성하세요. 프로토타입에서 상속된 값은 제외하셔야 합니다. 58 | 59 | 예제: 60 | 61 | ```js 62 | var notDuck = Object.create({quack: true}) 63 | var duck = {quack: true} 64 | duckCount(duck, notDuck) // 1 65 | ``` 66 | ## 인자 67 | 68 | * 0-20개의 인자를 넘깁니다. 각 인자는 어떤 프로퍼티도 가질 수 있는 모든 형이 가능합니다. 몇몇 인자는 'quack' 프로퍼티를 가집니다. 69 | 70 | ## 조건 71 | 72 | * for/while 반복문이나 Array#forEach를 사용하지 마세요. 73 | * counter/accumulator 변수를 만들지 마세요. 74 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 75 | 76 | ## 힌트 77 | 78 | * 모든 함수에서 사용할 수 있는 `arguments` 변수는 *배열*처럼 'quack'하는 *객체*입니다. 79 | 80 | ```js 81 | { 82 | 0: 'argument0', 83 | 1: 'argument1', // etc 84 | length: 2 85 | } 86 | ``` 87 | 88 | ## 참고 89 | 90 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call 91 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty 92 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in 93 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Array-like 94 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments 95 | 96 | 97 | ## 템플릿 98 | 99 | ```js 100 | function duckCount() { 101 | // 여기에 해답을 적으세요 102 | } 103 | 104 | module.exports = duckCount 105 | ``` 106 | -------------------------------------------------------------------------------- /exercises/basic_call/problem.md: -------------------------------------------------------------------------------- 1 | JavaScript implements 'duck' typing. Duck typing is a style of dynamic typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. The name of the concept refers to the duck test, attributed to James Whitcomb Riley, which may be phrased as follows: 2 | 3 | "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck" 4 | 5 | In JavaScript, in order to write robust programs we sometimes need to check an object conforms to the type that we need. 6 | 7 | We can use Object#hasOwnProperty to detect if an object 'has' a property defined on itself (i.e. not inherited from its prototype): 8 | 9 | ```js 10 | var duck = { 11 | quack: function() { 12 | console.log('quack') 13 | } 14 | } 15 | 16 | duck.hasOwnProperty('quack') // => true 17 | ``` 18 | 19 | We didn't give the duck a .hasOwnProperty method, where did it come from? 20 | 21 | Duck was created with the `{}` syntax, and as such it inherits from Object.prototype: 22 | 23 | ```js 24 | var object = {quack: true} 25 | 26 | Object.getPrototypeOf(object) === Object.prototype // => true 27 | object.hasOwnProperty('quack') // => true 28 | ``` 29 | 30 | But what if an object doesn't inherit from Object.prototype? 31 | 32 | ```js 33 | // create an object with 'null' prototype. 34 | var object = Object.create(null) 35 | object.quack = function() { 36 | console.log('quack') 37 | } 38 | 39 | Object.getPrototypeOf(object) === Object.prototype // => false 40 | Object.getPrototypeOf(object) === null // => true 41 | 42 | object.hasOwnProperty('quack') 43 | // => TypeError: Object object has no method 'hasOwnProperty' 44 | ``` 45 | 46 | We can still use `hasOwnProperty` from the `Object.prototype` though, if we call it with the `this` value set to something that 'looks like an object'. Function#call allows us to invoke any function with an altered `this` value. 47 | 48 | ```js 49 | // the first argument to call becomes the value of `this` 50 | // the rest of the arguments are passed to the function as per 51 | 52 | Object.prototype.hasOwnProperty.call(object, 'quack') // => true 53 | ``` 54 | 55 | # Task: 56 | 57 | Write a function `duckCount` that returns the number of arguments passed to it which have a property 'quack' defined directly on them. Do not match values inherited from prototypes. 58 | 59 | Example: 60 | 61 | ```js 62 | var notDuck = Object.create({quack: true}) 63 | var duck = {quack: true} 64 | duckCount(duck, notDuck) // 1 65 | ``` 66 | ## Arguments 67 | 68 | * You will be passed 0-20 arguments. Each argument could be of any type with any properties. Some of these items will have a 'quack' property. 69 | 70 | ## Conditions 71 | 72 | * Do not use any for/while loops or Array#forEach. 73 | * Do not create any counter/accumulator variables. 74 | * Do not create any unnecessary functions e.g. helpers. 75 | 76 | ## Hint 77 | 78 | * The `arguments` variable, available in every function, is an *Object* that quacks like an *Array*: 79 | 80 | ```js 81 | { 82 | 0: 'argument0', 83 | 1: 'argument1', // etc 84 | length: 2 85 | } 86 | ``` 87 | 88 | ## Resources 89 | 90 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call 91 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty 92 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in 93 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Array-like 94 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments 95 | 96 | 97 | ## Boilerplate 98 | 99 | ```js 100 | function duckCount() { 101 | // SOLUTION GOES HERE 102 | } 103 | 104 | module.exports = duckCount 105 | ``` 106 | -------------------------------------------------------------------------------- /exercises/basic_call/solution/solution.js: -------------------------------------------------------------------------------- 1 | function duckCount() { 2 | return Array.prototype.slice.call(arguments).filter(function(obj) { 3 | return Object.prototype.hasOwnProperty.call(obj, 'quack') 4 | }).length 5 | } 6 | 7 | module.exports = duckCount 8 | -------------------------------------------------------------------------------- /exercises/basic_every_some/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var random = require('../randomizer') 4 | var runner = require('../runner') 5 | 6 | function makeUser() { 7 | return { 8 | id: random.int(0, 1000), 9 | name: random.words(2, { capitalized: true }) 10 | } 11 | } 12 | 13 | function makeListOfUsers() { 14 | return random.arrayOf(10, 100, makeUser) 15 | } 16 | 17 | var good = makeListOfUsers() 18 | var bad = makeListOfUsers() 19 | var lists = random.arrayOf(20, function() { 20 | return random.arrayOf(20, function() { 21 | if (Math.random() < 0.95) { 22 | return good[random.int(0, 10)] 23 | } else { 24 | return bad[random.int(0, 10)] 25 | } 26 | }) 27 | }) 28 | 29 | var exercise = module.exports = runner.custom(function(fx, good, lists) { 30 | var test = fx(good) 31 | 32 | var goodLists = 0 33 | 34 | lists.forEach(function(list) { 35 | test(list) && ++goodLists 36 | }) 37 | 38 | return exercise.__('found_good_lists', goodLists) 39 | }).hideInput(good, lists) 40 | -------------------------------------------------------------------------------- /exercises/basic_every_some/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Écrivez une fonction qui reçoit une liste d’utilisateurs valides, et renvoie une fonction qui, elle, retournera `true` si tous les utilisateurs qu’on lui passe sont dans la liste originellement fournie. 4 | 5 | Vous n’aurez qu’à examiner la correspondance des propriétés `id`. 6 | 7 | ## Exemple 8 | 9 | ```js 10 | var goodUsers = [ 11 | { id: 1 }, 12 | { id: 2 }, 13 | { id: 3 } 14 | ] 15 | 16 | // `checkUsersValid` est la fonction que vous allez écrire 17 | var testAllValid = checkUsersValid(goodUsers) 18 | 19 | testAllValid([ 20 | { id: 2 }, 21 | { id: 1 } 22 | ]) 23 | // => true 24 | 25 | testAllValid([ 26 | { id: 2 }, 27 | { id: 4 }, 28 | { id: 1 } 29 | ]) 30 | // => false 31 | ``` 32 | 33 | ## Arguments 34 | 35 | * `goodUsers` : une liste d’utilisateurs valides 36 | 37 | Utilisez `Array#some()` et `Array#every()` pour vérifier que chaque utilisateur passé à la fonction que vous aurez générée existe dans le tableau initialement transmis à la fonction exportée. 38 | 39 | ## Conditions 40 | 41 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 42 | * Ne créez aucune fonction superflue 43 | 44 | ## Ressources 45 | 46 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/every 47 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/some 48 | 49 | ## Base de travail 50 | 51 | ```js 52 | function checkUsersValid(goodUsers) { 53 | return function allUsersValid(submittedUsers) { 54 | // VOTRE SOLUTION ICI 55 | }; 56 | } 57 | 58 | module.exports = checkUsersValid 59 | ``` 60 | -------------------------------------------------------------------------------- /exercises/basic_every_some/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 유효한 사용자의 목록을 받아, 주어진 사용자가 원래 사용자의 목록에 있으면 true를 반환하는 함수를 반환하세요. 4 | 5 | 아이디가 같은지만 확인하면 됩니다. 6 | 7 | ## 예제 8 | 9 | ```js 10 | var goodUsers = [ 11 | { id: 1 }, 12 | { id: 2 }, 13 | { id: 3 } 14 | ] 15 | 16 | // 선언할 함수 이름은 `checkUsersValid`입니다 17 | var testAllValid = checkUsersValid(goodUsers) 18 | 19 | testAllValid([ 20 | { id: 2 }, 21 | { id: 1 } 22 | ]) 23 | // => true 24 | 25 | testAllValid([ 26 | { id: 2 }, 27 | { id: 4 }, 28 | { id: 1 } 29 | ]) 30 | // => false 31 | ``` 32 | 33 | ## 인자 34 | 35 | * goodUsers: 유효한 사용자의 목록 36 | 37 | Array#some과 Array#every를 사용해 export된 함수에 넘겨진 배열에 반환된 함수에 넘겨진 모든 사용자가 있는지 확인하세요. 38 | 39 | ## 조건 40 | 41 | * for/while 반복문이나 Array#forEach를 사용하지 마세요. 42 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 43 | 44 | ## 참고 45 | 46 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/every 47 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/some 48 | 49 | ## 템플릿 50 | 51 | ```js 52 | function checkUsersValid(goodUsers) { 53 | return function allUsersValid(submittedUsers) { 54 | // 여기에 해답을 적으세요 55 | }; 56 | } 57 | 58 | module.exports = checkUsersValid 59 | ``` 60 | -------------------------------------------------------------------------------- /exercises/basic_every_some/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Return a function that takes a list of valid users, and returns a function that returns true if all of the supplied users exist in the original list of users. 4 | 5 | You only need to check that the ids match. 6 | 7 | ## Example 8 | 9 | ```js 10 | var goodUsers = [ 11 | { id: 1 }, 12 | { id: 2 }, 13 | { id: 3 } 14 | ] 15 | 16 | // `checkUsersValid` is the function you'll define 17 | var testAllValid = checkUsersValid(goodUsers) 18 | 19 | testAllValid([ 20 | { id: 2 }, 21 | { id: 1 } 22 | ]) 23 | // => true 24 | 25 | testAllValid([ 26 | { id: 2 }, 27 | { id: 4 }, 28 | { id: 1 } 29 | ]) 30 | // => false 31 | ``` 32 | 33 | ## Arguments 34 | 35 | * goodUsers: a list of valid users 36 | 37 | Use array#some and Array#every to check every user passed to your returned function exists in the array passed to the exported function. 38 | 39 | ## Conditions 40 | 41 | * Do not use any for/while loops or Array#forEach. 42 | * Do not create any unnecessary functions e.g. helpers. 43 | 44 | ## Resources 45 | 46 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/every 47 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/some 48 | 49 | ## Boilerplate 50 | 51 | ```js 52 | function checkUsersValid(goodUsers) { 53 | return function allUsersValid(submittedUsers) { 54 | // SOLUTION GOES HERE 55 | }; 56 | } 57 | 58 | module.exports = checkUsersValid 59 | ``` 60 | -------------------------------------------------------------------------------- /exercises/basic_every_some/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function checkUsersValid(goodUsers) { 2 | return function allUsersValid(submittedUsers) { 3 | return submittedUsers.every(function(submittedUser) { 4 | return goodUsers.some(function(goodUser) { 5 | return goodUser.id === submittedUser.id 6 | }) 7 | }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /exercises/basic_filter/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var loremIpsum = require('lorem-ipsum') 4 | var random = require('../randomizer') 5 | var runner = require('../runner') 6 | 7 | var input = random.arrayOf(10, 30, function() { 8 | return { message: loremIpsum() } 9 | }) 10 | 11 | module.exports = runner.hideInput(input) 12 | -------------------------------------------------------------------------------- /exercises/basic_filter/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Utilisez `Array#filter` pour écrire une fonction `getShortMessages()` 4 | 5 | `getShortMessages()` reçoit un tableau d’objets équipés d’une propriété `message`, et renvoie un tableau des messages qui ont *moins de 50 caractères de long*. 6 | 7 | Votre fonction doit renvoyer un tableau contenant les messages eux-mêmes, *sans leurs objets conteneurs*. 8 | 9 | ## Arguments 10 | 11 | * `messages` : un tableau d’objets aléatoires ayant l’aspect suivant : 12 | 13 | ```js 14 | { 15 | message: 'Esse id amet quis eu esse aute officia ipsum.' // aléatoire 16 | } 17 | ``` 18 | 19 | ## Conditions 20 | 21 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 22 | * Ne créez aucune fonction superflue 23 | 24 | ## Conseil 25 | 26 | * Essayez d’enchaîner des méthodes de `Array` ! 27 | 28 | ## Exemple de résultat 29 | 30 | ``` 31 | [ 'Tempor quis esse consequat sunt ea eiusmod.', 32 | 'Id culpa ad proident ad nulla laborum incididunt.', 33 | 'Ullamco in ea et ad anim anim ullamco est.', 34 | 'Est ut irure irure nisi.' ] 35 | ``` 36 | 37 | ## Ressources 38 | 39 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/filter 40 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map 41 | 42 | ## Base de travail 43 | 44 | ```js 45 | function getShortMessages(messages) { 46 | // VOTRE SOLUTION ICI 47 | } 48 | 49 | module.exports = getShortMessages 50 | ``` 51 | -------------------------------------------------------------------------------- /exercises/basic_filter/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | Array#filter를 사용해 `getShortMessages`를 호출하는 함수를 작성하세요. 3 | 4 | `getShortMessages`는 '.message' 프로퍼티를 가지는 배열을 받아 *50자보다 적은* 메시지의 배열을 반환합니다. 5 | 6 | 이 함수는 *메시지를 가진 객체가 아닌,* 메시지의 배열을 반환해야 합니다. 7 | 8 | ## 인자 9 | 10 | * messages: 밑에 있는 것처럼 생긴 임의의 객체를 10~100개 가지는 배열 11 | 12 | ```js 13 | { 14 | message: 'Esse id amet quis eu esse aute officia ipsum.' // 랜덤 15 | } 16 | ``` 17 | 18 | ## 조건 19 | 20 | * for/while 반복문이나 Array#forEach를 사용하지 마세요. 21 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 22 | 23 | ## 힌트 24 | 25 | * 배열 메소드를 몇 개 연결해 보세요. 26 | 27 | ## 예제 28 | 29 | ``` 30 | [ 'Tempor quis esse consequat sunt ea eiusmod.', 31 | 'Id culpa ad proident ad nulla laborum incididunt.', 32 | 'Ullamco in ea et ad anim anim ullamco est.', 33 | 'Est ut irure irure nisi.' ] 34 | ``` 35 | 36 | ## 참고 37 | 38 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter 39 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 40 | 41 | ## 템플릿 42 | 43 | ```js 44 | function getShortMessages(messages) { 45 | // 여기에 해답을 적으세요 46 | } 47 | 48 | module.exports = getShortMessages 49 | ``` 50 | -------------------------------------------------------------------------------- /exercises/basic_filter/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | Use Array#filter to write a function called `getShortMessages`. 3 | 4 | `getShortMessages` takes an array of objects with '.message' properties and returns an array of messages that are *less than < 50 characters long*. 5 | 6 | The function should return an array containing the messages themselves, *without their containing object*. 7 | 8 | ## Arguments 9 | 10 | * messages: an Array of 10 to 100 random objects that look something like this: 11 | 12 | ```js 13 | { 14 | message: 'Esse id amet quis eu esse aute officia ipsum.' // random 15 | } 16 | ``` 17 | 18 | ## Conditions 19 | 20 | * Do not use any for/while loops or Array#forEach. 21 | * Do not create any unnecessary functions e.g. helpers. 22 | 23 | ## Hint 24 | 25 | * Try chaining some Array methods! 26 | 27 | ## Example 28 | 29 | ``` 30 | [ 'Tempor quis esse consequat sunt ea eiusmod.', 31 | 'Id culpa ad proident ad nulla laborum incididunt.', 32 | 'Ullamco in ea et ad anim anim ullamco est.', 33 | 'Est ut irure irure nisi.' ] 34 | ``` 35 | 36 | ## Resources 37 | 38 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter 39 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 40 | 41 | ## Boilerplate 42 | 43 | ```js 44 | function getShortMessages(messages) { 45 | // SOLUTION GOES HERE 46 | } 47 | 48 | module.exports = getShortMessages 49 | ``` 50 | -------------------------------------------------------------------------------- /exercises/basic_filter/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function getShortMessages(messages) { 2 | return messages.filter(function(item) { 3 | return item.message.length < 50 4 | }).map(function(item) { 5 | return item.message 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_with_objectcreate/exercise.js: -------------------------------------------------------------------------------- 1 | var input = require('../../input') 2 | 3 | module.exports = input() 4 | .wrap(function(words, extend) { 5 | 6 | function User(title, name) { 7 | this.title = title 8 | this.name = name 9 | console.info('NEW USER: ' + this) 10 | } 11 | 12 | User.prototype.displayName = function() { 13 | return this.title + ' ' + this.name 14 | } 15 | 16 | User.prototype.toString = function() { 17 | return '[User: '+this.displayName()+']' 18 | } 19 | 20 | var BetterUser = extend(User) 21 | 22 | console.log('creating new users:') 23 | 24 | console.log("new User('Mrs.', 'Alice Smith')") 25 | var alice = new User('Mrs.', 'Alice Smith') 26 | console.log('') 27 | 28 | console.log("new BetterUser('Miss', 'Wei Lu')") 29 | var wei = new BetterUser('Miss', 'Wei Lu') 30 | console.log('') 31 | 32 | console.log("new BetterUser('Mr.', 'Joe Smith')") 33 | var joe = new BetterUser('Mr.', 'Joe Smith') 34 | console.log('') 35 | 36 | //Test inheritance 37 | console.log('Test inheritance') 38 | console.log('wei instanceof BetterUser: ', wei instanceof BetterUser) 39 | console.log('wei instanceof User: ', wei instanceof User) 40 | console.log('wei.displayName === User.prototype.displayName', wei.displayName === User.prototype.displayName) 41 | }) 42 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_with_objectcreate/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Create a new "BetterUser" type that extends "User" by overriding the User's `.toString` method. 4 | 5 | Your exported function will be passed the constructor function for a "User" type that looks like this: 6 | 7 | ```js 8 | /** 9 | * User Constructor. 10 | * 11 | * @param title {String} Title for User, e.g. 'Mr.', 'Mrs.', 'Dr.', etc. 12 | * @param name {String} Name of User e.g. 'John Smith' 13 | */ 14 | 15 | function User(title, name) { 16 | this.title = title 17 | this.name = name 18 | console.info('NEW USER: ' + this) 19 | } 20 | 21 | /** 22 | * Creates full display name for a user. 23 | * @return {String} Display name 24 | */ 25 | 26 | User.prototype.displayName = function() { 27 | return this.title + ' ' + this.name 28 | } 29 | 30 | /** 31 | * @return {String} Formatted name & title 32 | */ 33 | 34 | User.prototype.toString = function() { 35 | return '[User:'+this.displayName()+']' 36 | } 37 | ``` 38 | 39 | Note: you do not need to copy this into your solution. 40 | 41 | ## Example 42 | 43 | From your exported function, return a `BetterUser` constructor function that extends `User` with a custom `toString` method that works like so: 44 | 45 | ```js 46 | var joe = new BetterUser('Mr.', 'Joe Smith') // pass in title and name 47 | console.log('Hello ' + joe) // 'Hello [BetterUser: Mr. Joe Smith]' 48 | ``` 49 | 50 | ## Conditions 51 | 52 | * Don't call the User constructor unnecessarily! 53 | * Don't use `__proto__` 54 | * Do not create any unnecessary functions e.g. helpers. 55 | 56 | ## Resources 57 | 58 | * http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/ 59 | * http://tobyho.com/2011/11/11/js-object-inheritance/ 60 | * http://hughfdjackson.com/javascript/2012/01/05/prototypes:-the-short%28est-possible%29-story/ 61 | 62 | ## Boilerplate 63 | 64 | ```js 65 | // User is a constructor 66 | function upgradeUser(User) { 67 | 68 | // EDIT THESE AS NECESSARY 69 | function BetterUser() { 70 | 71 | } 72 | 73 | return BetterUser 74 | } 75 | 76 | module.exports = upgradeUser 77 | ``` 78 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_with_objectcreate/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function(User) { 2 | function BetterUser() { 3 | User.apply(this, arguments) 4 | } 5 | 6 | BetterUser.prototype = Object.create(User.prototype, {constructor: BetterUser}) 7 | 8 | BetterUser.prototype.toString = function() { 9 | return '[BetterUser: '+this.displayName()+']' 10 | } 11 | 12 | return BetterUser 13 | } 14 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_without_objectcreate/exercise.js: -------------------------------------------------------------------------------- 1 | var input = require('../../input') 2 | 3 | module.exports = input() 4 | .wrap(function(words, extend) { 5 | var assert = require('assert') 6 | 7 | function User(title, name) { 8 | this.title = title 9 | this.name = name 10 | console.info('NEW USER: ' + this) 11 | } 12 | 13 | User.prototype.displayName = function() { 14 | return this.title + ' ' + this.name 15 | } 16 | 17 | User.prototype.toString = function() { 18 | return '[User: '+this.displayName()+']' 19 | } 20 | 21 | // Hackery, have to call console.log at least once 22 | // otherwise it triggers the below Object.create exception 23 | // if the user happens to use it in their code. 24 | console.log('') 25 | 26 | // stamp out Object.create when running user's code 27 | var create = Object.create 28 | Object.create = function() { 29 | throw new Error('Do not use Object.create!') 30 | } 31 | 32 | var BetterUser = extend(User) 33 | 34 | // restore original Object.create 35 | Object.create = create 36 | 37 | console.log('creating new users:') 38 | 39 | console.log("new User('Mrs.', 'Alice Smith')") 40 | var alice = new User('Mrs.', 'Alice Smith') 41 | console.log('') 42 | 43 | console.log("new BetterUser('Miss', 'Wei Lu')") 44 | var wei = new BetterUser('Miss', 'Wei Lu') 45 | console.log('') 46 | 47 | console.log("new BetterUser('Mr.', 'Joe Smith')") 48 | var joe = new BetterUser('Mr.', 'Joe Smith') 49 | console.log('') 50 | 51 | //Test inheritance 52 | console.log('Test inheritance') 53 | console.log('wei instanceof BetterUser: ', wei instanceof BetterUser) 54 | console.log('wei instanceof User: ', wei instanceof User) 55 | console.log('wei.displayName === User.prototype.displayName', wei.displayName === User.prototype.displayName) 56 | 57 | 58 | }) 59 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_without_objectcreate/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Do the exact same task as "Basic Inhertance with Object.create", except don't use Object.create! 4 | 5 | Details copied below for your reference. 6 | 7 | ## Conditions 8 | 9 | * Don't call the User constructor unnecessarily! 10 | * Don't use `Object.create`. 11 | * Don't use `__proto__` 12 | 13 | ## Hints 14 | 15 | * Prototypes are always Objects 16 | * Your `BetterUser` instances need to 'inherit' from `User.prototype` 17 | * No reason you can't create dummy objects in the inheritance tree 18 | * Understand what `Object.create` does. 19 | 20 | ## Resources 21 | 22 | * http://www.bennadel.com/blog/2184-Object-create-Improves-Constructor-Based-Inheritance-In-Javascript-It-Doesn-t-Replace-It.htm 23 | 24 | ## Previous Task Definition 25 | 26 | Create a "BetterUser" that extends "User" by overriding the User's `.toString` method. 27 | 28 | Your exported function will be passed the constructor function for a "User" type that looks like this: 29 | 30 | ```js 31 | /** 32 | * User Constructor. 33 | * 34 | * @param title {String} Title for User, e.g. 'Mr.', 'Mrs.', 'Dr.', etc. 35 | * @param name {String} Name of User e.g. 'John Smith' 36 | */ 37 | 38 | function User(title, name) { 39 | this.title = title 40 | this.name = name 41 | console.info('NEW USER: ' + this) 42 | } 43 | 44 | /** 45 | * Creates full display name for a user. 46 | * @return {String} Display name 47 | */ 48 | 49 | User.prototype.displayName = function() { 50 | return this.title + ' ' + this.name 51 | } 52 | 53 | /** 54 | * @return {String} Formatted name & title 55 | */ 56 | 57 | User.prototype.toString = function() { 58 | return '[User: '+this.displayName()+']' 59 | } 60 | ``` 61 | 62 | Note: you do not need to copy this into your solution. 63 | 64 | ## Example 65 | 66 | From your exported function, return a `BetterUser` constructor function that extends `User` with a custom `toString` method that works like so: 67 | 68 | ```js 69 | var joe = new BetterUser('Mr.', 'Joe Smith') // pass in title and name 70 | console.log('Hello ' + joe) // 'Hello [BetterUser: Mr. Joe Smith]' 71 | ``` 72 | 73 | ## Boilerplate 74 | 75 | ```js 76 | // User is a constructor 77 | function upgradeUser(User) { 78 | 79 | // EDIT THESE AS NECESSARY 80 | function BetterUser() { 81 | 82 | } 83 | 84 | return BetterUser 85 | } 86 | 87 | module.exports = upgradeuser 88 | ``` 89 | -------------------------------------------------------------------------------- /exercises/basic_inheritance_without_objectcreate/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function(User) { 2 | function BetterUser() { 3 | User.apply(this, arguments) 4 | } 5 | 6 | function C() {} 7 | C.prototype = User.prototype 8 | BetterUser.prototype = new C() 9 | 10 | BetterUser.prototype.toString = function() { 11 | return '[BetterUser: '+this.displayName()+']' 12 | } 13 | 14 | return BetterUser 15 | } 16 | -------------------------------------------------------------------------------- /exercises/basic_map/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var random = require('../randomizer') 4 | var runner = require('../runner') 5 | 6 | var input = random.arrayOfInts(20, 0, 9) 7 | 8 | var usedMap = false 9 | var regularMap = Array.prototype.map 10 | 11 | Array.prototype.map = function() { 12 | usedMap = true 13 | return regularMap.apply(this, arguments) 14 | } 15 | 16 | module.exports = runner.init(function() { 17 | usedMap = false 18 | }).wrapUp(function(callback) { 19 | if (!usedMap) { 20 | this.emit('fail', this.__('didnt_use_map')); 21 | } else { 22 | this.emit('pass', this.__('used_map')); 23 | } 24 | callback(null, usedMap) 25 | })(input) 26 | -------------------------------------------------------------------------------- /exercises/basic_map/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Convertissez le code suivant pour utiliser `Array#map` plutôt qu’une boucle : 4 | 5 | ```js 6 | function doubleAll(numbers) { 7 | var result = [] 8 | for (var i = 0; i < numbers.length; i++) { 9 | result.push(numbers[i] * 2) 10 | } 11 | return result 12 | } 13 | 14 | module.exports = doubleAll 15 | ``` 16 | 17 | ## Arguments 18 | 19 | * `numbers` : Un tableau d’entiers 20 | 21 | ## Conditions 22 | 23 | * Votre solution doit utiliser `Array.prototype.map` 24 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 25 | * Ne créez aucune fonction superflue 26 | 27 | ## Ressources 28 | 29 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map 30 | 31 | ## Base de travail 32 | 33 | ```js 34 | function doubleAll(numbers) { 35 | // VOTRE SOLUTION ICI 36 | } 37 | 38 | module.exports = doubleAll 39 | ``` 40 | -------------------------------------------------------------------------------- /exercises/basic_map/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 다음 코드의 for 반복문을 Array#map으로 바꾸세요. 4 | 5 | ```js 6 | function doubleAll(numbers) { 7 | var result = [] 8 | for (var i = 0; i < numbers.length; i++) { 9 | result.push(numbers[i] * 2) 10 | } 11 | return result 12 | } 13 | 14 | module.exports = doubleAll 15 | ``` 16 | 17 | ## 인자 18 | 19 | * numbers: 0에서 9사이의 정수를 0~20개 가지는 배열 20 | 21 | ## 조건 22 | 23 | * Array.prototype.map()을 사용하셔야 합니다. 24 | * for/while 반복문이나 Array.prototype.forEach를 사용하지 마세요. 25 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 26 | 27 | ## 참고 28 | 29 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 30 | 31 | ## 템플릿 32 | 33 | ```js 34 | function doubleAll(numbers) { 35 | // 여기에 해답을 적으세요 36 | } 37 | 38 | module.exports = doubleAll 39 | ``` 40 | -------------------------------------------------------------------------------- /exercises/basic_map/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Convert the following code from a for-loop to Array#map: 4 | 5 | ```js 6 | function doubleAll(numbers) { 7 | var result = [] 8 | for (var i = 0; i < numbers.length; i++) { 9 | result.push(numbers[i] * 2) 10 | } 11 | return result 12 | } 13 | 14 | module.exports = doubleAll 15 | ``` 16 | 17 | ## Arguments 18 | 19 | * numbers: An Array of 0 to 20 Integers between 0 and 9 20 | 21 | ## Conditions 22 | 23 | * Your solution should use Array.prototype.map() 24 | * Do not use any for/while loops or Array.prototype.forEach. 25 | * Do not create any unnecessary functions e.g. helpers. 26 | 27 | ## Resources 28 | 29 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 30 | 31 | ## Boilerplate 32 | 33 | ```js 34 | function doubleAll(numbers) { 35 | // SOLUTION GOES HERE 36 | } 37 | 38 | module.exports = doubleAll 39 | ``` 40 | -------------------------------------------------------------------------------- /exercises/basic_map/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function doubleAll(numbers) { 2 | return numbers.map(function double(num) { 3 | return num * 2 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /exercises/basic_recursion/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var loremIpsum = require('lorem-ipsum') 4 | var runner = require('../runner') 5 | 6 | var input = loremIpsum({count: 1, units:'paragraphs'}) 7 | .replace(/([^\w ])/g, '')// remove non-words and spaces 8 | .toLowerCase() // lowercase I guess 9 | .split(' ') // create array of words 10 | 11 | module.exports = runner.custom(function(fx, input) { 12 | return fx(input, function(prev, curr) { 13 | prev[curr] = ++prev[curr] || 1 14 | return prev 15 | }, {}) 16 | })(input) 17 | -------------------------------------------------------------------------------- /exercises/basic_recursion/problem.fr.md: -------------------------------------------------------------------------------- 1 | La récursivité est un concept fondamental de programmation qui peut apporter des solutions élégantes et efficaces à des problèmes algorithmiques. En fait, la récursivité est si puissante que tous les comportements itératifs peuvent être définis sous forme de fonctions récursives. Vous la trouverez particulièrement indispensable lorsque vous parcourrez des structures de données imbriquées. 2 | 3 | Une fonction récursive est une fonction qui s’appelle elle-même. Par exemple, la fonction récursive ci-dessous reçoit un tableau de mots et renvoie un tableau de ces mêmes mots en majuscules. 4 | 5 | ```js 6 | function toUpperArray(items) { 7 | if (!items.length) return [] // condition de fin 8 | var head = items[0] // élément sur lequel travailler 9 | head = head.toUpperCase() // exécution du travail 10 | var tail = items.slice(1) // passage à l’étape suivante 11 | return [head].concat(toUpperArray(tail)) // récursion terminale 12 | } 13 | 14 | toUpperArray(['bonjour', 'monde']) // => ['BONJOUR', 'MONDE'] 15 | ``` 16 | 17 | Cet exercice vise à vous faire découvrir la récursivité en implémentant une fonction familière à l’aide d’une fonction récursive. 18 | 19 | # Défi 20 | 21 | Implémentez `Array#reduce()` de façon récursive. 22 | 23 | Pour vérifier que votre récursivité fonctionne correctement, nous utiliserons votre implémentation de `reduce()` avec notre solution pour l’exercice précédent, « Les bases : Reduce ». Votre fonction recevra donc un tableau de mots et une fonction, ainsi qu’une valeur initiale, et devrait donc renvoyer à terme un objet contenant les comptages pour chaque mot. Naturellement, votre implémentation de `reduce()` ne fait pas tout ça : c’est la fonction que nous vous passerons en argument qui s’en chargera. 24 | 25 | Par souci de simplicité, votre implémentation de `reduce()` **n’a pas besoin de gérer le cas où l’argument de valeur initiale est manquant**. Vous pouvez supposer qu’il sera toujours fourni. 26 | 27 | ## Arguments 28 | 29 | * `arr` : Un tableau sur lequel appliquer la réduction 30 | * `fn` : La fonction à utiliser comme étape de réduction. Tout comme pour le `Array#reduce()` standard, cette fonction recevra comme arguments `previousValue`, `currentValue`, `index` et le tableau sur lequel on est en train d’itérer. 31 | * `init` : Valeur initiale de la réduction. Contrairement à `Array#reduce()`, cet argument est ici obligatoire (vous pouvez supposer qu’il sera toujours fourni). 32 | 33 | ## Exemple 34 | 35 | ```js 36 | // Votre fonction `reduce()` devrait se comporter comme la méthode 37 | // standard `Array#reduce()`, à ceci près qu’elle recevra le tableau 38 | // à traiter en premier argument : 39 | 40 | reduce([1,2,3], function(prev, curr, index, arr) { 41 | return prev + curr 42 | }, 0) 43 | // => 6 44 | ``` 45 | 46 | ## Conditions 47 | 48 | * N’utilisez pas de boucle (`for`, `while`…) 49 | * N’utilisez aucune méthode de `Array`, du genre `Array#map()` ou `Array#reduce()` 50 | 51 | ## Ressources 52 | 53 | * https://fr.wikipedia.org/wiki/R%C3%A9cursivit%C3%A9 54 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce 55 | 56 | ## Base de travail 57 | 58 | ```js 59 | function reduce(arr, fn, initial) { 60 | // VOTRE SOLUTION ICI 61 | } 62 | 63 | module.exports = reduce 64 | ``` 65 | -------------------------------------------------------------------------------- /exercises/basic_recursion/problem.ko.md: -------------------------------------------------------------------------------- 1 | 재귀는 알고리즘 문제를 우아하고 효과적으로 풀 수 있는 기초 프로그래밍 개념입니다. 사실, 재귀는 모든 반복 행동을 재귀 함수만으로 정의할 수 있을 정도로 강력합니다. 중첩된 데이터 구조를 반복할 때, 재귀는 필수적이라는 걸 아실 수 있을 겁니다. 2 | 3 | 재귀 함수는 자기 자신을 호출하는 함수입니다. 예를 들어, 이 재귀 함수는 단어의 배열을 받아, 단어를 대문자로 바꾼 배열을 반환합니다. 4 | 5 | ```js 6 | function toUpperArray(items) { 7 | if (!items.length) return [] // 종료 조건 8 | var head = items[0] // 처리할 아이템 9 | head = head.toUpperCase() // 행동 수행 10 | var tail = items.slice(1) // 다음 11 | return [head].concat(toUpperArray(tail)) // 귀납적 단계 12 | } 13 | 14 | toUpperArray(['hello', 'world']) // => ['HELLO', 'WORLD'] 15 | ``` 16 | 17 | 이 연습 문제의 핵심은 인숙한 인터페이스를 재귀 함수로 구현해 재귀에 익숙해지는 것입니다. 18 | 19 | # 해야할 일 20 | 21 | 재귀를 이용해 Array#reduce를 구현하세요. 22 | 23 | reduce가 올바르게 동작하는지 테스트하기 위해 구현하신 reduce를 basic_reduce 해결책의 실행에 사용합니다. 즉, reduce 함수에 단어의 배열, 함수, 배열 안의 각 단어의 횟수를 반환할 객체의 초기값이 넘겨집니다. 구현하실 reduce에 넘겨지기 때문에 이 부분을 구현하실 필요는 없습니다. 24 | 25 | 단순하게 하기 위해, 구현하실 reduce에 **초기값이 없을 경우에 대한 대비를 하실 필요는 없습니다**. 초기값이 언제나 있으리라 가정하셔도 됩니다. 26 | 27 | ## 인자 28 | 29 | * arr: reduce 할 배열 30 | * fn: reduce 단계에서 사용할 함수. 일반 Array#reduce처럼, 이 함수에는 previousValue, currentValue, index, 반복할 배열을 넘겨야 합니다. 31 | * init: reduce의 초기값. Array#reduce와는 다르게, 이 값은 필요합니다. (그리고 항상 제공된다고 가정합니다.) 32 | 33 | ## 예제 34 | 35 | ```js 36 | // reduce 함수는 첫 번째 인자로 배열을 받는 것을 37 | // 제외하고 일반 Array#reduce와 같은 동작을 합니다 38 | 39 | reduce([1,2,3], function(prev, curr, index, arr) { 40 | return prev + curr 41 | }, 0) 42 | // => 6 43 | ``` 44 | 45 | ## 조건 46 | 47 | * for/while 반복문을 사용하지 마세요. 48 | * Array#map이나 Array#reduce 같은 Array 메소드를 사용하지 마세요. 49 | 50 | ## 참고 51 | 52 | * https://en.wikipedia.org/wiki/Recursion 53 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 54 | 55 | ## 템플릿 56 | 57 | ```js 58 | function reduce(arr, fn, initial) { 59 | // 여기에 해답을 적으세요 60 | } 61 | 62 | module.exports = reduce 63 | ``` 64 | -------------------------------------------------------------------------------- /exercises/basic_recursion/problem.md: -------------------------------------------------------------------------------- 1 | Recursion is a fundamental programming concept which can lead to elegant and efficient solutions to algorithmic problems. In fact, recursion is so powerful, all iterating behaviour can be defined using recursive functions. You will find recursion indispensable when iterating over nested data structures. 2 | 3 | A recursive function is a function which calls itself. For example, this recursive function will take an array of words, and return an array of those words, uppercased. 4 | 5 | ```js 6 | function toUpperArray(items) { 7 | if (!items.length) return [] // end condition 8 | var head = items[0] // item to operate on 9 | head = head.toUpperCase() // perform action 10 | var tail = items.slice(1) // next 11 | return [head].concat(toUpperArray(tail)) // recursive step 12 | } 13 | 14 | toUpperArray(['hello', 'world']) // => ['HELLO', 'WORLD'] 15 | ``` 16 | 17 | The point of this exercise is to familiarise yourself with recursion by implementing a familiar interface using a recursive function. 18 | 19 | # Task 20 | 21 | Implement Array#reduce using recursion. 22 | 23 | To test your reduction works correctly we will use your reduce implementation to execute our solution to the previous basic_reduce problem. i.e. your reduce function will be passed an array of words, and a function, and an initial value which will return an object containing the counts for each word found in the array. You don't need to implement this functionality, it will be supplied to your reduce implementation. 24 | 25 | For simplicity, your implementation of reduce **need not replicate the behaviour of a reduce missing an initial value**. You may assume the initial value will always be supplied. 26 | 27 | ## Arguments 28 | 29 | * arr: An Array to reduce over 30 | * fn: Function to use as the reduction step. Like regular Array#reduce, this function must be passed previousValue, currentValue, index and the array we're iterating over. 31 | * init: Initial value of the reduction. Unlike Array#reduce, this value is required (and you may assume it will always be supplied). 32 | 33 | ## Example 34 | 35 | ```js 36 | // Your reduce function should behave the same as a 37 | // regular Array#reduce, but it will take the array 38 | // to operate on as the first argument: 39 | 40 | reduce([1,2,3], function(prev, curr, index, arr) { 41 | return prev + curr 42 | }, 0) 43 | // => 6 44 | ``` 45 | 46 | ## Conditions 47 | 48 | * Do not use any for/while loops. 49 | * Do not use any Array methods like Array#map or Array#reduce. 50 | 51 | ## Resources 52 | 53 | * https://en.wikipedia.org/wiki/Recursion 54 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 55 | 56 | ## Boilerplate 57 | 58 | ```js 59 | function reduce(arr, fn, initial) { 60 | // SOLUTION GOES HERE 61 | } 62 | 63 | module.exports = reduce 64 | ``` 65 | -------------------------------------------------------------------------------- /exercises/basic_recursion/solution/solution.js: -------------------------------------------------------------------------------- 1 | function reduce(arr, fn, initial) { 2 | return (function reduceOne(index, value) { 3 | if (index > arr.length - 1) return value // end condition 4 | return reduceOne(index + 1, fn(value, arr[index], index, arr)) // calculate & pass values to next step 5 | })(0, initial) // IIFE. kick off recursion with initial values 6 | } 7 | 8 | module.exports = reduce 9 | -------------------------------------------------------------------------------- /exercises/basic_recursion/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | function reduce(arr, fn, initial) { 2 | return (function reduceOne(index, value) { 3 | // condition de fin 4 | if (index > arr.length - 1) return value 5 | 6 | // calculer les valeurs et les passer à l’étape suivante 7 | return reduceOne(index + 1, fn(value, arr[index], index, arr)) 8 | })(0, initial) // IIFE. Démarrer la récursion avec les valeurs de départ 9 | } 10 | 11 | module.exports = reduce 12 | -------------------------------------------------------------------------------- /exercises/basic_reduce/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var loremIpsum = require('lorem-ipsum') 4 | var runner = require('../runner') 5 | 6 | var input = loremIpsum({ count: 1, units: 'paragraphs' }) 7 | .replace(/([^\w ])/g, '')// remove non-words and spaces 8 | .toLowerCase() // lowercase I guess 9 | .split(' ') // create array of words 10 | 11 | module.exports = runner(input) 12 | -------------------------------------------------------------------------------- /exercises/basic_reduce/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | En partant d’un tableau de `String`s, utilisez `Array#reduce()` pour créer un objet qui contient, pour chaque `String` du tableau, le nombre de fois qu’elle y apparaît. Renvoyez l’objet directement (inutile de faire un `console.log()`). 4 | 5 | ## Exemple 6 | 7 | ```js 8 | var inputWords = ['Apple', 'Banana', 'Apple', 'Durian', 'Durian', 'Durian'] 9 | 10 | console.log(countWords(inputWords)) 11 | 12 | // => 13 | // { 14 | // Apple: 2, 15 | // Banana: 1, 16 | // Durian: 3 17 | // } 18 | ``` 19 | 20 | ## Arguments 21 | 22 | * `inputWords` : un tableau de `String`s aléatoires. 23 | 24 | ## Conditions 25 | 26 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 27 | * Ne créez aucune fonction superflue 28 | 29 | ## Ressources 30 | 31 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce 32 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 33 | 34 | ## Base de travail 35 | 36 | ```js 37 | function countWords(inputWords) { 38 | // VOTRE SOLUTION ICI 39 | } 40 | 41 | module.exports = countWords 42 | ``` 43 | -------------------------------------------------------------------------------- /exercises/basic_reduce/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 주어진 문자열의 배열을 `Array#reduce`를 이용해 각 문자열이 배열에 나타난 숫자를 가지는 객체를 만드세요. 직접 객체를 반환해야 합니다.(console.log는 필요없어요.) 4 | 5 | ## 예제 6 | 7 | ```js 8 | var inputWords = ['Apple', 'Banana', 'Apple', 'Durian', 'Durian', 'Durian'] 9 | 10 | console.log(countWords(inputWords)) 11 | 12 | // => 13 | // { 14 | // Apple: 2, 15 | // Banana: 1, 16 | // Durian: 3 17 | // } 18 | ``` 19 | 20 | ## 인자 21 | 22 | * inputWords: 임의의 문자열 배열 23 | 24 | ## 조건 25 | 26 | * for/while 반복문이나 Array#forEach를 사용하지 마세요. 27 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 28 | 29 | ## 참고 30 | 31 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 32 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 33 | 34 | ## 템플릿 35 | 36 | ```js 37 | function countWords(inputWords) { 38 | // 여기에 해답을 적으세요 39 | } 40 | 41 | module.exports = countWords 42 | ``` 43 | -------------------------------------------------------------------------------- /exercises/basic_reduce/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Given an Array of strings, use `Array#reduce` to create an object that contains the number of times each string occured in the array. Return the object directly (no need to console.log). 4 | 5 | ## Example 6 | 7 | ```js 8 | var inputWords = ['Apple', 'Banana', 'Apple', 'Durian', 'Durian', 'Durian'] 9 | 10 | console.log(countWords(inputWords)) 11 | 12 | // => 13 | // { 14 | // Apple: 2, 15 | // Banana: 1, 16 | // Durian: 3 17 | // } 18 | ``` 19 | 20 | ## Arguments 21 | 22 | * inputWords: An array of random Strings. 23 | 24 | ## Conditions 25 | 26 | * Do not use any for/while loops or Array#forEach. 27 | * Do not create any unnecessary functions e.g. helpers. 28 | 29 | ## Resources 30 | 31 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 32 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 33 | 34 | ## Boilerplate 35 | 36 | ```js 37 | function countWords(inputWords) { 38 | // SOLUTION GOES HERE 39 | } 40 | 41 | module.exports = countWords 42 | ``` 43 | -------------------------------------------------------------------------------- /exercises/basic_reduce/solution/solution.js: -------------------------------------------------------------------------------- 1 | function countWords(arr) { 2 | return arr.reduce(function(countMap, word) { 3 | countMap[word] = ++countMap[word] || 1 // increment or initialize to 1 4 | return countMap 5 | }, {}) // second argument to reduce initialises countMap to {} 6 | } 7 | 8 | module.exports = countWords 9 | -------------------------------------------------------------------------------- /exercises/basic_reduce/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | function countWords(arr) { 2 | return arr.reduce(function(countMap, word) { 3 | countMap[word] = ++countMap[word] || 1 // incrémenter ou initialiser à 1 4 | return countMap 5 | }, {}) // le 2e argument de reduce initialise `countMap` à `{}` 6 | } 7 | 8 | module.exports = countWords 9 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | "use strict" 4 | 5 | var path = require('path') 6 | var runner = require('../runner') 7 | 8 | module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), { localized: true }) 9 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Modifiez la fonction récursive `repeat()`, fournie plus bas dans la base de travail, de façon à ce qu’elle ne bloque pas la boucle événementielle (c’est-à-dire pour qu’elle laisse passer les timers et gestionnaires E/S). Il vous faudra nécessairement en faire une fonction asynchrone. 4 | 5 | Un timeout sera déclenché après 100 millisecondes, qui affichera les résultats du test et terminara le processus. `repeat()` doit relâcher son contrôle sur la boucle événementielle pour permettre au timeout de s’exécuter avant que toutes les opérations ne se terminent. 6 | 7 | Essayez d’exécuter l’opération passée à `repeat()` autant de fois que possible avant le timeout ! 8 | 9 | ## Conditions 10 | 11 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 12 | * Ne créez aucune fonction superflue 13 | 14 | ## Conseils 15 | 16 | Si votre programme prend trop de temps à s’exécuter, vous avez probablement un souci. Utilisez Ctrl+C pour arrêter le processus Node. 17 | 18 | ## Ressources 19 | 20 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers 21 | 22 | ## Base de travail 23 | 24 | ```js 25 | function repeat(operation, num) { 26 | // modifiez cette fonction pour la rendre interruptible 27 | if (num <= 0) return 28 | operation() 29 | return repeat(operation, --num) 30 | } 31 | 32 | module.exports = repeat 33 | ``` 34 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 템플릿으로 주어진 재귀 `repeat` 함수를, 이벤트 루프를 중단하지 않도록(Timer와 IO 핸들러가 발동할 수 있도록) 수정하세요. repeat를 비동기적으로 만들 필요가 있습니다. 4 | 5 | 테스트의 결과를 출력하고 프로세스를 종료하는 타임아웃은 100 밀리 초 후에 발동하도록 예약되어 있습니다. 모든 연산이 완료되기 전에 타임아웃이 작업을 중단할 수 있도록 `repeat`는 이벤트 루프의 제어권을 해제해야 합니다. 6 | 7 | timeout이 발동하기 전에 가능한 많은 작업을 수행해 보십시오! 8 | 9 | ## 조건 10 | 11 | * 어떤 for/while 반복문이나 Array#forEach도 사용하지 마세요. 12 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 13 | 14 | ## 힌트 15 | 16 | * 프로그램의 수행시간이 오래 걸린다면, 뭔가 잘못된 것입니다. 17 | 노드 프로세스를 죽이려면 Control - C를 사용하세요. 18 | 19 | ## 참고 20 | 21 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers 22 | 23 | ## 템플릿 24 | 25 | ```js 26 | function repeat(operation, num) { 27 | // 도중에 중단될 수 있도록 이곳을 고치세요 28 | if (num <= 0) return 29 | operation() 30 | return repeat(operation, --num) 31 | } 32 | 33 | module.exports = repeat 34 | ``` 35 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Modify the recursive `repeat` function provided in the boilerplate, such that it does not block the event loop (i.e. Timers and IO handlers can fire). This necessarily requires repeat to be asynchronous. 4 | 5 | A timeout is queued to fire after 100 milliseconds, which will print the results of the test and exit the process. `repeat` should release control of the event loop to allow the timeout to interrupt before all of the operations complete. 6 | 7 | Try to perform as many operations as you can before the timeout fires! 8 | 9 | ## Conditions 10 | 11 | * Do not use any for/while loops or Array#forEach. 12 | * Do not create any unnecessary functions e.g. helpers. 13 | 14 | ## Hints 15 | 16 | * If your program takes a long time to run, something is probably wrong. 17 | Use Control - C to kill the node process. 18 | 19 | ## Resources 20 | 21 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers 22 | 23 | ## Boilerplate 24 | 25 | ```js 26 | function repeat(operation, num) { 27 | // modify this so it can be interrupted 28 | if (num <= 0) return 29 | operation() 30 | return repeat(operation, --num) 31 | } 32 | 33 | module.exports = repeat 34 | ``` 35 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/solution/solution.js: -------------------------------------------------------------------------------- 1 | function repeat(operation, num) { 2 | if (num <= 0) return 3 | 4 | operation() 5 | 6 | // release control every 10 or so 7 | // iterations. 8 | // 10 is arbitrary. 9 | if (num % 10 === 0) { 10 | setTimeout(function() { 11 | repeat(operation, --num) 12 | }) 13 | } else { 14 | repeat(operation, --num) 15 | } 16 | } 17 | 18 | module.exports = repeat 19 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | function repeat(operation, num) { 2 | if (num <= 0) return 3 | 4 | operation() 5 | 6 | // relâcher le contrôle tous les 10 tours 7 | // (10 est une taille de lot arbitraire). 8 | if (num % 10 === 0) { 9 | setTimeout(function() { 10 | repeat(operation, --num) 11 | }) 12 | } else { 13 | repeat(operation, --num) 14 | } 15 | } 16 | 17 | module.exports = repeat 18 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var path = require('path') 4 | 5 | var repeat = require(path.resolve(process.cwd(), process.argv[2])) 6 | 7 | var count = 0 8 | var CYCLES = 10000 9 | 10 | function operation() { 11 | for (var i = 0; i < 1000000; i++) { 12 | // burn some CPU cycles 13 | } 14 | // count how many times this function was called 15 | count++ 16 | } 17 | 18 | 19 | console.error() 20 | console.error(operation.toString()) 21 | console.error() 22 | console.error('Trying to execute the above operation %d times, this may crash…', CYCLES) 23 | console.error() 24 | console.error('Press Ctrl+C to kill.') 25 | console.error() 26 | 27 | var start = Date.now() 28 | repeat(operation, CYCLES) 29 | 30 | setTimeout(function() { 31 | var end = Date.now() 32 | console.error('Performed %d operations.', count) 33 | 34 | if (count < 10) { 35 | console.log('Fail! You should have completed some operations!') 36 | process.exit(1) 37 | } 38 | 39 | if (count >= CYCLES) { 40 | console.log('Fail! Should not have completed all operations!') 41 | process.exit(1) 42 | } 43 | 44 | console.log('Operations successfully interrupted!') 45 | console.error('Interrupted after %d milliseconds.', end - start) 46 | process.exit() 47 | }, 100) 48 | -------------------------------------------------------------------------------- /exercises/blocking_event_loop/wrapper_fr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var path = require('path') 4 | 5 | var repeat = require(path.resolve(process.cwd(), process.argv[2])) 6 | 7 | var count = 0 8 | var CYCLES = 10000 9 | 10 | function operation() { 11 | for (var i = 0; i < 1000000; i++) { 12 | // burn some CPU cycles 13 | } 14 | // count how many times this function was called 15 | count++ 16 | } 17 | 18 | console.error() 19 | console.error(operation.toString()) 20 | console.error() 21 | console.error('J’essaie de répéter l’opération %d fois, ce peut planter…', CYCLES) 22 | console.error() 23 | console.error('Pressez Ctrl+C pour interrompre.') 24 | console.error() 25 | 26 | var start = Date.now() 27 | repeat(operation, CYCLES) 28 | 29 | setTimeout(function() { 30 | var end = Date.now() 31 | console.error('J’ai effectué %d opérations.', count) 32 | 33 | if (count < 10) { 34 | console.log('Raté ! Vous auriez dû pouvoir effectuer quelques opérations !') 35 | process.exit(1) 36 | } 37 | 38 | if (count >= CYCLES) { 39 | console.log('Raté ! Je n’aurais pas du pouvoir aller au bout !') 40 | process.exit(1) 41 | } 42 | 43 | console.log('Opérations interrompues avec succès !') 44 | console.error('Interrompu après %d millisecondes.', end - start) 45 | process.exit() 46 | }, 100) 47 | -------------------------------------------------------------------------------- /exercises/currying/exercise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by naor on 10/10/13. 3 | * Migrated by tdd on 03/16/15. 4 | */ 5 | "use strict"; 6 | 7 | var runner = require('../runner') 8 | 9 | var exercise = module.exports = runner.custom(function(curryN) { 10 | var result = [] 11 | result.push("Testing curryN without n:") 12 | 13 | function add3(one, two, three) { 14 | return one + two + three 15 | } 16 | 17 | var curryC = curryN(add3) 18 | var curryB = curryC(1) 19 | var curryA = curryB(2) 20 | 21 | result.push("curryA(3) => " + curryA(3)) // => 6 22 | result.push("curryA(10) => " + curryA(10)) // => 13 23 | result.push("curryN(add3)(1)(2)(3) => " + curryN(add3)(1)(2)(3)) // => 6 24 | 25 | function joinWithComma() { 26 | return Array.prototype.join.call(arguments, ','); 27 | } 28 | 29 | var curry1 = curryN(joinWithComma, 4) 30 | var curry2 = curry1(1) 31 | var curry3 = curry2(2) 32 | var curry4 = curry3(3) 33 | result.push(curry4(4)) 34 | result.push("Testing we can change the inner function to generate different set of outputs:") 35 | var curry3 = curry2(6) 36 | var curry4 = curry3(3) 37 | result.push(curry4(5)) 38 | 39 | result.push("Testing curryN with n:") 40 | 41 | function strConcat(){ 42 | var args = Array.prototype.slice.call(arguments) 43 | return Array.prototype.concat.apply([], args).join(" ") 44 | } 45 | 46 | var words = exercise.__('five_words').split(',') 47 | var call = words.map(function(word) { return "('" + word + "')" }).join('') 48 | result.push( 49 | "curryN(strConcat, 5)" + call + ") => " + 50 | curryN(strConcat, 5)(words[0])(words[1])(words[2])(words[3])(words[4])) 51 | 52 | return result 53 | }).hideInput() 54 | -------------------------------------------------------------------------------- /exercises/currying/problem.fr.md: -------------------------------------------------------------------------------- 1 | Voici un exemple d’implémentation de `curry3()`, qui « curryfie » jusqu’à 3 arguments : 2 | 3 | ```js 4 | function curry3(fun){ 5 | return function(one){ 6 | return function(two){ 7 | return function (three){ 8 | return fun(one, two, three) 9 | } 10 | } 11 | } 12 | } 13 | ``` 14 | 15 | Si nous devions utiliser cette implémentation avec la fonction d’exemple suivante : 16 | 17 | ```js 18 | function abc(one, two, three) { 19 | return one/two/three 20 | } 21 | ``` 22 | 23 | Ça donnerait quelque chose comme ça : 24 | 25 | ```js 26 | var curryC = curry3(abc) 27 | var curryB = curryC(6) 28 | var curryA = curryB(3) 29 | 30 | console.log(curryA(2)) // => 1 31 | ``` 32 | 33 | # Défi 34 | 35 | Dans cet exercice, nous allons implémenter la fonction `curry()` pour un nombre quelconque d’arguments. 36 | 37 | `curryN()` acceptera deux paramètres : 38 | 39 | * `fn` : la fonction à « curryfier ». 40 | * `n` : un nombre optionnel d’arguments à « curryfier ». Si cet argument est manquant, `curryN()` utilisera l’arité de `fn` à la place (c’est-à-dire son nombre de paramètres déclarés). 41 | 42 | ## Exemple 43 | 44 | ```js 45 | function add3(one, two, three) { 46 | return one + two + three 47 | } 48 | 49 | var curryC = curryN(add3) 50 | var curryB = curryC(1) 51 | var curryA = curryB(2) 52 | console.log(curryA(3)) // => 6 53 | console.log(curryA(10)) // => 13 54 | 55 | console.log(curryN(add3)(1)(2)(3)) // => 6 56 | ``` 57 | 58 | ## Conditions 59 | 60 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 61 | 62 | ## Conseil 63 | 64 | On peut détecter l’arité d’une fonction avec la propriété `.length` de celle-ci. 65 | 66 | ## Base de travail 67 | 68 | ```js 69 | function curryN(fn, n) { 70 | // VOTRE SOLUTION ICI 71 | } 72 | 73 | module.exports = curryN 74 | ``` 75 | -------------------------------------------------------------------------------- /exercises/currying/problem.ko.md: -------------------------------------------------------------------------------- 1 | 이 프로그램은 세 인자를 curries up하는 `curry3`의 구현 예제입니다. 2 | 3 | ```js 4 | function curry3(fun){ 5 | return function(one){ 6 | return function(two){ 7 | return function (three){ 8 | return fun(one, two, three) 9 | } 10 | } 11 | } 12 | } 13 | ``` 14 | 15 | 이 구현을 이런 간단한 함수에 사용하려 한다고 해봅시다. 16 | 17 | ```js 18 | function abc(one, two, three) { 19 | return one/two/three 20 | } 21 | ``` 22 | 23 | 이는 이렇게 동작할 것입니다. 24 | 25 | ```js 26 | var curryC = curry3(abc) 27 | var curryB = curryC(6) 28 | var curryA = curryB(3) 29 | 30 | console.log(curryA(2)) // => 1 31 | ``` 32 | 33 | # 해야할 일 34 | 35 | 이 도전 과제에서는, 임의의 수의 인자를 받는 'curry' 함수를 구현할 것입니다. 36 | 37 | `curryN`은 두 파라미터를 받습니다. 38 | 39 | * fn: curry할 함수. 40 | * n: 생략할 수 있는 curry할 인자의 수. 주어지지 않으면 `curryN`은 fn의 인자를 `n`의 값으로 사용해야 함. 41 | 42 | ## 예제 43 | 44 | ```js 45 | function add3(one, two, three) { 46 | return one + two + three 47 | } 48 | 49 | var curryC = curryN(add3) 50 | var curryB = curryC(1) 51 | var curryA = curryB(2) 52 | console.log(curryA(3)) // => 6 53 | console.log(curryA(10)) // => 13 54 | 55 | console.log(curryN(add3)(1)(2)(3)) // => 6 56 | ``` 57 | 58 | ## 조건 59 | 60 | * 어떤 for/while 반복문이나 Array#forEach도 사용하지 마세요. 61 | 62 | ## 힌트 63 | 64 | * 함수의 예상되는 인자의 수는 함수의 .length 속성을 확인해서 알 수 있습니다. 65 | 66 | ## 템플릿 67 | 68 | ```js 69 | function curryN(fn, n) { 70 | // 여기에 해답을 적으세요 71 | } 72 | 73 | module.exports = curryN 74 | ``` 75 | -------------------------------------------------------------------------------- /exercises/currying/problem.md: -------------------------------------------------------------------------------- 1 | This is an example implementation of `curry3`, which curries up to 3 arguments: 2 | 3 | ```js 4 | function curry3(fun){ 5 | return function(one){ 6 | return function(two){ 7 | return function (three){ 8 | return fun(one, two, three) 9 | } 10 | } 11 | } 12 | } 13 | ``` 14 | 15 | If we were to use this implementation with this sample function: 16 | 17 | ```js 18 | function abc(one, two, three) { 19 | return one/two/three 20 | } 21 | ``` 22 | 23 | It would work like so: 24 | 25 | ```js 26 | var curryC = curry3(abc) 27 | var curryB = curryC(6) 28 | var curryA = curryB(3) 29 | 30 | console.log(curryA(2)) // => 1 31 | ``` 32 | 33 | # Task 34 | 35 | In this challenge, we're going to implement a 'curry' function for an arbitrary number of arguments. 36 | 37 | `curryN` will take two parameters: 38 | 39 | * fn: The function we want to curry. 40 | * n: Optional number of arguments to curry. If not supplied, `curryN` should use the fn's arity as the value for `n`. 41 | 42 | ## Example 43 | 44 | ```js 45 | function add3(one, two, three) { 46 | return one + two + three 47 | } 48 | 49 | var curryC = curryN(add3) 50 | var curryB = curryC(1) 51 | var curryA = curryB(2) 52 | console.log(curryA(3)) // => 6 53 | console.log(curryA(10)) // => 13 54 | 55 | console.log(curryN(add3)(1)(2)(3)) // => 6 56 | ``` 57 | 58 | ## Conditions 59 | 60 | * Do not use any for/while loops or `Array.prototype.forEach`. 61 | 62 | ## Hint 63 | 64 | * You can detect the number of expected arguments to a function (it's arity) by checking a function's .length property. 65 | 66 | ## Boilerplate 67 | 68 | ```js 69 | function curryN(fn, n) { 70 | // SOLUTION GOES HERE 71 | } 72 | 73 | module.exports = curryN 74 | ``` 75 | -------------------------------------------------------------------------------- /exercises/currying/solution/solution.js: -------------------------------------------------------------------------------- 1 | function curryN(fn, n) { 2 | n = n || fn.length 3 | return function curriedN(arg) { 4 | if (n <= 1) return fn(arg) 5 | return curryN(fn.bind(this, arg), n - 1) 6 | } 7 | } 8 | 9 | module.exports = curryN 10 | -------------------------------------------------------------------------------- /exercises/currying/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | function curryN(fn, n) { 2 | n = n || fn.length 3 | return function curriedN(arg) { 4 | if (n <= 1) return fn(arg) 5 | return curryN(fn.bind(this, arg), n - 1) 6 | } 7 | } 8 | 9 | module.exports = curryN 10 | -------------------------------------------------------------------------------- /exercises/function_call/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var loremIpsum = require('lorem-ipsum') 4 | var runner = require('../runner') 5 | 6 | var words = loremIpsum({ count: 5, units:'words'}) 7 | .replace(/([^\w ])/g, '')// remove non-words and spaces 8 | .toLowerCase() // lowercase I guess 9 | .split(' ') // create array of words 10 | 11 | module.exports = runner.custom(function(slice, words) { 12 | var result = [] 13 | words.forEach(function(word) { 14 | result.push('word:', word); 15 | result.push('slice(word):', slice(word)) 16 | result.push('slice(word, 0, 1):', slice(word, 0, 1)) 17 | result.push('slice(word, 2):', slice(word, 2)) 18 | result.push('slice(word, -1):', slice(word, -1)) 19 | }) 20 | return result 21 | })(words) 22 | -------------------------------------------------------------------------------- /exercises/function_call/problem.fr.md: -------------------------------------------------------------------------------- 1 | ## Défi 2 | 3 | Écrivez une fonction qui vous permet d’utiliser `Array.prototype.slice` sans avoir à utiliser 4 | `.call` pour l’invoquer. 5 | 6 | En temps normal, vous devriez appeler `slice()` à l’aide de `call()` ou `apply()` : 7 | 8 | ```js 9 | var slice = Array.prototype.slice 10 | 11 | function() { 12 | var args = slice.call(arguments) // ça marche 13 | } 14 | ``` 15 | 16 | Nous, on veut que le code suivant marche : 17 | 18 | ```js 19 | var slice = yourFunction 20 | 21 | function() { 22 | var args = slice(arguments) // ça marche 23 | } 24 | ``` 25 | 26 | ## Exemple 27 | 28 | Votre fonction `slice()` devrait avoir le comportement suivant : 29 | 30 | ```js 31 | var nums = [1,2,3,4,5] 32 | 33 | // Votre fonction slice doit correspondre au comportement 34 | // de la fonction slice standard, à ceci près qu'elle reçoit 35 | // le tableau comme premier argument. 36 | 37 | slice(nums, 0, 2) // [1, 2] 38 | slice(nums, 1, 2) // [2] 39 | 40 | // Avec le slice standard, pour comparaison 41 | nums.slice(0, 2) // [1, 2] 42 | nums.slice(1, 2) // [2] 43 | ``` 44 | 45 | ## Conditions 46 | 47 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 48 | * N’utilisez pas le mot-clé `function` :D 49 | 50 | ## Conseils 51 | 52 | * La solution fait une seule ligne. Même pas longue. 53 | * Tout fonction JavaScript hérite de méthodes telles que `call()`, `apply()` et `bind()`, provenant de l’objet `Function.prototype`. 54 | * `Function#call()` fonctionne quelle que soit la fonction sur laquelle on l’appelle (par exemple dans `someFunction.call()`, `this` à l’exécution sera `someFunction`). 55 | * `Function#call()` est elle-même une fonction, et hérite donc de `Function.prototype` : 56 | 57 | ```js 58 | function myFunction() { 59 | console.log("J’ai appelé ma fonction") 60 | } 61 | 62 | Function.prototype.call.call(myFunction) // => "J’ai appelé ma fonction" 63 | ``` 64 | 65 | ## Base de travail 66 | 67 | ```js 68 | module.exports = // VOTRE SOLUTION ICI ! 69 | ``` 70 | -------------------------------------------------------------------------------- /exercises/function_call/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | `Array.prototype.slice`를 사용하는 함수를 `slice.call`이나 `slice.apply`를 사용하지 않고 작성하세요. 4 | 5 | 일반적으로는 `slice`에는 `call`이나 `apply`가 필요합니다. 6 | 7 | ```js 8 | var slice = Array.prototype.slice 9 | 10 | function() { 11 | var args = slice.call(arguments) // this works 12 | } 13 | ``` 14 | 15 | 이렇게 동작하게 하고 싶습니다. 16 | 17 | ```js 18 | var slice = yourFunction 19 | 20 | function() { 21 | var args = slice(arguments) // this works 22 | } 23 | ``` 24 | 25 | ## 예제 26 | 27 | `slice` 함수는 다음과 같이 사용할 수 있습니다. 28 | 29 | ```js 30 | var nums = [1,2,3,4,5] 31 | 32 | // slice 함수는 첫 번째 인자로 배열을 받는 것을 제외하고 33 | // slice의 일반적인 행동과 같아야 합니다. 34 | 35 | slice(nums, 0, 2) // [1, 2] 36 | slice(nums, 1, 2) // [2] 37 | 38 | // 비교를 위한 일반적인 slice의 사용법 39 | nums.slice(0, 2) // [1, 2] 40 | nums.slice(1, 2) // [2] 41 | ``` 42 | 43 | ## 조건 44 | 45 | * 어떤 for/while 반복문이나 Array#forEach도 사용하지 마세요. 46 | * `function` 키워드를 사용하지 마세요. :D 47 | 48 | ## 힌트 49 | 50 | * 한 줄이면 충분합니다. 51 | * 모든 JavaScript 함수는 call, apply, bind 같은 메소드를 `Function.prototype` 객체로부터 상속합니다. 52 | * Function#call은 호출될 때 `this`의 값을 실행합니다. `someFunction.call()` 안에서 `this`의 값은 `someFunction`이 됩니다. 53 | * Function.call 자체는 `Function.prototype`에서 상속되는 함수입니다. 54 | 55 | ```js 56 | function myFunction() { 57 | console.log('called my function') 58 | } 59 | 60 | Function.prototype.call.call(myFunction) // => "called my function" 61 | ``` 62 | 63 | ## 템플릿 64 | 65 | ```js 66 | module.exports = // 여기에 해답을 적으세요 67 | ``` 68 | -------------------------------------------------------------------------------- /exercises/function_call/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Write a function that allows you to use `Array.prototype.slice` without using `slice.call` or `slice.apply` to invoke it. 4 | 5 | Normally you have to use `slice` with `call` or `apply`: 6 | 7 | ```js 8 | var slice = Array.prototype.slice 9 | 10 | function() { 11 | var args = slice.call(arguments) // this works 12 | } 13 | ``` 14 | 15 | We want this to work: 16 | 17 | ```js 18 | var slice = yourFunction 19 | 20 | function() { 21 | var args = slice(arguments) // this works 22 | } 23 | ``` 24 | 25 | ## Example 26 | 27 | A function, `slice` that exhibits the following behaviour: 28 | 29 | ```js 30 | var nums = [1,2,3,4,5] 31 | 32 | // your slice function should match the regular 33 | // behaviour of slice, except it takes the array 34 | // as the first argument 35 | 36 | slice(nums, 0, 2) // [1, 2] 37 | slice(nums, 1, 2) // [2] 38 | 39 | // regular slice usage for comparison 40 | nums.slice(0, 2) // [1, 2] 41 | nums.slice(1, 2) // [2] 42 | ``` 43 | 44 | ## Conditions 45 | 46 | * Do not use any for/while loops or Array#forEach. 47 | * Do not use the `function` keyword :D 48 | 49 | ## Hints 50 | 51 | * This is absolutely a one liner. 52 | * Every JavaScript Function inherits methods such as call, apply and bind from the object `Function.prototype`. 53 | * Function#call executes the value of `this` when it is invoked. Inside `someFunction.call()`, the value of `this` will be `someFunction`. 54 | * Function.call itself is a function thus it inherits from `Function.prototype` 55 | 56 | ```js 57 | function myFunction() { 58 | console.log('called my function') 59 | } 60 | 61 | Function.prototype.call.call(myFunction) // => "called my function" 62 | ``` 63 | 64 | ## Boilerplate 65 | 66 | ```js 67 | module.exports = // your solution here! 68 | ``` 69 | -------------------------------------------------------------------------------- /exercises/function_call/solution/solution.js: -------------------------------------------------------------------------------- 1 | // Explained: 2 | // The value of `this` in Function.call is the function 3 | // that will be executed. 4 | // 5 | // Bind returns a new function with the value of `this` fixed 6 | // to whatever was passed as its first argument. 7 | // 8 | // Every function 'inherits' from Function.prototype, 9 | // thus every function, including call, apply and bind 10 | // have the methods call apply and bind. 11 | // 12 | // Function.prototype.call === Function.call 13 | module.exports = Function.call.bind(Array.prototype.slice) 14 | -------------------------------------------------------------------------------- /exercises/function_call/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | // Explication : 2 | // 3 | // La valeur de `this` dans `Function#call()` est la fonction 4 | // sur laquelle on appelle `call()`. 5 | // 6 | // `bind()` renvoie une nouvelle fonction dont la valeur de 7 | // `this` est définie à ce qu’on lui a passé comme premier argument. 8 | // 9 | // Toute fonction « hérite » `Function.prototype`, et donc toute fonction, 10 | // y compris `call()`, `apply()` et `bind()`, a les méthodes `apply()` 11 | // et `bind()`. 12 | // 13 | // Function.prototype.call === Function.call 14 | module.exports = Function.call.bind(Array.prototype.slice) 15 | -------------------------------------------------------------------------------- /exercises/function_spies/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var deepEqual = require('deep-eql') 4 | var random = require('../randomizer') 5 | var runner = require('../runner') 6 | var loremIpsum = require('lorem-ipsum') 7 | var util = require('util') 8 | 9 | var input = random.arrayOf(20, function() { return loremIpsum().split(' ') }) 10 | 11 | var exercise = module.exports = runner.custom(function(Spy, input) { 12 | var count = 0, slice = Array.prototype.slice 13 | var parent = { 14 | test: function() { 15 | if (!deepEqual(slice.call(arguments), input[count])) { 16 | exercise.emit('fail', exercise.__('not_all_args')) 17 | } 18 | if (this !== parent) { 19 | exercise.emit('fail', exercise.__('incorrect_this')) 20 | } 21 | return arguments 22 | } 23 | } 24 | var originalFn = parent.test.bind(parent) 25 | var spy = Spy(parent, 'test') 26 | 27 | var result = [] 28 | input.forEach(function(args, i) { 29 | result.push(util.format.apply(util, args)) 30 | count = i 31 | if (!deepEqual(originalFn.apply(parent, args), parent.test.apply(parent, args))) { 32 | exercise.emit('fail', exercise.__('incorrect_return')) 33 | } 34 | }) 35 | count++ 36 | 37 | if (!spy.count || spy.count !== count) { 38 | exercise.emit('fail', exercise.__('incorrect_count')) 39 | } else { 40 | result.push(exercise.__('call_times', spy.count)) 41 | } 42 | 43 | return result 44 | }).quiet(input) 45 | -------------------------------------------------------------------------------- /exercises/function_spies/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Ajoutez des capacités à une méthode précise sur un objet, tout en préservant son comportement d’origine : créez un espion qui garde la trace du nombre de fois que la fonction a été appelée. 4 | 5 | ## Exemple 6 | 7 | ```js 8 | var spy = Spy(console, 'error') 9 | 10 | console.error('appel de console.error') 11 | console.error('appel de console.error') 12 | console.error('appel de console.error') 13 | 14 | console.log(spy.count) // 3 15 | ``` 16 | 17 | ## Arguments 18 | 19 | * `target` : un objet contenant la méthode `method`. 20 | * `method` : une `String` indiquant le nom de la méthode de `target` à espionner. 21 | 22 | ## Conditions 23 | 24 | * N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` 25 | * Ne créez aucune fonction superflue 26 | 27 | ## Conseils 28 | 29 | Les fonctions ont un contexte (une valeur de `this`), une entrée (les arguments) et une sortie (la valeur de retour). Assurez vous que vous préservez tous ces aspects pour la fonction que vous espionnez. 30 | 31 | ## Base de travail 32 | 33 | ```js 34 | function Spy(target, method) { 35 | // VOTRE SOLUTION ICI 36 | } 37 | 38 | module.exports = Spy 39 | ``` 40 | -------------------------------------------------------------------------------- /exercises/function_spies/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 객체의 특정 메소드를 오버라이드해 이전 기능을 유지하면서 새로운 기능을 추가하세요. 4 | 5 | 얼마나 함수가 호출되었는지 감시하는 스파이를 만드세요. 6 | 7 | ## 예제 8 | 9 | ```js 10 | var spy = Spy(console, 'error') 11 | 12 | console.error('calling console.error') 13 | console.error('calling console.error') 14 | console.error('calling console.error') 15 | 16 | console.log(spy.count) // 3 17 | ``` 18 | 19 | ## 인자 20 | 21 | * target: `method` 메소드를 가지는 객체 22 | * method: `target`에 있는 감시할 메소드 이름(문자열) 23 | 24 | ## 조건 25 | 26 | * 어떤 for/while 반복문이나 Array#forEach도 사용하지 마세요. 27 | * 헬퍼 같은 불필요한 함수를 만들지 마세요. 28 | 29 | ## 힌트 30 | 31 | * 함수는 문맥, 입력, 출력을 가집니다. 감시하는 함수의 문맥, 입력, *출력*을 확실히 고려하세요. 32 | 33 | ## 템플릿 34 | 35 | ```js 36 | function Spy(target, method) { 37 | // 여기에 해답을 적으세요 38 | } 39 | 40 | module.exports = Spy 41 | ``` 42 | -------------------------------------------------------------------------------- /exercises/function_spies/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Override a specified method of an object with new functionality while still maintaining all of the old behaviour. 4 | 5 | Create a spy that keeps track of how many times a function is called. 6 | 7 | ## Example 8 | 9 | ```js 10 | var spy = Spy(console, 'error') 11 | 12 | console.error('calling console.error') 13 | console.error('calling console.error') 14 | console.error('calling console.error') 15 | 16 | console.log(spy.count) // 3 17 | ``` 18 | 19 | ## Arguments 20 | 21 | * target: an object containing the method `method` 22 | * method: a string with the name of the method on `target` to spy on. 23 | 24 | ## Conditions 25 | 26 | * Do not use any for/while loops or Array#forEach. 27 | * Do not create any unnecessary functions e.g. helpers. 28 | 29 | ## Hint 30 | 31 | * Functions have context, input and output. Make sure you consider the context, input to *and output from* the function you are spying on. 32 | 33 | ## Boilerplate 34 | 35 | ```js 36 | function Spy(target, method) { 37 | // SOLUTION GOES HERE 38 | } 39 | 40 | module.exports = Spy 41 | ``` 42 | -------------------------------------------------------------------------------- /exercises/function_spies/solution/solution.js: -------------------------------------------------------------------------------- 1 | function Spy(target, method) { 2 | var originalFunction = target[method] 3 | 4 | // use an object so we can pass by reference, not value 5 | // i.e. we can return result, but update count from this scope 6 | var result = { 7 | count: 0 8 | } 9 | 10 | // replace method with spy method 11 | target[method] = function() { 12 | result.count++ // track function was called 13 | return originalFunction.apply(this, arguments) // invoke original function 14 | } 15 | 16 | return result 17 | } 18 | 19 | module.exports = Spy 20 | -------------------------------------------------------------------------------- /exercises/function_spies/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | function Spy(target, method) { 2 | var originalFunction = target[method] 3 | 4 | // Utilisons un objet pour pouvoir le renvoyer par référence, 5 | // et non par copie de valeur. Ainsi nous pouvons renvoyer 6 | // `result`, mais mettre à jour `count` depuis cette portée. 7 | var result = { 8 | count: 0 9 | } 10 | 11 | // remplaçons la méthode par sa version « espion » 12 | target[method] = function() { 13 | // On comptabilise l’appel 14 | result.count++ 15 | // On invoque la version d’origine 16 | return originalFunction.apply(this, arguments) 17 | } 18 | 19 | return result 20 | } 21 | 22 | module.exports = Spy 23 | -------------------------------------------------------------------------------- /exercises/hello_world/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var runner = require('../runner') 4 | 5 | module.exports = runner(require('lorem-ipsum')()) 6 | -------------------------------------------------------------------------------- /exercises/hello_world/problem.fr.md: -------------------------------------------------------------------------------- 1 | ## Défi 2 | 3 | Écrivez une fonction qui prend une `String` en entrée et la renvoie en majuscules. 4 | 5 | ## Arguments 6 | 7 | * `input` : une `String` contenant un texte aléatoire (type lorem ipsum). 8 | 9 | ## Base de travail 10 | 11 | ```js 12 | function upperCaser(input) { 13 | // VOTRE SOLUTION ICI 14 | } 15 | 16 | module.exports = upperCaser 17 | ``` 18 | -------------------------------------------------------------------------------- /exercises/hello_world/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 입력을 받아 대문자로 바꿔 출력하는 함수를 작성하세요. 4 | 5 | ## 인자 6 | 7 | * input: 임의의 영단어 문자열(lorem ipsum). 8 | 9 | ## 템플릿 10 | 11 | ```js 12 | function upperCaser(input) { 13 | // 여기에 해답을 적으세요 14 | } 15 | 16 | module.exports = upperCaser 17 | ``` 18 | -------------------------------------------------------------------------------- /exercises/hello_world/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Write a function that takes an input string and returns it uppercased. 4 | 5 | ## Arguments 6 | 7 | * input: a String of random words (lorem ipsum). 8 | 9 | ## Boilerplate 10 | 11 | ```js 12 | function upperCaser(input) { 13 | // SOLUTION GOES HERE 14 | } 15 | 16 | module.exports = upperCaser 17 | ``` 18 | -------------------------------------------------------------------------------- /exercises/hello_world/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function(input) { 2 | return input.toUpperCase() 3 | } 4 | -------------------------------------------------------------------------------- /exercises/higher_order_functions/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var random = require('../randomizer') 4 | var runner = require('../runner') 5 | 6 | var counter 7 | 8 | var exercise = module.exports = runner.init(function() { 9 | console.log("------------------------") 10 | counter = 0 11 | }).quiet(function count() { 12 | console.log(exercise.__('call_log', ++counter)) 13 | }, random.int(3, 10)) 14 | 15 | exercise.ignoreReturnValue = true 16 | -------------------------------------------------------------------------------- /exercises/higher_order_functions/problem.fr.md: -------------------------------------------------------------------------------- 1 | Une fonction d’ordre supérieur est une fonction qui fait au moins l’une des deux choses suivantes : 2 | 3 | * Prendre une ou plusieurs fonctions comme arguments 4 | * Renvoyer une fonction 5 | 6 | Toutes les autres fonctions sont des fonctions de premier ordre. 7 | 8 | Contrairement à de nombreux langages impératifs, JavaScript vous permet d’utiliser des fonctions d’ordre supérieur parce qu’il traite ses fonctions comme des valeurs, au même titre que n’importe quelle autre valeur en JavaScript, telles que les `String`s ou `Number`s. Les valeurs de type `Function` peuvent être stockées dans des variables, dans des propriétés d’objets, ou être passées comme arguments à d’autres fonctions. 9 | 10 | Les valeurs de type `Function` sont en réalité des objets (qui héritent de `Function.prototype`), de sorte que vous pouvez même leur ajouter des propriétés et y stocker des valeurs, comme pour n’importe quel autre objet. 11 | 12 | La différence principale entre les `Function`s et les autres types de valeurs en JavaScript est leur syntaxe d’appel : si une référence à une fonction est suivie par des parenthèses (avec des valeurs optionnelles séparées par des virgules) : `someFunction(arg1, arg2, etc)`, alors le corps de la fonction est exécuté avec les arguments éventuellement passés. 13 | 14 | Dans cet exercice, nous allons prouver que les fonctions peuvent être passées comme des valeurs, en passant une fonction comme argument à celle que vous allez écrire. 15 | 16 | ## Défi 17 | 18 | Écrivez une fonction qui en prend une autre comme premier argument, ainsi qu’un nombre `num` comme second argument, et exécute la fonction passée `num` fois. 19 | 20 | Utilisez le code de base fourni ci-dessous pour démarrer. La majorité des exercices de cet atelier vous fourniront une base de travail. 21 | 22 | ## Arguments 23 | 24 | * `operation` : Une `Function`, qui ne prend pas d’arguments et ne renvoie rien d’utile. 25 | * `num` : le nombre de fois qu’il vous faudra appeler `operation` 26 | 27 | ## Ressources 28 | 29 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions 30 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/prototype 31 | 32 | ## Conseils 33 | 34 | * La solution est vraiment simple. 35 | * Vous avez le droit d’utiliser une boucle ; points bonus si vous préférez la récursivité. 36 | * Vous verrez sans doute un affichage à l’exécution. Il viendra de la fonction qui vous est passée. 37 | * Vous n’avez pas besoin de faire de `console.log(…)` vous-même. 38 | 39 | ## Base de travail 40 | 41 | ```js 42 | function repeat(operation, num) { 43 | // VOTRE SOLUTION ICI 44 | } 45 | 46 | // Ne retirez pas la ligne ci-dessous 47 | module.exports = repeat 48 | ``` 49 | -------------------------------------------------------------------------------- /exercises/higher_order_functions/problem.ko.md: -------------------------------------------------------------------------------- 1 | 고차(higher-order) 함수는 다음 중 하나에 해당하는 함수입니다. 2 | 3 | * 입력으로 하나 이상의 함수를 받음 4 | * 함수를 반환 5 | 6 | 여기 해당하지 않는 함수는 전부 1차(first order) 함수입니다. [1] 7 | 8 | 다른 많은 언어의 구현과는 달리, JavaScript는 "first-class functions"을 가지고 있기 때문에 높은 순위 함수를 이용할 수 있습니다. 이 말은 함수를 JavaScript 안의 다른 값들처럼 사용할 수 있다는 이야기입니다. 문자열이나 숫자처럼, 함수 값은 변수나 객체의 프로퍼티나 다른 함수에 인자로 넘길 수 있습니다. 함수 값은 사실은 객체(`Function.prototype`으로부터 상속됨)입니다. 그래서 거기에 다른 객체와 마찬가지로 프로퍼티를 추가하고 값을 저장할 수 있습니다. 9 | 10 | JavaScript의 다른 형과 함수의 중요한 차이점은 호출 구문입니다. `someFunctionValue(arg1, arg2, etc)`처럼 함수의 참조 뒤에 괄호와 함께 쉼표(,)로 구별되는 값들이 온다면, 함수의 몸통은 (있다면) 주어진 인자로 실행 될 것입니다. 11 | 12 | 이 예제에서는 함수를 인자로 넘겨봄으로써 함수는 값처럼 넘길 수 있다는 것을 보여드리겠습니다. 13 | 14 | # 해야할 일 15 | 16 | 첫 번째 인자로 함수, 두 번째 인자로 숫자 `num`을 받아 넘겨진 함수를 `num`번 실행하는 함수를 구현하세요. 17 | 18 | 시작할 때 아래의 템플릿을 사용하세요. 앞으로 대부분의 연습 문제는 템플릿이 제공됩니다. 19 | 20 | ## 인자 21 | 22 | * operation: 인자를 받지 않고 유용한 값을 반환하는 함수 23 | * num: `operation`을 호출할 횟수 24 | 25 | ## 참고 26 | 27 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions_and_function_scope 28 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype 29 | 30 | ## 힌트 31 | 32 | * 너무 깊게 생각하지 마세요. 생각보다 간단합니다. 33 | * 구현에 재귀를 사용하고 있다면 반복문을 사용하셔도 됩니다. (보너스 포인트) 34 | * 넘겨진 함수에서 하는 출력을 볼 수도 있습니다. 35 | * 아무것도 console.log할 필요 없습니다. 36 | 37 | ## 템플릿 38 | 39 | ```js 40 | function repeat(operation, num) { 41 | // 여기에 해답을 적으세요 42 | } 43 | 44 | // 밑의 줄을 지우지 마세요 45 | module.exports = repeat 46 | ``` 47 | -------------------------------------------------------------------------------- /exercises/higher_order_functions/problem.md: -------------------------------------------------------------------------------- 1 | A higher-order function is a function that does at least one of the following: 2 | 3 | * Take one or more functions as an input 4 | * Output a function 5 | 6 | All other functions are first order functions. [1] 7 | 8 | Unlike many other languages with imperative features, JavaScript allows you to utilize higher-order functions because it has "first-class functions". This means functions can be treated just like any other value in JavaScript: just like Strings or Numbers, Function values can be stored as variables, properties on objects or passed to other functions as arguments. Function values are actually Objects (inheriting from `Function.prototype`) so you can even add properties and store values on them, just like any regular Object. 9 | 10 | The key difference between Functions and other value types in JavaScript is the call syntax: if a reference to a function is followed by parenthesis and some optional comma-separated values: `someFunctionValue(arg1, arg2, etc)`, then the function body will be executed with the supplied arguments (if any). 11 | 12 | In this exercise we're going to demonstrate that functions can be passed as values by passing you a function as an argument. 13 | 14 | # Task 15 | 16 | Implement a function that takes a function as its first argument, a number `num` as its second argument, then executes the passed in function `num` times. 17 | 18 | Use the boilerplate code given to you below to get started. Most/all future exercises will provide boilerplate. 19 | 20 | ## Arguments 21 | 22 | * operation: A Function, takes no arguments, returns no useful value. 23 | * num: the number of times to call `operation` 24 | 25 | ## Resources 26 | 27 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions_and_function_scope 28 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype 29 | 30 | ## Hints 31 | 32 | * Don't overthink it, the code should be rather simple. 33 | * It's ok to use a loop in your implementation, bonus points if you use recursion instead. 34 | * You may notice some output. That is coming from the function we passed you. 35 | * You do not need to console.log anything. 36 | 37 | ## Boilerplate 38 | 39 | ```js 40 | function repeat(operation, num) { 41 | // SOLUTION GOES HERE 42 | } 43 | 44 | // Do not remove the line below 45 | module.exports = repeat 46 | ``` 47 | -------------------------------------------------------------------------------- /exercises/higher_order_functions/solution/solution.js: -------------------------------------------------------------------------------- 1 | function repeat(operation, num) { 2 | if (num <= 0) return 3 | operation() 4 | return repeat(operation, --num) 5 | } 6 | 7 | module.exports = repeat 8 | -------------------------------------------------------------------------------- /exercises/implement_map_with_reduce/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var runner = require('../runner') 4 | 5 | function randomInt(min, max) { 6 | return Math.floor((Math.random() * (max - min)) + min) 7 | } 8 | 9 | var numbers = Array.apply(null, {length: Math.random() * 20 + 1}).map(function() { 10 | return randomInt(0, 9) 11 | }) 12 | 13 | module.exports = runner.custom(function(map, numbers) { 14 | var result = [] 15 | result.push('non-array entries', map(numbers, function(item) { return item * 3 })) 16 | result.push('array entries', map(numbers, function(item) { return [item * 3] })) 17 | return result 18 | })(numbers) 19 | -------------------------------------------------------------------------------- /exercises/implement_map_with_reduce/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | Utilisez `Array#reduce()` pour implémenter une version simple de `Array#map()`. 4 | 5 | ## Résultat attendu 6 | 7 | Une fonction `map()` applique une fonction quelconque à chaque élément d’un tableau, et renvoie le tableau des résultats obtenus. 8 | 9 | ```js 10 | var nums = [1,2,3,4,5] 11 | 12 | // `map` est la fonction que vous exportez 13 | var output = map(nums, function double(item) { 14 | return item * 2 15 | }) 16 | 17 | console.log(output) // => [2,4,6,8,10] 18 | ``` 19 | 20 | ## Arguments 21 | 22 | * `input` : un tableau de données quelconques, de types variés. 23 | * `operation` : une fonction quelconque, à appliquer aux éléments de `input`. 24 | 25 | ## Ressources 26 | 27 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 28 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce 29 | 30 | ## Base de travail 31 | 32 | ```js 33 | 34 | function arrayMap(arr, fn) { 35 | // VOTRE SOLUTION ICI 36 | } 37 | 38 | module.exports = arrayMap; 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /exercises/implement_map_with_reduce/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | Array#reduce를 사용해 단순한 Array#map을 구현하세요. 4 | 5 | ## 예상 출력 6 | 7 | `map` 함수는 배열의 각 요소에 함수를 적용한 결과를 새로운 배열에 수집합니다. 8 | 9 | ```js 10 | 11 | var nums = [1,2,3,4,5] 12 | 13 | // `map`은 당신이 export한 함수 14 | var output = map(nums, function double(item) { 15 | return item * 2 16 | }) 17 | 18 | console.log(output) // => [2,4,6,8,10] 19 | 20 | ``` 21 | 22 | ## 인자 23 | 24 | * input: 어떤 형도 넣을 수 있는 임의의 배열 25 | * operation: `input` 안의 요소에 적용할 수 있는 임의의 함수 26 | 27 | ## 참고 28 | 29 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 30 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce 31 | 32 | ## 템플릿 33 | 34 | ```js 35 | 36 | function arrayMap(arr, fn) { 37 | // 여기에 해답을 적으세요 38 | } 39 | 40 | module.exports = arrayMap; 41 | 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /exercises/implement_map_with_reduce/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Use Array#reduce to implement a simple version of Array#map. 4 | 5 | ## Expected Output 6 | 7 | A function `map` that applies a function to each item in an array and collects the results in a new Array. 8 | 9 | ```js 10 | 11 | var nums = [1,2,3,4,5] 12 | 13 | // `map` is your exported function 14 | var output = map(nums, function double(item) { 15 | return item * 2 16 | }) 17 | 18 | console.log(output) // => [2,4,6,8,10] 19 | 20 | ``` 21 | 22 | ## Arguments 23 | 24 | * input: an arbitrary Array of any type. 25 | * operation: an arbitrary Function which can be applied to items in `input`. 26 | 27 | ## Hints 28 | 29 | * No need to implement the optional `thisArg` argument of `Array.prototype.map`, bonus points if you do! 30 | 31 | ## Resources 32 | 33 | * https://en.wikipedia.org/wiki/Reduce_(higher-order_function) 34 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce 35 | 36 | ## Boilerplate 37 | 38 | ```js 39 | 40 | function arrayMap(arr, fn) { 41 | //SOLUTION GOES HERE 42 | } 43 | 44 | module.exports = arrayMap; 45 | 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /exercises/implement_map_with_reduce/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function arrayMap(arr, fn, thisArg) { 2 | return arr.reduce(function(acc, item, index, arr) { 3 | acc.push(fn.call(thisArg, item, index, arr)) 4 | return acc 5 | }, []) 6 | } 7 | -------------------------------------------------------------------------------- /exercises/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Hello World", 3 | "Higher Order Functions", 4 | "Basic: Map", 5 | "Basic: Filter", 6 | "Basic: Every Some", 7 | "Basic: Reduce", 8 | "Basic: Recursion", 9 | "Basic: Call", 10 | "Partial Application without Bind", 11 | "Partial Application with Bind", 12 | "Implement Map with Reduce", 13 | "Function Spies", 14 | "Blocking Event Loop", 15 | "Trampoline", 16 | "Async Loops", 17 | "Recursion", 18 | "Currying", 19 | "Function Call" 20 | ] 21 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var path = require('path') 4 | var random = require('../randomizer') 5 | var runner = require('../runner') 6 | 7 | var input = random.arrayOfLorems(1, 20) 8 | 9 | module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), input) 10 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/problem.fr.md: -------------------------------------------------------------------------------- 1 | # Défi 2 | 3 | **Utilisez `Function#bind()`** pour implémenter une fonction de log qui vous permet de préfixer vos messages. 4 | 5 | Votre implémentation doit accepter une `String` de préfixe, et renvoyer une fonction qui affichera les contenus qu’on lui passe sur la console, préfixée par cette `String`. 6 | 7 | Assurez-vous que **tous** les arguments passés à la fonction de log (celle renvoyée par la fonction que vous allez écrire) sont bien affichés. 8 | 9 | **Affichez le résultat directement sur la console.** 10 | 11 | ## Arguments 12 | 13 | * `namespace` : une `String` qui préfixe les messages passés à la fonction qui sera retournée. 14 | 15 | ## Exemple 16 | 17 | ```js 18 | var info = logger('INFO :') 19 | info('ceci est un message d’information') 20 | // INFO : ceci est un message d’information 21 | 22 | var warn = logger('WARN :') 23 | warn('ceci est un avertissement', 'avec du rab') 24 | // WARN : ceci est un avertissement avec du rab 25 | ``` 26 | 27 | ## Conditions 28 | 29 | * Utilisez `Function#bind()` 30 | 31 | ## Base de travail 32 | 33 | ```js 34 | module.exports = function logger(namespace) { 35 | // VOTRE SOLUTION ICI 36 | } 37 | ``` 38 | 39 | ## Resources 40 | 41 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/bind 42 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | **Function#bind를 사용해** 메시지에 네임스페이스를 사용할 수 있는 로그 함수를 구현하세요. 4 | 5 | 구현은 네임스페이스 문자열을 받아야 하고 콘솔에 네임스페이스가 앞에 붙은 메시지를 출력하는 함수를 반환해야 합니다. 6 | 7 | 반환된 로그 함수에 넘겨진 *모든* 인자는 출력되어야 합니다. 8 | 9 | ** 결과를 콘솔에 직접 출력하세요 ** 10 | 11 | ## 인자 12 | 13 | * 네임스페이스: 반환하는 함수에 넘겨질 각 메시지의 앞에 붙을 문자열 14 | 15 | ## 예제 16 | 17 | ```js 18 | 19 | var info = logger('INFO:') 20 | info('this is an info message') 21 | // INFO: this is an info message 22 | 23 | var warn = logger('WARN:') 24 | warn('this is a warning message', 'with more info') 25 | // WARN: this is a warning message with more info 26 | 27 | ``` 28 | 29 | ## 조건 30 | 31 | * Function#bind를 사용할 것 32 | 33 | ## 템플릿 34 | 35 | ```js 36 | 37 | module.exports = function(namespace) { 38 | // 여기에 해답을 적으세요 39 | } 40 | 41 | ``` 42 | 43 | ## 참고 44 | 45 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 46 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | **Use Function#bind** to implement a logging function that allows you to namespace messages. 4 | 5 | Your implementation should take a namespace string, and return a function that prints messages to the console with the namespace prepended. 6 | 7 | Make sure *all* arguments passed to the returned logging function are printed. 8 | 9 | ** Print the output to the console directly ** 10 | 11 | ## Arguments 12 | 13 | * namespace: a String to prepend to each message passed to the returned function. 14 | 15 | ## Example 16 | 17 | ```js 18 | 19 | var info = logger('INFO:') 20 | info('this is an info message') 21 | // INFO: this is an info message 22 | 23 | var warn = logger('WARN:') 24 | warn('this is a warning message', 'with more info') 25 | // WARN: this is a warning message with more info 26 | 27 | ``` 28 | 29 | ## Conditions 30 | 31 | * Use Function#bind 32 | 33 | ## Boilerplate 34 | 35 | ```js 36 | 37 | module.exports = function(namespace) { 38 | // SOLUTION GOES HERE 39 | } 40 | 41 | ``` 42 | 43 | ## Resources 44 | 45 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 46 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/solution/solution.js: -------------------------------------------------------------------------------- 1 | module.exports = function(namespace) { 2 | return console.log.bind(console, namespace) 3 | } 4 | -------------------------------------------------------------------------------- /exercises/partial_application_with_bind/wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | var fx = require(path.resolve(process.cwd(), process.argv[2])) 7 | var input = JSON.parse(fs.readFileSync(process.argv[3], { encoding: 'utf-8' }))[0] 8 | 9 | var info = fx('INFO:') 10 | var warn = fx('WARN:') 11 | input.forEach(function(message, i) { 12 | if (i % 2 === 0) info.apply(null, message.split(' ')) 13 | else warn.apply(null, message.split(' ')) 14 | }) 15 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var path = require('path') 4 | var random = require('../randomizer') 5 | var runner = require('../runner') 6 | 7 | var input = random.arrayOfLorems(1, 20) 8 | 9 | module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), input) 10 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/problem.fr.md: -------------------------------------------------------------------------------- 1 | L’application partielle vous permet de créer de nouvelles fonctions à partir de fonctions existantes, en pré-remplissant tout ou partie de leurs arguments. Une fois les arguments pré-remplis définis, vous obtenez une nouvelle fonction qui n’attend plus que les éventuels arguments restants pour exécuter la fonction d’origine. 2 | 3 | Plus formellement : l’application partielle désigne le pré-remplissage d’arguments d’une fonction pour produire une fonction de moindre arité. 4 | 5 | À titre d’exemple, imaginons que nous avons une fonction `add()` qui accepte deux arguments et renvoie leur somme : 6 | 7 | ```js 8 | function add(x, y) { 9 | return x + y 10 | } 11 | 12 | add(10, 20) // => 30 13 | ``` 14 | 15 | À présent, imaginons que nous disposons d’une fonction `partiallyApply()`. Celle-ci reçoit une fonction et quelques arguments qu’elle « applique partiellement » (qu’elle pré-remplit, en somme). 16 | 17 | Dans le code qui suit, nous pré-remplissons le premier argument de notre fonction `add()`, à savoir `x` : 18 | 19 | ```js 20 | var addTen = partiallyApply(add, 10) // pré-remplit `x` à 10 21 | ``` 22 | 23 | `addTen()` est une nouvelle fonction, qui n’a plus besoin que du paramètre `y` de `add()`. `add()` n’a pas encore été appelée ! 24 | 25 | Une fois que nous passons l’argument pour `y`, la fonction `add()` originale peut être appelée : 26 | 27 | ```js 28 | addTen(20) // => 30 29 | addTen(100) // => 110 30 | addTen(0) // => 10 31 | // etc. 32 | ``` 33 | 34 | Tous les exemples ci-dessus reviennent à appeler `add(10, y)`, ou `y` serait fourni dans l’appel à la bien-nommée `addTen()`. 35 | 36 | # Défi 37 | 38 | Utilisez l’application partielle pour créer une fonction qui pré-remplit un premier argument pour `console.log()`. En somme, implémentez une fonction de log qui préfixe son message. 39 | 40 | Votre implémentation doit accepter une `String` de préfixe, et renvoyer une fonction qui affichera les contenus qu’on lui passe sur la console, préfixée par cette `String`. 41 | 42 | Vous aurez besoin de `Function#apply()` pour implémenter l’application partielle. 43 | 44 | Assurez-vous que **tous** les arguments passés à la fonction de log (celle renvoyée par la fonction que vous allez écrire) sont bien affichés. 45 | 46 | **Affichez le résultat directement sur la console.** 47 | 48 | ## Arguments 49 | 50 | * `namespace` : une `String` qui préfixe les messages passés à la fonction qui sera retournée. 51 | 52 | ## Exemple 53 | 54 | ```js 55 | var info = logger('INFO :') 56 | info('ceci est un message d’information') 57 | // INFO : ceci est un message d’information 58 | 59 | var warn = logger('WARN :') 60 | warn('ceci est un avertissement', 'avec du rab') 61 | // WARN : ceci est un avertissement avec du rab 62 | ``` 63 | 64 | ## Conditions 65 | 66 | * N’utilisez pas `Function#bind()` 67 | * Utilisez `Function#apply()` 68 | 69 | ## Base de travail 70 | 71 | ```js 72 | var slice = Array.prototype.slice 73 | 74 | function logger(namespace) { 75 | // VOTRE SOLUTION ICI 76 | } 77 | 78 | module.exports = logger 79 | ``` 80 | 81 | ## Ressources 82 | 83 | * https://en.wikipedia.org/wiki/Partial_application 84 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/apply 85 | 86 | ## Conseils 87 | 88 | Gardez à l’esprit que `console.log()` accepte un nombre quelconque d’arguments et les affiche, séparés par des espaces : 89 | 90 | ```js 91 | console.log('bonjour', 'monde') // => 'bonjour monde' 92 | console.log(1, 2, 3) // => 1 2 3 93 | ``` 94 | 95 | Nous voulons simplement pré-remplir le premier argument de `console.log()`. 96 | 97 | `Function#apply()` nous permet d’exécuter une fonction, en fournissant une valeur spécifique pour `this` (ce qu’on peut royalement ignorer sur ce coup), puis un **tableau d’arguments à passer à la fonction** : 98 | 99 | ```js 100 | add(10, 20) // => 30 101 | add.apply(null, [10, 20]) // => 30 102 | ``` 103 | 104 | Faites bien la différence entre `apply()` et `call()` : 105 | 106 | ```js 107 | add.apply(null, [10, 20]) // => 30 108 | add.call(null, 10, 20) // => 30 109 | ``` 110 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/problem.ko.md: -------------------------------------------------------------------------------- 1 | 부분 애플리케이션을 사용해 이미 있는 함수로부터 인자의 개수를 고친 새로운 함수를 만들 수 있습니다. 부분적으로 적용할 인자를 설정한 후, 나머지 인자를 받아 원래 함수를 실행할 수도 있는 새로운 함수를 얻을 수 있습니다. 2 | 3 | 더 딱딱하게 말하면 부분 애플리케이션은 더 좁은 목적으로 사용하기 위한 함수를 만들기 위해 함수에 넘길 인자의 수를 고칠 때 참조됩니다. 4 | 5 | 예제로, 2개의 인자를 받아 더하는 `add`라는 함수가 있다고 합시다. 6 | 7 | ```js 8 | 9 | function add(x, y) { 10 | return x + y 11 | } 12 | 13 | add(10, 20) // => 30 14 | 15 | ``` 16 | 17 | 이제, `partiallyApply`라는 함수가 있다고 합시다. `partiallyApply`는 함수와 '부분적으로 사용할' 인자를 받습니다. 18 | 19 | 첫 번째 인자를 `add` 함수의 `x`로 '부분적으로 사용했습니다'. 20 | 21 | ```js 22 | 23 | var addTen = partiallyApply(add, 10) // `x`를 10으로 고정 24 | 25 | ``` 26 | 27 | 새로운 함수 `addTen`은 `add` 함수의 `y` 파라미터를 받습니다. `add`는 아직 호출되지 않았습니다! 28 | 29 | `y`를 위한 인자를 넘기면, 원래 `add` 함수를 실행할 수 있습니다. 30 | 31 | ```js 32 | 33 | addTen(20) // => 30 34 | addTen(100) // => 110 35 | addTen(0) // => 10 36 | 37 | // 등등 38 | 39 | ``` 40 | 41 | 위에 있는 예제는 모두 `add(10, y)`를 호출하는 것과 같고 `y`는 `addTen`이라고 적절히 이름 지은 호출에 공급됩니다. 42 | 43 | # 해야할 일 44 | 45 | 부분 애플리케이션을 사용해 `console.log`에 넘길 첫 번째 인자를 고정하는 함수를 만드세요. 다시 말해 출력 앞에 네임스페이스를 붙일 수 있는 로그 함수를 구현하세요. 46 | 47 | 48 | 구현은 네임스페이스 문자열을 받아야 하고 콘솔에 네임스페이스가 앞에 붙은 메시지를 출력하는 함수를 반환해야 합니다. 49 | 50 | 부분 애플리케이션을 구현하기 위해 `Function#apply`를 사용해야 합니다. 51 | 52 | 반환되는 로그 함수에서 *모든* 인자가 넘겨지고 출력되어야 합니다. 53 | 54 | ** 결과를 콘솔에 직접 출력하세요 ** 55 | 56 | ## 인자 57 | 58 | * 네임스페이스: 반환하는 함수에 넘겨질 각 메시지의 앞에 붙을 문자열 59 | 60 | ## 예제 61 | 62 | ```js 63 | 64 | var info = logger('INFO:') 65 | info('this is an info message') 66 | // INFO: this is an info message 67 | 68 | var warn = logger('WARN:') 69 | warn('this is a warning message', 'with more info') 70 | // WARN: this is a warning message with more info 71 | ``` 72 | 73 | ## 조건 74 | 75 | * Function#bind를 사용하지 말 것 76 | * Function#apply를 사용할 것 77 | 78 | ## 템플릿 79 | 80 | ```js 81 | 82 | var slice = Array.prototype.slice 83 | 84 | function logger(namespace) { 85 | // 여기에 해답을 적으세요 86 | } 87 | 88 | module.exports = logger 89 | 90 | ``` 91 | 92 | ## 참고 93 | 94 | * https://en.wikipedia.org/wiki/Partial_application 95 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply 96 | 97 | ## 힌트 98 | 99 | `console.log`는 개수와 상관없이 인자를 받을 수 있으며 공백으로 구분해 출력합니다. 100 | 101 | ```js 102 | 103 | console.log('hello', 'world') // => 'hello world' 104 | console.log(1,2,3) // => 1 2 3 105 | 106 | ``` 107 | 108 | `console.log`에 넘길 첫 번째 인자를 '부분 적용'하고 싶을 뿐입니다. 109 | 110 | `Function.prototype.apply`를 사용하면 'this의 값'을 새로 제공해 함수를 실행하고 *인자의 배열을 함수에 적용*할 수 있습니다. 111 | 112 | 113 | ```js 114 | 115 | add(10, 20) // => 30 116 | add.apply(null, [10, 20]) // => 30 117 | 118 | ``` 119 | 120 | `apply`를 `Function.prototype.call`과 비교하면 이렇습니다. 121 | 122 | ```js 123 | 124 | add.apply(null, [10, 20]) // => 30 125 | add.call(null, 10, 20) // => 30 126 | 127 | ``` 128 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/problem.md: -------------------------------------------------------------------------------- 1 | Partial application allows you to create new functions from existing functions, while fixing some number of arguments. After setting the arguments to be partially applied, you get a new function ready to take the rest of the arguments and perhaps execute the original function. 2 | 3 | More formally: Partial application refers to the process of fixing a number of arguments to a function, producing another function of smaller arity. 4 | 5 | As an example, say we have a function `add`, that takes 2 arguments and adds them together: 6 | 7 | ```js 8 | 9 | function add(x, y) { 10 | return x + y 11 | } 12 | 13 | add(10, 20) // => 30 14 | 15 | ``` 16 | 17 | Now, pretend we have a function `partiallyApply`. `partiallyApply` takes a function, and some arguments to 'partially apply'. 18 | 19 | Here we 'partially apply' the first parameter, `x`, of our `add` function: 20 | 21 | ```js 22 | 23 | var addTen = partiallyApply(add, 10) // fix `x` to 10 24 | 25 | ``` 26 | 27 | `addTen` is a new function that takes the `y` parameter of `add`. `add` has not yet been called! 28 | 29 | Once we pass the argument for `y`, we can execute the original `add` function: 30 | 31 | ```js 32 | 33 | addTen(20) // => 30 34 | addTen(100) // => 110 35 | addTen(0) // => 10 36 | 37 | // etc 38 | 39 | ``` 40 | 41 | All of the above examples are same as calling `add(10, y)`, where `y` was supplied in the call to the appropriately named `addTen`. 42 | 43 | # Task 44 | 45 | Use partial application to create a function that fixes the first argument to `console.log`. i.e. Implement a logging function that prepends a namespace string to its output. 46 | 47 | Your implementation should take a namespace String and return a function that prints messages to the console with the namespace prepended. 48 | 49 | You should use `Function#apply` to implement the partial application. 50 | 51 | Make sure *all* arguments passed to the returned logging function are printed. 52 | 53 | ** Print the output to the console directly ** 54 | 55 | ## Arguments 56 | 57 | * namespace: a String to prepend to each message passed to the returned function. 58 | 59 | ## Example 60 | 61 | ```js 62 | 63 | var info = logger('INFO:') 64 | info('this is an info message') 65 | // INFO: this is an info message 66 | 67 | var warn = logger('WARN:') 68 | warn('this is a warning message', 'with more info') 69 | // WARN: this is a warning message with more info 70 | ``` 71 | 72 | ## Conditions 73 | 74 | * Do not use Function#bind 75 | * Use Function#apply 76 | 77 | ## Boilerplate 78 | 79 | ```js 80 | 81 | var slice = Array.prototype.slice 82 | 83 | function logger(namespace) { 84 | // SOLUTION GOES HERE 85 | } 86 | 87 | module.exports = logger 88 | 89 | ``` 90 | 91 | ## Resources 92 | 93 | * https://en.wikipedia.org/wiki/Partial_application 94 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply 95 | 96 | ## Hints 97 | 98 | Remember `console.log` takes any number of arguments and prints them, separated by spaces: 99 | 100 | ```js 101 | 102 | console.log('hello', 'world') // => 'hello world' 103 | console.log(1,2,3) // => 1 2 3 104 | 105 | ``` 106 | 107 | We simply want to 'partially apply' the first argument to `console.log`. 108 | 109 | `Function.prototype.apply` allows us to execute a function, supply a new 'value for this' (we can ignore in this case), and then *an array of arguments to apply to the function*: 110 | 111 | 112 | ```js 113 | 114 | add(10, 20) // => 30 115 | add.apply(null, [10, 20]) // => 30 116 | 117 | ``` 118 | 119 | Also contrast `apply` with `Function.prototype.call`: 120 | 121 | ```js 122 | 123 | add.apply(null, [10, 20]) // => 30 124 | add.call(null, 10, 20) // => 30 125 | 126 | ``` 127 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/solution/solution.js: -------------------------------------------------------------------------------- 1 | var slice = Array.prototype.slice 2 | 3 | function logger(namespace) { 4 | return function() { 5 | console.log.apply(console, [namespace].concat(slice.call(arguments))) 6 | } 7 | } 8 | 9 | module.exports = logger 10 | -------------------------------------------------------------------------------- /exercises/partial_application_without_bind/wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | var fx = require(path.resolve(process.cwd(), process.argv[2])) 7 | var input = JSON.parse(fs.readFileSync(process.argv[3], { encoding: 'utf-8' }))[0] 8 | 9 | console.log.bind = function() { 10 | throw new Error('Try implementing this without bind!') 11 | } 12 | 13 | var info = fx('INFO:') 14 | var warn = fx('WARN:') 15 | input.forEach(function(message, i) { 16 | if (i % 2 === 0) info.apply(null, message.split(' ')) 17 | else warn.apply(null, message.split(' ')) 18 | }) 19 | -------------------------------------------------------------------------------- /exercises/prototypical_inheritance_by_hand/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var input = require('../../input') 4 | 5 | module.exports = input().wrap(function(input, submission) { 6 | var New = submission.New 7 | var Lookup = submission.Lookup 8 | var Create = submission.Create 9 | 10 | var animal = { 11 | hungry: true 12 | } 13 | 14 | var cat = { 15 | color: 'black' 16 | } 17 | 18 | var kitten = { 19 | size: 'small' 20 | } 21 | 22 | var otherKitten = { 23 | size: 'small', 24 | color: 'grey' 25 | } 26 | 27 | cat.__PROTO__ = animal 28 | kitten.__PROTO__ = cat 29 | otherKitten.__PROTO__ = cat 30 | 31 | console.log() 32 | console.log('Testing Lookup:') 33 | console.log() 34 | console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color')) 35 | console.log("Lookup(otherKitten, 'color')", Lookup(otherKitten, 'color')) 36 | console.log("Lookup(kitten, 'wings')", Lookup(kitten, 'wings')) 37 | console.log("Lookup(cat, 'hungry')", Lookup(cat, 'hungry')) 38 | console.log("Lookup(kitten, 'hungry')", Lookup(kitten, 'hungry')) 39 | console.log() 40 | console.log('Changing prototype property...') 41 | 42 | console.log("cat.color = 'blue'") 43 | cat.color = 'blue' 44 | 45 | console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color')) 46 | console.log("Lookup(otherKitten, 'color')", Lookup(otherKitten, 'color')) 47 | 48 | console.log() 49 | console.log('Testing Create:') 50 | console.log() 51 | 52 | console.log("var cat = Create({color: 'black'})") 53 | console.log("var otherCat = Create(cat)") 54 | var cat = Create({color: 'black'}) 55 | var otherCat = Create(cat) 56 | 57 | console.log("Lookup(cat, 'color')", Lookup(cat, 'color')) 58 | console.log("Lookup(otherCat, 'color')", Lookup(otherCat, 'color')) 59 | console.log("otherCat.__PROTO__ === cat", otherCat.__PROTO__ === cat) 60 | 61 | console.log() 62 | console.log('Testing New:') 63 | console.log() 64 | 65 | var rootProto = Object.create(null) 66 | 67 | var Cat = function Cat(color) { 68 | this.color = color 69 | } 70 | 71 | Cat.PROTOTYPE = rootProto 72 | 73 | Cat.PROTOTYPE.speak = function() { 74 | return 'Meow!' 75 | } 76 | var Kitten = function Kitten() { 77 | Cat.apply(this, arguments) 78 | } 79 | 80 | Kitten.PROTOTYPE = Create(Cat.PROTOTYPE) 81 | 82 | Kitten.PROTOTYPE.speak = function() { 83 | return 'Mew.' 84 | } 85 | 86 | console.log("var cat = New(Cat, 'red')") 87 | var cat = New(Cat, 'red') 88 | 89 | console.log("var kitten = New(Kitten, 'blue')") 90 | var kitten = New(Kitten, 'blue') 91 | 92 | console.log("Lookup(cat, 'color')", Lookup(cat, 'color')) 93 | console.log("Lookup(cat, 'speak')()", Lookup(cat, 'speak')()) 94 | console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color')) 95 | console.log("Lookup(kitten, 'speak')()", Lookup(kitten, 'speak')()) 96 | console.log() 97 | console.log("Changing Cat prototype to return MEOW from speak()...") 98 | 99 | Cat.PROTOTYPE.speak = function() { 100 | return 'MEOW.' 101 | } 102 | 103 | console.log("Lookup(cat, 'speak')()", Lookup(cat, 'speak')()) 104 | console.log("Lookup(kitten, 'speak')()", Lookup(kitten, 'speak')()) 105 | console.log() 106 | console.log('Testing constructor can return any object...') 107 | console.log('var Dog = function Dog() { return 3 }') 108 | var Dog = function Dog() { 109 | return 3 110 | } 111 | 112 | console.log("New(Dog)", New(Dog)) 113 | 114 | console.log('var Dog = function Dog() { return false }') 115 | var Dog = function() { 116 | return false 117 | } 118 | 119 | console.log("New(Dog)", New(Dog)) 120 | console.log() 121 | console.log('Ensuring we can access prototype properties in the constructor...') 122 | console.log("function Cow() { console.log('moo', Lookup(this, 'moo')) } ") 123 | 124 | function Cow() { 125 | console.log('moo', Lookup(this, 'moo')); 126 | } 127 | 128 | console.log('Cow.PROTOTYPE.moo = true ') 129 | Cow.PROTOTYPE = {} 130 | Cow.PROTOTYPE.moo = true 131 | 132 | console.log('New(Cow)') 133 | var cow = New(Cow) 134 | }) 135 | -------------------------------------------------------------------------------- /exercises/prototypical_inheritance_by_hand/problem.md: -------------------------------------------------------------------------------- 1 | We're going to implement a rough analog of JavaScript's prototypical inheritance by hand, to ensure we fully understand exactly how prototypical inheritance fits together. 2 | 3 | # Task 4 | 5 | Implement the functions `New`, `Create` and `Lookup` to simulate JavaScript's `new`, `Object.create` and property lookup mechanisms respectively. 6 | 7 | Throughout this exercise, you will avoid using any built-in JavaScript inheritance features. Instead, you will need to use your own New, Create and Lookup functions as well as `__PROTO__` to represent an instance's prototype, and `PROTOTYPE` to represent a constructor's prototype. 8 | 9 | i.e. 10 | 11 | * New(Apple, 1,2,3) == new Apple(1,2,3) 12 | * `obj.__PROTO__` == obj.__proto__ || Object.getPrototypeOf(obj) 13 | * `Constructor.PROTOTYPE` == `Constructor.prototype` 14 | 15 | ## Part 1: Lookup 16 | 17 | `Lookup` will simulate the behaviour of JavaScript's property lookup mechanism, or "Getters". When you reference any object's property in JavaScript, it will 'walk up the prototype chain' to find the property, if found it will return its value, otherwise it will return `undefined`. 18 | 19 | Your `Lookup` function will be passed a context object, and the property String that we're looking for. If the property is found on the current context, return that property, otherwise check the context's prototype, `__PROTO__`. 20 | 21 | If a property cannot be found in the object's prototype chain, simply return `undefined`. 22 | 23 | ```js 24 | 25 | var cat = { 26 | color: 'black' 27 | } 28 | 29 | var kitten = { 30 | size: 'small' 31 | } 32 | 33 | var otherKitten = { 34 | size: 'small', 35 | color: 'grey' 36 | } 37 | 38 | kitten.__PROTO__ = cat 39 | otherKitten.__PROTO__ = cat 40 | 41 | Lookup(kitten, 'color') // => 'black' 42 | Lookup(otherKitten, 'color') // => 'grey' 43 | 44 | Lookup(kitten, 'wings') // => undefined 45 | 46 | // changing properties on the prototype should 47 | // affect any instances that inherit from it. 48 | cat.color = 'blue' 49 | 50 | Lookup(kitten, 'color') // => 'blue' 51 | 52 | // overridden properties should still work 53 | Lookup(otherKitten, 'color') // => 'grey' 54 | 55 | ``` 56 | 57 | Side Note: in JavaScript, when you 'get' a property (i.e. lookup), the engine walks up the prototype chain to find the value, but if you 'set' a property it ignores the prototype chain and simply sets the value on the current object. We could have implemented a 'Setter' as an exercise, but since there's no magic, it's pretty trivial: 58 | 59 | ```js 60 | 61 | function Setter(context, property, value) { 62 | return context[property] = value 63 | } 64 | 65 | ``` 66 | 67 | ## Part 2: Create 68 | 69 | `Create` will simulate the behaviour of `Object.create`. 70 | 71 | `Create` will be passed an object, and you must return a new object with its prototype (`__PROTO__`) set to the supplied object. 72 | 73 | ```js 74 | fuction Cat() { 75 | 76 | } 77 | 78 | Cat.PROTOTYPE.speak = function() { 79 | return 'Meow!' 80 | } 81 | 82 | function Kitten() { 83 | Cat.apply(this, arguments) 84 | } 85 | 86 | Kitten.PROTOTYPE = Create(Cat.PROTOTYPE) 87 | 88 | var kitten = New(Kitten) 89 | Lookup(kitten, 'speak')() // => 'Meow!' 90 | 91 | ``` 92 | 93 | ## Finale: New 94 | 95 | `New` will simulate the behaviour of JavaScript's `new` keyword. 96 | 97 | The first argument passed to `New` will be a constructor function (i.e. a type). Subsequent parameters must be passed to the constructor function when creating the the new object. 98 | 99 | `New` will return new objects using the supplied constructor function. 100 | 101 | ```js 102 | 103 | function Cat(color) { 104 | this.color = color 105 | } 106 | 107 | var blackCat = New(Cat, 'black') 108 | blackCat.color // => black 109 | 110 | var brownCat = New(Cat, 'brown') 111 | brownCat.color // => brown 112 | 113 | ``` 114 | 115 | The constructor function passed to `New` may have a `.PROTOTYPE` property. All objects created with this constructor will have their `__PROTO__` set to the constructor's `.PROTOTYPE` property. 116 | 117 | ```js 118 | 119 | function Cat(color) { 120 | this.color = color 121 | } 122 | 123 | Cat.PROTOTYPE.speak = function() { 124 | return 'Meow!' 125 | } 126 | 127 | function Kitten() { 128 | Cat.apply(this, arguments) 129 | } 130 | 131 | Kitten.PROTOTYPE = Create(Cat) 132 | 133 | Kitten.PROTOTYPE.speak = function() { 134 | return 'Mew.' 135 | } 136 | 137 | var blackCat = New(Cat, 'black') 138 | blackCat.color // => black 139 | 140 | var brownCat = New(Cat, 'brown') 141 | brownCat.color // => brown 142 | 143 | ``` 144 | 145 | Prototype properties should be available in the constructor: 146 | 147 | ```js 148 | function Cow() { 149 | // lookup this.moo: 150 | console.log('moo', Lookup(this, 'moo')); 151 | } 152 | 153 | // this step is done for you automatically in 'real' javascript. 154 | Cow.PROTOTYPE = {} 155 | 156 | Cow.PROTOTYPE.moo = true 157 | var cow = New(Cow) // 'moo' true 158 | 159 | ``` 160 | 161 | We also need to simulate one other behaviour of the `new` keyword: If the constructor itself returns a value, `New` will return that value. 162 | 163 | ```js 164 | 165 | function Cat(){ 166 | return 3 167 | } 168 | var cat = new Cat() // 3 169 | var cat = New(Cat) // 3 170 | 171 | ``` 172 | 173 | ## Conditions 174 | 175 | * Do not use any built-in javascript prototypical inheritance features. 176 | * Do not call `new` 177 | * Do not use `__proto__`, `Object.getPrototypeOf` or `Object.setPrototypeOf` 178 | 179 | ## Hints 180 | 181 | * Use `hasOwnProperty` to discover if an object has a property. 182 | 183 | 184 | ## Boilerplate 185 | 186 | ```js 187 | 188 | /** 189 | * @param context {Object} Initial object to start searching for `property` 190 | * @param property {String} name of property we are trying to locate. 191 | * @return {Mixed} The value of `property` in `context` or somewhere in its prototype chain. 192 | */ 193 | 194 | function Lookup(context, property) { 195 | 196 | } 197 | 198 | /** 199 | * @param proto {Object} The prototype to use for the created object. 200 | * @return A new Object whose prototype is set to `proto` 201 | */ 202 | 203 | function Create(proto) { 204 | 205 | } 206 | 207 | /** 208 | * @param Constructor {Function} Constructor for a new type. 209 | * @return new instance of the type defined by `Constructor`. 210 | */ 211 | 212 | function New(Constructor) { 213 | 214 | } 215 | 216 | module.exports = { 217 | Lookup: Lookup, 218 | Create: Create, 219 | New: New 220 | } 221 | 222 | ``` 223 | -------------------------------------------------------------------------------- /exercises/prototypical_inheritance_by_hand/solution/solution.js: -------------------------------------------------------------------------------- 1 | function Lookup(context, property) { 2 | if (!context) return undefined 3 | if (Object.prototype.hasOwnProperty.call(context, property)) return context[property] 4 | return Lookup(context.__PROTO__, property) 5 | } 6 | 7 | function Create(proto) { 8 | return { 9 | __PROTO__: proto 10 | } 11 | } 12 | 13 | function New(Type) { 14 | var obj = Create(Type.PROTOTYPE) 15 | var args = [].slice.call(arguments, 1) // remove Type arg 16 | var result = Type.apply(obj, args) 17 | if (typeof result !== 'undefined') return result 18 | return obj 19 | } 20 | 21 | module.exports = { 22 | Lookup: Lookup, 23 | Create: Create, 24 | New: New 25 | } 26 | -------------------------------------------------------------------------------- /exercises/randomizer.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var loremIpsum = require('lorem-ipsum') 4 | 5 | function randomArrayOf(minCells, maxCells, callback) { 6 | if ('function' === typeof maxCells && 'undefined' === typeof callback) { 7 | callback = maxCells 8 | maxCells = minCells 9 | } 10 | return Array.apply(null, { length: randomInt(minCells, maxCells) }).map(callback) 11 | } 12 | 13 | function randomArrayOfInts(maxCells, minInt, maxInt) { 14 | return randomArrayOf(0, maxCells, function() { 15 | return randomInt(minInt, maxInt) 16 | }) 17 | } 18 | 19 | function randomArrayOfLorems(minCells, maxCells) { 20 | var loremOptions = Array.prototype.slice(arguments, 2) 21 | return randomArrayOf(minCells, maxCells, function() { 22 | return loremIpsum.apply(loremIpsum, loremOptions) 23 | }) 24 | } 25 | 26 | function randomInt(min, max) { 27 | if (min === max) return max 28 | return Math.floor((Math.random() * (max - min + 1)) + min) 29 | } 30 | 31 | function randomWords(count, options) { 32 | options = options || {} 33 | var result = loremIpsum().split(' ').slice(0, count) 34 | if (options.capitalized) { 35 | result = result.map(function(word) { 36 | word = word[0].toUpperCase() + word.substring(1) 37 | return word 38 | }) 39 | } 40 | return result.join(' ') 41 | } 42 | 43 | module.exports = { 44 | int: randomInt, 45 | arrayOf: randomArrayOf, 46 | arrayOfInts: randomArrayOfInts, 47 | arrayOfLorems: randomArrayOfLorems, 48 | words: randomWords 49 | } 50 | -------------------------------------------------------------------------------- /exercises/recursion/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var random = require('../randomizer') 4 | var runner = require('../runner') 5 | 6 | var data = { 7 | "name": "functional-javascript-workshop", 8 | "version": "0.0.13", 9 | "dependencies": { 10 | "workshopper": { 11 | "version": "0.3.3", 12 | "dependencies": { 13 | "map-async": { 14 | "version": "0.1.1" 15 | }, 16 | "tuple-stream": { 17 | "version": "0.0.2" 18 | }, 19 | "split": { 20 | "version": "0.2.10" 21 | }, 22 | "through": { 23 | "version": "2.3.4" 24 | }, 25 | "mkdirp": { 26 | "version": "0.3.5" 27 | }, 28 | "colors-tmpl": { 29 | "version": "0.1.0", 30 | "dependencies": { 31 | "colors": { 32 | "version": "0.6.2" 33 | } 34 | } 35 | }, 36 | "terminal-menu": { 37 | "version": "0.1.0", 38 | "dependencies": { 39 | "resumer": { 40 | "version": "0.0.0" 41 | }, 42 | "charm": { 43 | "version": "0.1.2" 44 | }, 45 | "inherits": { 46 | "version": "2.0.1" 47 | } 48 | } 49 | }, 50 | "optimist": { 51 | "version": "0.6.0", 52 | "dependencies": { 53 | "wordwrap": { 54 | "version": "0.0.2" 55 | }, 56 | "minimist": { 57 | "version": "0.0.4" 58 | } 59 | } 60 | }, 61 | "pygmentize-bundled": { 62 | "version": "2.1.0", 63 | "dependencies": { 64 | "readable-stream": { 65 | "version": "1.0.17" 66 | }, 67 | "bl": { 68 | "version": "0.4.1" 69 | }, 70 | "through2": { 71 | "version": "0.2.1" 72 | } 73 | } 74 | }, 75 | "xtend": { 76 | "version": "2.1.1", 77 | "dependencies": { 78 | "object-keys": { 79 | "version": "0.4.0" 80 | } 81 | } 82 | } 83 | } 84 | }, 85 | "lorem-ipsum": { 86 | "version": "0.1.1", 87 | "dependencies": { 88 | "optimist": { 89 | "version": "0.3.7", 90 | "dependencies": { 91 | "wordwrap": { 92 | "version": "0.0.2" 93 | } 94 | } 95 | }, 96 | "inflection": { 97 | "version": "1.2.6" 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | function getRandomSubTree() { 105 | return random.int(0, Object.keys(data.dependencies.workshopper.dependencies).length) 106 | } 107 | 108 | var items = [getRandomSubTree(), getRandomSubTree(), getRandomSubTree(), getRandomSubTree()] 109 | 110 | module.exports = runner.custom(function(getDependencies) { 111 | var dependencyTree1 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[0]]] 112 | var dependencyTree2 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[1]]] 113 | var dependencyTree3 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[2]]] 114 | var dependencyTree4 = data.dependencies[Object.keys(data.dependencies)[1]] 115 | var fullTree = data 116 | 117 | var result = [] 118 | result.push("getDependencies(dependencyTree1)", getDependencies(dependencyTree1)) 119 | result.push("getDependencies(dependencyTree2)", getDependencies(dependencyTree2)) 120 | result.push("getDependencies(dependencyTree3)", getDependencies(dependencyTree3)) 121 | result.push("getDependencies(dependencyTree4)", getDependencies(dependencyTree4)) 122 | result.push("getDependencies(fullTree)", getDependencies(fullTree)) 123 | return result 124 | }).hideInput(data) 125 | -------------------------------------------------------------------------------- /exercises/recursion/problem.fr.md: -------------------------------------------------------------------------------- 1 | ## Défi 2 | 3 | Écrivez une fonction récursive qui renvoie toutes les dépendances (et sous-dépendances) unique d’un module, triées alphabétiquement. Elles doivent être affichées au format *dependance@version*, par ex. `inflection@1.2.6`. 4 | 5 | On autorise des versions multiples d’un même module, mais les doublons (versions identiques) doivent être retirés. 6 | 7 | ## Arguments 8 | 9 | * `tree` : Une arborescence de dépendances. Voir ci-dessous pour sa structure. 10 | 11 | ## Exemple 12 | 13 | ```js 14 | var loremIpsum = { 15 | "name": "lorem-ipsum", 16 | "version": "0.1.1", 17 | "dependencies": { 18 | "optimist": { 19 | "version": "0.3.7", 20 | "dependencies": { 21 | "wordwrap": { 22 | "version": "0.0.2" 23 | } 24 | } 25 | }, 26 | "inflection": { 27 | "version": "1.2.6" 28 | } 29 | } 30 | } 31 | 32 | getDependencies(loremIpsum) // => [ 'inflection@1.2.6', 'optimist@0.3.7', 'wordwrap@0.0.2' ] 33 | 34 | ``` 35 | 36 | ## Conditions 37 | 38 | * N’utilisez pas de boucle `for`/`while`. 39 | 40 | ## Base de travail 41 | 42 | ```js 43 | function getDependencies(tree) { 44 | // VOTRE SOLUTION ICI 45 | // Note : n’hésitez pas à ajouter des arguments dont vous auriez besoin dans 46 | // vos appels récursifs. Mais ça n’a rien d’obligatoire ! Il y a bien des 47 | // manières de faire de la récursivité. 48 | } 49 | 50 | module.exports = getDependencies 51 | ``` 52 | 53 | ## Ressources 54 | 55 | * https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/keys 56 | -------------------------------------------------------------------------------- /exercises/recursion/problem.ko.md: -------------------------------------------------------------------------------- 1 | # 해야할 일 2 | 3 | 중복없이 모든 의존성과 모듈의 하위 의존성을 알파벳 순으로 반환하는 재귀 함수를 구현하세요. 의존성은 dependency@version (예: 'inflection@1.2.6')의 형식으로 출력해야 합니다. 4 | 5 | 같은 모듈의 여러 버전은 허용됩니다만, 같은 버전이 중복되면 제거해야 합니다. 6 | 7 | ## 인자: 8 | 9 | * tree: 의존성 트리. 구조는 아래의 예제를 보세요. 10 | 11 | ## 예제 12 | 13 | ```js 14 | var loremIpsum = { 15 | "name": "lorem-ipsum", 16 | "version": "0.1.1", 17 | "dependencies": { 18 | "optimist": { 19 | "version": "0.3.7", 20 | "dependencies": { 21 | "wordwrap": { 22 | "version": "0.0.2" 23 | } 24 | } 25 | }, 26 | "inflection": { 27 | "version": "1.2.6" 28 | } 29 | } 30 | } 31 | 32 | getDependencies(loremIpsum) // => [ 'inflection@1.2.6', 'optimist@0.3.7', 'wordwrap@0.0.2' ] 33 | 34 | ``` 35 | 36 | ## 조건: 37 | 38 | * for/while 반복문을 사용하지 마세요. 39 | 40 | ## 템플릿 41 | 42 | ```js 43 | 44 | function getDependencies(tree) { 45 | // 여기에 해답을 적으세요 46 | // 주의: 이 함수를 재귀 호출할 수 있도록 47 | // 인자를 추가하셔도 됩니다 48 | // 필요 없을 수도 있습니다! 재귀를 구현하는 방법은 많습니다. 49 | } 50 | 51 | module.exports = getDependencies 52 | 53 | ``` 54 | 55 | ## 참고 56 | 57 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys 58 | -------------------------------------------------------------------------------- /exercises/recursion/problem.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | Implement a recursive function that returns all of the unique dependencies, and sub-dependencies of a module, sorted alphabetically. Dependencies should be printed as dependency@version e.g. 'inflection@1.2.6'. 4 | 5 | Multiple versions of the same module are allowed, but duplicates modules of the same version should be removed. 6 | 7 | ## Arguments: 8 | 9 | * tree: A dependency tree. See below for an example of the structure. 10 | 11 | ## Example 12 | 13 | ```js 14 | var loremIpsum = { 15 | "name": "lorem-ipsum", 16 | "version": "0.1.1", 17 | "dependencies": { 18 | "optimist": { 19 | "version": "0.3.7", 20 | "dependencies": { 21 | "wordwrap": { 22 | "version": "0.0.2" 23 | } 24 | } 25 | }, 26 | "inflection": { 27 | "version": "1.2.6" 28 | } 29 | } 30 | } 31 | 32 | getDependencies(loremIpsum) // => [ 'inflection@1.2.6', 'optimist@0.3.7', 'wordwrap@0.0.2' ] 33 | 34 | ``` 35 | 36 | ## Conditions: 37 | 38 | * Do not use any for/while loops. 39 | 40 | ## Boilerplate 41 | 42 | ```js 43 | 44 | function getDependencies(tree) { 45 | // SOLUTION GOES HERE 46 | // Note: Feel free to add additional arguments 47 | // to this function for use with recursive calls. 48 | // Or not! There are many ways to recurse. 49 | } 50 | 51 | module.exports = getDependencies 52 | 53 | ``` 54 | 55 | ## Resources 56 | 57 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys 58 | -------------------------------------------------------------------------------- /exercises/recursion/solution/solution.js: -------------------------------------------------------------------------------- 1 | function getDependencies(mod, result) { 2 | result = result || [] 3 | var dependencies = mod && mod.dependencies || [] 4 | Object.keys(dependencies).forEach(function(dep) { 5 | var key = dep + '@' + mod.dependencies[dep].version 6 | if (result.indexOf(key) === -1) result.push(key) 7 | getDependencies(mod.dependencies[dep], result) 8 | }) 9 | return result.sort() 10 | } 11 | 12 | module.exports = getDependencies 13 | -------------------------------------------------------------------------------- /exercises/runner.js: -------------------------------------------------------------------------------- 1 | var compareStdOut = require('workshopper-exercise/comparestdout') 2 | var deepEqual = require('deep-eql') 3 | var execute = require('workshopper-exercise/execute') 4 | var exerciser = require('workshopper-exercise') 5 | var filecheck = require('workshopper-exercise/filecheck') 6 | var fs = require('fs') 7 | var os = require('os') 8 | var path = require('path') 9 | var util = require('util') 10 | 11 | var verbose = true, showInput = true, initFx, wrapUpFx, customFx, wrapperModule 12 | 13 | function runner() { 14 | var exercise = execute(filecheck(exerciser())) 15 | var input = Array.prototype.slice.call(arguments) 16 | var submittedFx, __ 17 | 18 | exercise.addProcessor(function(mode, callback) { 19 | __ = exercise.__.bind(exercise) 20 | var testFile = this.args[0] 21 | try { 22 | submittedFx = require(path.resolve(process.cwd(), testFile)); 23 | } catch (e) { 24 | this.emit('fail', e.stack) 25 | return callback(e, false) 26 | } 27 | 28 | if (typeof submittedFx !== 'function') { 29 | var message = __('fail.must_export_function') 30 | this.emit('fail', message) 31 | return callback(new Error(message), false) 32 | } 33 | 34 | callback(null, true) 35 | }); 36 | 37 | if (wrapperModule && wrapperModule.path) { 38 | exercise.addSetup(function setupWrapperModule(mode, callback) { 39 | var modulePath = wrapperModule.path 40 | if (wrapperModule.options && wrapperModule.options.localized) { 41 | var localizedPath = modulePath.replace(/\.\w+$/, '_' + exercise.lang + '$&') 42 | if (fs.existsSync(path.resolve(process.cwd(), localizedPath))) { 43 | modulePath = localizedPath 44 | } 45 | } 46 | this.solutionCommand = [ modulePath, this.solution ].concat(this.solutionArgs) 47 | this.submissionCommand = [ modulePath, this.submission ].concat(this.submissionArgs) 48 | 49 | if (input.length > 0) { 50 | var file = path.join(os.tmpdir(), path.basename(this.solution)) + '.input.json' 51 | fs.writeFileSync(file, JSON.stringify(input), { encoding: 'utf-8' }) 52 | exercise.addCleanup(function(mode, pass, callback) { 53 | fs.unlink(file, callback) 54 | }) 55 | this.solutionCommand.splice(2, 0, file) 56 | this.submissionCommand.splice(2, 0, file) 57 | } 58 | process.nextTick(callback) 59 | }) 60 | 61 | return compareStdOut(exercise) 62 | } 63 | 64 | exercise.addProcessor(function(mode, callback) { 65 | if (initFx) { initFx(); } 66 | var submittedResult = obtainResult(submittedFx, input) 67 | if (verbose) { 68 | if (showInput) { 69 | var displayInput = input.length === 1 ? input[0] : 70 | input.map(function(o) { return 'function' === typeof o ? o.toString() : o }) 71 | console.log(__('input'), util.inspect(displayInput, { colors: true }).replace(/,\n\s*/g, ", ")) 72 | } 73 | console.log(__('submission'), util.inspect(submittedResult, { colors: true }).replace(/,\n\s*/g, ", ")) 74 | } 75 | 76 | if ('run' === mode) { 77 | return callback(null, true) 78 | } 79 | 80 | if (initFx) { initFx(); } 81 | var solutionFx = require(this.solution) 82 | var solutionResult = obtainResult(solutionFx, input) 83 | if (verbose) { 84 | console.log(__('solution'), util.inspect(solutionResult, { colors: true }).replace(/,\n\s*/g, ", ")) 85 | } 86 | var resultsMatch = exercise.ignoreReturnValue ? true : deepEqual(submittedResult, solutionResult) 87 | callback(null, resultsMatch) 88 | }) 89 | 90 | if (wrapUpFx) { 91 | exercise.addVerifyProcessor(wrapUpFx) 92 | } 93 | 94 | return exercise 95 | } 96 | 97 | function obtainResult(fx, input) { 98 | if (customFx) { 99 | input = [fx].concat(input) 100 | return customFx.apply(null, input) 101 | } 102 | return fx.apply(null, input) 103 | } 104 | 105 | runner.custom = function custom(fx) { 106 | customFx = fx 107 | return runner 108 | } 109 | 110 | runner.hideInput = function quiet() { 111 | showInput = false 112 | return runner.apply(null, arguments) 113 | } 114 | 115 | runner.init = function init(fx) { 116 | initFx = fx 117 | return runner 118 | } 119 | 120 | runner.quiet = function quiet() { 121 | verbose = false 122 | return runner.apply(null, arguments) 123 | } 124 | 125 | runner.wrapWith = function wrapWith(modulePath, options) { 126 | verbose = false 127 | wrapperModule = { path: modulePath, options: options } 128 | return runner.apply(null, Array.prototype.slice.call(arguments, 1)) 129 | } 130 | 131 | runner.wrapUp = function wrapUp(fx) { 132 | wrapUpFx = fx 133 | return runner 134 | } 135 | 136 | module.exports = runner 137 | -------------------------------------------------------------------------------- /exercises/trampoline/exercise.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var runner = require('../runner') 4 | 5 | var exercise = module.exports = runner.custom(function(repeat) { 6 | var COUNT = 100000 7 | console.log(exercise.__('intro', COUNT)) 8 | var count = 0 9 | repeat(function() { 10 | count++ 11 | }, COUNT) 12 | console.log(exercise.__('result', count)) 13 | }).quiet() 14 | -------------------------------------------------------------------------------- /exercises/trampoline/problem.fr.md: -------------------------------------------------------------------------------- 1 | La base de travail fournie plus bas contient une définition de `repeat()`. Celle-ci recevra une opération sous forme de `Function`, et un nombre `num`, pour invoquer la fonction `num` fois : 2 | 3 | ```js 4 | var count = 0 5 | repeat(function() { 6 | count++ 7 | }, 100) 8 | 9 | console.log('exécuté %d fois.', count) 10 | // => exécuté 100 fois. 11 | ``` 12 | 13 | MAIS vous remarquerez qu’exécuter `repeat()` avec un nombre trop grand de répétitions causera un débordement de pile (*stack overflow*) : 14 | 15 | ``` 16 | var count = 0 17 | repeat(function() { 18 | count++ 19 | }, 100000) 20 | 21 | console.log('exécuté %d fois', count) 22 | // => RangeError: Maximum call stack size exceeded 23 | ``` 24 | 25 | # Défi 26 | 27 | Modifiez la base de travail ci-dessous pour qu’elle utilise un trampoline afin que la fonction s’appelle elle-même continuellement. 28 | 29 | Vous pouvez supposer que l’opération qui vous sera fournie ne prend aucun argument (ou qu’ils sont déjà pré-remplis), et que sa valeur de retour est sans importance. 30 | 31 | ## Conditions 32 | 33 | Vos modifications de l’implémentation de `repeat()` ne doivent pas utiliser de boucles ou d’itérateurs (ex. `forEach()`). 34 | 35 | ## Conseils 36 | 37 | - Modifiez `repeat()` pour qu’elle envoie la « prochaine étape », s’il y en a une. 38 | - Un trampoline continue à exécuter les étapes en obtenant à chaque tour la suivante, jusqu’à ce qu’il n’y en ait plus. Pour le coup, vous pouvez utiliser une boucle dans votre fonction de trampoline ! 39 | - Si votre programme prend trop de temps à s’exécuter, vous avez probablement un souci. Utilisez Ctrl+C pour arrêter le processus Node. 40 | 41 | ## Base de travail 42 | 43 | ```js 44 | function repeat(operation, num) { 45 | // Modifiez ce code pour qu’il ne cause pas de débordement de pile ! 46 | if (num <= 0) return 47 | operation() 48 | return repeat(operation, --num) 49 | } 50 | 51 | function trampoline(fn) { 52 | // Voilà où implémenter votre trampoline ; une boucle est autorisée ici. 53 | } 54 | 55 | module.exports = function(operation, num) { 56 | // Et voilà où vous voudrez sans doute plutôt appeler votre trampoline ! 57 | return repeat(operation, num) 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /exercises/trampoline/problem.ko.md: -------------------------------------------------------------------------------- 1 | 템플릿은 `repeat`의 선언을 포함합니다. `repeat`는 함수 operation, 숫자 num을 받아, operation을 num번 수행합니다. 2 | 3 | ```js 4 | var count = 0 5 | repeat(function() { 6 | count++ 7 | }, 100) 8 | 9 | console.log('executed %d times.', count) 10 | // => executed 100 times. 11 | ``` 12 | 13 | 하지만 `repeat`를 큰 `num`으로 수행하면 스택 오버플로가 일어날 수 있습니다. 14 | 15 | ``` 16 | var count = 0 17 | repeat(function() { 18 | count++ 19 | }, 100000) 20 | 21 | console.log('executed %d times', count) 22 | // => RangeError: Maximum call stack size exceeded 23 | ``` 24 | 25 | # 해야할 일 26 | 27 | trampoline을 사용해 지속적으로 동기 호출하도록 밑의 템플릿을 수정하세요. 28 | 29 | 반복되는 연산이 인자를 사용하지 않고(혹은 이미 기능에 바인딩되었고) 반환 값이 중요하지 않다고 생각해도 됩니다. 30 | 31 | ## 조건 32 | 33 | * 반복문을 포함해 repeat의 구현을 변경하지 마세요. 34 | (그래도 다른 방법으로 변경할 수 있습니다.) 35 | 36 | ## 힌트 37 | 38 | * `repeat`를 수정해 다음 단계가 있다면 '다음 단계'를 반환하도록 하세요. 39 | * trampoline은 다음 단계가 없을 때까지, 계속해서 동기적으로 단계를 실행하고 새로운 단계로 넘어갑니다. 여기에 반복문을 사용할 수 있습니다! 40 | * 프로그램의 수행시간이 길다면, 무언가 잘못된 것입니다. 노드 프로세스를 죽이려면 Control - C를 사용하세요. 41 | 42 | ## 템플릿 43 | 44 | ```js 45 | function repeat(operation, num) { 46 | // 스택 오버플로를 일으키지 않도록 수정하세요! 47 | if (num <= 0) return 48 | operation() 49 | return repeat(operation, --num) 50 | } 51 | 52 | function trampoline(fn) { 53 | // trampoline을 구현하셔야 합니다. 54 | } 55 | 56 | module.exports = function(operation, num) { 57 | // 여기서 trampoline을 호출하셔야 합니다! 58 | return repeat(operation, num) 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /exercises/trampoline/problem.md: -------------------------------------------------------------------------------- 1 | The boilerplate includes a definition of `repeat`. `repeat` will take a Function operation, and a Number num, and invoke operation num times: 2 | 3 | ```js 4 | var count = 0 5 | repeat(function() { 6 | count++ 7 | }, 100) 8 | 9 | console.log('executed %d times.', count) 10 | // => executed 100 times. 11 | ``` 12 | 13 | BUT note that executing `repeat` with a large `num` causes a stack overflow: 14 | 15 | ``` 16 | var count = 0 17 | repeat(function() { 18 | count++ 19 | }, 100000) 20 | 21 | console.log('executed %d times', count) 22 | // => RangeError: Maximum call stack size exceeded 23 | ``` 24 | 25 | # Task 26 | 27 | Modify the boilerplate below such that it uses a trampoline to continuously call itself synchronously. 28 | 29 | You can assume that the operation passed to repeat does not take arguments (or they are already bound to the function) and the return value is not important. 30 | 31 | ## Conditions 32 | 33 | * Do not change the implementation of repeat to include any loops 34 | (you may change it in other ways though). 35 | 36 | ## Hints 37 | 38 | * Modify `repeat` so it returns the 'next step', if there is one. 39 | * A trampoline continues to synchronously execute steps, getting new steps, until there are no more steps. You can use a loop here! 40 | * If your program takes a long time to run, something is probably wrong. Use Control - C to kill the node process. 41 | 42 | ## Boilerplate 43 | 44 | ```js 45 | function repeat(operation, num) { 46 | // Modify this so it doesn't cause a stack overflow! 47 | if (num <= 0) return 48 | operation() 49 | return repeat(operation, --num) 50 | } 51 | 52 | function trampoline(fn) { 53 | // You probably want to implement a trampoline! 54 | } 55 | 56 | module.exports = function(operation, num) { 57 | // You probably want to call your trampoline here! 58 | return repeat(operation, num) 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /exercises/trampoline/solution/solution.js: -------------------------------------------------------------------------------- 1 | function repeat(operation, num) { 2 | return function() { 3 | if (num <= 0) return 4 | operation() 5 | return repeat(operation, --num) 6 | } 7 | } 8 | 9 | function trampoline(fn) { 10 | while(fn && typeof fn === 'function') { 11 | fn = fn() 12 | } 13 | } 14 | 15 | module.exports = function(operation, num) { 16 | trampoline(function() { 17 | return repeat(operation, num) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /functional-javascript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict" 4 | 5 | var Workshopper = require('workshopper') 6 | var path = require('path') 7 | 8 | Workshopper({ 9 | name : 'functional-javascript' 10 | , appDir : __dirname 11 | , languages : ['en', 'fr', 'ko'] 12 | }) 13 | -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "FUNCTIONAL JAVASCRIPT IS GOOD", 3 | "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin", 4 | "common": { 5 | "exercise": { 6 | "fail": { 7 | "missing_deps": "You need to install all of the dependencies you are using in your solution (e.g. lodash)", 8 | "module_not_found": "Could not find your file. Make sure the path is correct.", 9 | "must_export_function": "You should always return a function using the module.exports object." 10 | }, 11 | "input": "input: %s", 12 | "submission": "submission: %s", 13 | "solution": "solution: %s" 14 | } 15 | }, 16 | "exercises": { 17 | "Basic: Map": { 18 | "didnt_use_map": "You did not use Array#map", 19 | "used_map": "Yay! You used Array#map" 20 | }, 21 | "Basic: Every Some": { 22 | "found_good_lists": "found %d good lists!" 23 | }, 24 | "Basic: Call": { 25 | "matched_objects": "Matched %d of %d valid objects from %d total." 26 | }, 27 | "Higher Order Functions": { 28 | "call_log": "Called function %d times." 29 | }, 30 | "Function Spies": { 31 | "call_times": "Method called %d times.", 32 | "incorrect_return": "Check your function's return value!", 33 | "incorrect_this": "Check the function's this! Hint: Function#apply", 34 | "not_all_args": "Check you are passing ALL the arguments! Hint: Function#apply", 35 | "incorrect_count": "Check that your Spy is counting the method invocations correctly!" 36 | }, 37 | "Trampoline": { 38 | "intro": "repeating %d times", 39 | "result": "Successfully executed %d times." 40 | }, 41 | "Async Loops": { 42 | "all_loaded": "All %d users loaded!", 43 | "bad_result": "expected: \n%s\n but got:\n%s", 44 | "took_too_long": "Took too long!" 45 | }, 46 | "Currying": { 47 | "five_words": "This,problem,has,been,solved" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "LE JAVASCRIPT FONCTIONNEL C’EST LE BIEN", 3 | "subtitle" : "\u001b[23mSélectionnez un exercice et tapez \u001b[3mEnter\u001b[23m pour démarrer", 4 | "common": { 5 | "exercise": { 6 | "fail": { 7 | "missing_deps": "Vous avez besoin d’installer toutes les dépendances que vous utilisez dans votre solution (ex. lodash)", 8 | "module_not_found": "Impossible de trouver votre fichier. Assurez-vous que le chemin d’accès au fichier est correct.", 9 | "must_export_function": "Vous devriez toujours renvoyer une fonction au travers de `module.exports`." 10 | }, 11 | "input": "entrée: %s", 12 | "submission": "votre résultat : %s", 13 | "solution": "résultat attendu : %s" 14 | } 15 | }, 16 | "exercise": { 17 | "Hello World": "Bonjour Monde", 18 | "Higher Order Functions": "Fonctions d’Ordre Supérieur", 19 | "Basic: Map": "Les bases : Map", 20 | "Basic: Filter": "Les bases : Filter", 21 | "Basic: Every Some": "Les bases : Every, Some", 22 | "Basic: Reduce": "Les bases : Reduce", 23 | "Basic: Recursion": "Les bases : Récursivité", 24 | "Basic: Call": "Les bases : Call", 25 | "Partial Application without Bind": "Application Partielle sans Bind", 26 | "Partial Application with Bind": "Application Partielle avec Bind", 27 | "Implement Map with Reduce": "Implémenter Map à l’aide de Reduce", 28 | "Function Spies": "Espions sur Fonctions", 29 | "Blocking Event Loop": "Boucle d’Événements Bloquée", 30 | "Trampoline": "Trampoline", 31 | "Async Loops": "Boucles Asynchrones", 32 | "Recursion": "Récursivité", 33 | "Currying": "Currying", 34 | "Function Call": "Appel de Fonction" 35 | }, 36 | "exercises": { 37 | "Basic: Map": { 38 | "didnt_use_map": "Vous n’avez pas utilisé Array#map", 39 | "used_map": "Youpi ! Vous avez utilisé Array#map" 40 | }, 41 | "Basic: Every Some": { 42 | "found_good_lists": "%d bonnes listes trouvées !" 43 | }, 44 | "Basic: Call": { 45 | "matched_objects": "Détection de %d des %d objets valides, sur un total de %d." 46 | }, 47 | "Higher Order Functions": { 48 | "call_log": "Fonction appelée %d fois." 49 | }, 50 | "Function Spies": { 51 | "call_times": "Méthode appelée %d fois.", 52 | "incorrect_return": "Vérifiez la valeur de retour de votre fonction !", 53 | "incorrect_this": "Vérifiez le `this` de votre fonction ! Conseil : `Function#apply()`.", 54 | "not_all_args": "Vérifiez que vous transmettez bien TOUS les arguments ! Conseil : `Function#apply()`.", 55 | "incorrect_count": "Vérifiez que votre Spy est compter les appels de méthode correctement!" 56 | }, 57 | "Trampoline": { 58 | "intro": "Répétition de la fonction %d fois", 59 | "result": "La fonction a été exécutée avec succès %d fois." 60 | }, 61 | "Async Loops": { 62 | "all_loaded": "L’ensemble des %d utilisateurs a été chargé !", 63 | "bad_result": "j’attendais : \n%s\n mais j’ai reçu :\n%s", 64 | "took_too_long": "Ça a pris trop longtemps !" 65 | }, 66 | "Currying": { 67 | "five_words": "Ce,problème,est,désormais,résolu" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "FUNCTIONAL JAVASCRIPT IS GOOD", 3 | "subtitle": "\u001b[23m연습 문제를 선택하고 \u001b[3m엔터\u001b[23m를 눌러 시작하세요", 4 | "common": { 5 | "exercise": { 6 | "fail": { 7 | "missing_deps": "해결책에서 사용하는 모든 의존성을 설치하셔야 합니다. (예: lodash)", 8 | "module_not_found": "파일을 찾을 수 없습니다. 경로가 정확한지 확인하세요.", 9 | "must_export_function": "항상 module.exports 객체를 사용해 함수를 반환해야 합니다." 10 | }, 11 | "input": "input: %s", 12 | "submission": "submission: %s", 13 | "solution": "solution: %s" 14 | } 15 | }, 16 | "exercise": { 17 | "Hello World": "헬로우 월드", 18 | "Higher Order Functions": "고차 함수", 19 | "Basic: Map": "기초: Map", 20 | "Basic: Filter": "기초: Filter", 21 | "Basic: Every Some": "기초: Every, Some", 22 | "Basic: Reduce": "기초: Reduce", 23 | "Basic: Recursion": "기초: Recursion", 24 | "Basic: Call": "기초: Call", 25 | "Partial Application without Bind": "바인드 없는 부분 애플리케이션", 26 | "Partial Application with Bind": "바인드 있는 부분 애플리케이션", 27 | "Implement Map with Reduce": "Reduce로 Map 구현하기", 28 | "Function Spies": "함수 스파이", 29 | "Blocking Event Loop": "블로킹 이벤트 루프", 30 | "Trampoline": "트램폴린", 31 | "Async Loops": "비동기 반복", 32 | "Recursion": "재귀", 33 | "Currying": "Currying", 34 | "Function Call": "함수 호출" 35 | }, 36 | "exercises": { 37 | "Basic: Map": { 38 | "didnt_use_map": "Array#map을 사용하지 않았습니다.", 39 | "used_map": "야호! Array#map을 사용했습니다." 40 | }, 41 | "Basic: Every Some": { 42 | "found_good_lists": "%d개의 좋은 목록을 발견했습니다!" 43 | }, 44 | "Basic: Call": { 45 | "matched_objects": "Matched %d of %d valid objects from %d total." 46 | }, 47 | "Higher Order Functions": { 48 | "call_log": "함수가 %d번 호출되었습니다." 49 | }, 50 | "Function Spies": { 51 | "call_times": "메소드가 %d번 호출되었습니다.", 52 | "incorrect_return": "함수의 반환값을 확인하세요!", 53 | "incorrect_this": "함수의 this를 확인하세요! 힌트: Function#apply", 54 | "not_all_args": "인자를 모두 넘겼는지 확인하세요! 힌트: Function#apply", 55 | "incorrect_count": "Check that your Spy is counting the method invocations correctly!" 56 | }, 57 | "Trampoline": { 58 | "intro": "%d번 반복 중", 59 | "result": "성공적으로 %d번 실행되었습니다." 60 | }, 61 | "Async Loops": { 62 | "all_loaded": "모두 %d 사용자를 불러왔습니다!", 63 | "bad_result": "expected: \n%s\n but got:\n%s", 64 | "took_too_long": "너무 오래 걸립니다!" 65 | }, 66 | "Currying": { 67 | "five_words": "This,problem,has,been,solved" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | var serialize = function(input) { 4 | if (typeof input !== 'function') return JSON.stringify(input) 5 | return JSON.stringify({ 6 | isFunction: true, 7 | body: input.toString() 8 | }) 9 | } 10 | 11 | var deserialize = function(input) { 12 | var out = JSON.parse(input) 13 | if (!out.isFunction) return out 14 | eval('out = (' + out.body + ')') 15 | return out 16 | } 17 | 18 | module.exports = function() { 19 | var args = Array.prototype.slice.call(arguments).map(serialize) 20 | 21 | function input() { 22 | var opts = { 23 | args: args, 24 | solutionExecWrap: __filename, 25 | execWrap: __filename, 26 | } 27 | return opts 28 | } 29 | input.wrap = function(fn) { 30 | args.push(serialize(fn)) 31 | return input 32 | } 33 | return input 34 | 35 | } 36 | 37 | module.exports.init = function() { 38 | var mod = require(path.resolve(process.cwd(), process.argv[3])) 39 | var args = process.argv.slice(4).map(deserialize) 40 | var wrap = args.pop() 41 | wrap(args, mod) 42 | } 43 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timoxley/functional-javascript-workshop/024dd9e0532f4cffe021d7a9f9cdaf3eb2ff420f/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functional-javascript-workshop", 3 | "version": "1.0.6", 4 | "description": "The basics of functional programming in JavaScript. No libraries required.", 5 | "main": "index.js", 6 | "bin": { 7 | "functional-javascript": "functional-javascript.js", 8 | "functional-javascript-workshop": "functional-javascript.js" 9 | }, 10 | "scripts": {}, 11 | "keywords": [ 12 | "workshopper", 13 | "tutorial", 14 | "lesson" 15 | ], 16 | "author": "Tim Oxley", 17 | "license": "MIT", 18 | "dependencies": { 19 | "deep-eql": "^0.1.3", 20 | "lorem-ipsum": "^1.0.1", 21 | "workshopper": "^2.6.0", 22 | "workshopper-exercise": "^2.4.0" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/timoxley/functional-javascript-workshop" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/timoxley/functional-javascript-workshop/issues" 30 | }, 31 | "homepage": "https://github.com/timoxley/functional-javascript-workshop" 32 | } 33 | --------------------------------------------------------------------------------