diff --git a/.gitignore b/.gitignore index fd4f2b0..ca6f332 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules .DS_Store +npm-debug.log +TODO.md diff --git a/exercises/async_loops/exercise.js b/exercises/async_loops/exercise.js new file mode 100644 index 0000000..b084927 --- /dev/null +++ b/exercises/async_loops/exercise.js @@ -0,0 +1,58 @@ +"use strict" + +var deepEqual = require('deep-eql') +var inspect = require('util').inspect +var loremIpsum = require('lorem-ipsum') +var random = require('../randomizer') +var runner = require('../runner') + +// All deepEqual impls (assert, deep-eql…) seem to b0rk when multiple objects +// in an array share the same `id` value (wtf?!), so we make sure they're unique. +var userCount = random.int(10, 20) +var userIds = [] +while (userIds.length < userCount) { + var id = random.int(0, 1000) + if (-1 !== userIds.indexOf(id)) continue + userIds.push(id) +} + +var users = random.arrayOf(userCount, function() { + return { + id: userIds.shift(), + name: random.words(2, { capitalized: true }) + } +}) + +var fx + +module.exports = runner.custom(function(f) { + fx = f +}).wrapUp(function(callback) { + var self = this + var ids = users.map(function(user) {return user.id }) + var load = function(id, fn) { + setTimeout(function() { + var match = users.filter(function(user) {return user.id === id}) + if (match.length) fn(match[0]) + else fn(null) + }, random.int(0, 1000)) + } + var done = function(submittedUsers) { + clearTimeout(tooLong) + console.log(submittedUsers) + + if (!deepEqual(submittedUsers, users)) { + self.emit('fail', self.__('bad_result', inspect(users), inspect(submittedUsers))) + return callback(null, false) + } + + console.log(self.__('all_loaded', submittedUsers.length)) + callback(null, true) + } + + fx.call(fx, ids, load, done) + var tooLong = setTimeout(function() { + self.emit('fail', self.__('took_too_long')) + callback(null, false) + }, 1000) +}).quiet(users) diff --git a/exercises/async_loops/problem.fr.md b/exercises/async_loops/problem.fr.md new file mode 100644 index 0000000..6875e85 --- /dev/null +++ b/exercises/async_loops/problem.fr.md @@ -0,0 +1,51 @@ +Le code ci-dessous est cassé ! + +Un développeur Java a ajouté le code atroce ci-dessous à notre projet et ne l’a pas testé ! + +```js +function loadUsers(userIds, load, done) { + var users = [] + for (var i = 0; i < userIds.length; i++) { + users.push(load(userIds[i])) + } + return users +} + +module.exports = loadUsers +``` + +## Défi + +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. + +## Arguments + +* `userIds` : un tableau d’IDs numériques d’utilisateurs. +* `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`). +* `done` : une fonction de rappel finale, qui attend comme argument un tableau des objets utilisateurs chargés. + +## Conditions + +* N’utilisez pas de boucles `for`/`while` (mais `Array#forEach()` reste autorisé). +* L’ordre des utilisateurs dans le tableau passé à `done()` doit correspondre à celui des IDs dans le tableau `userIds` que vous aurez reçu. +* Les utilisateurs doivent être chargés en parallèle, donc la séquence entière ne devrait pas dépasser une seconde. +* Ne créez aucune fonction superflue + +## Conseils + +* Vous n’avez pas besoin d’un tri pour maintenir l’ordre du résultat. +* 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`. + +## Base de travail + +```js +function loadUsers(userIds, load, done) { + var users = [] + for (var i = 0; i < userIds.length; i++) { + users.push(load(userIds[i])) + } + return users +} + +module.exports = loadUsers +``` diff --git a/problems/async_loops/problem.md b/exercises/async_loops/problem.md similarity index 100% rename from problems/async_loops/problem.md rename to exercises/async_loops/problem.md diff --git a/problems/async_loops/solution.js b/exercises/async_loops/solution/solution.js similarity index 100% rename from problems/async_loops/solution.js rename to exercises/async_loops/solution/solution.js diff --git a/problems/basic_call/setup.js b/exercises/basic_call/exercise.js similarity index 71% rename from problems/basic_call/setup.js rename to exercises/basic_call/exercise.js index 96dc536..59ae91e 100644 --- a/problems/basic_call/setup.js +++ b/exercises/basic_call/exercise.js @@ -1,18 +1,11 @@ "use strict" -var input = require('../../input') +var random = require('../randomizer') +var runner = require('../runner') -function randomInt(min, max) { - return Math.floor((Math.random() * (max - min + 1)) + min) -} +var input = random.arrayOfInts(20, 0, 10) -module.exports = input(new Array(randomInt(0, 20)) -.join(',') -.split(',') -.map(function() { - return randomInt(0, 10) -})).wrap(function(input, mod) { - var numbers = input[0] +var exercise = module.exports = runner.custom(function(fx, numbers) { var valid = 1 var objects = [{quack: true}].concat(numbers.map(function(num) { switch(num) { @@ -60,5 +53,5 @@ module.exports = input(new Array(randomInt(0, 20)) } })) - console.log('Matched %d of %d valid objects from %d total.', mod.apply(mod, objects), valid, objects.length) -}) + return exercise.__('matched_objects', fx.apply(null, objects), valid, objects.length) +}).hideInput(input) diff --git a/exercises/basic_call/problem.fr.md b/exercises/basic_call/problem.fr.md new file mode 100644 index 0000000..cfc973f --- /dev/null +++ b/exercises/basic_call/problem.fr.md @@ -0,0 +1,104 @@ +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 : + + > « 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. » + +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. + +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) : + +```js +var duck = { + quack: function() { + console.log('quack') + } +} + +duck.hasOwnProperty('quack') // => true +``` + +Nous n’avons toutefois pas équipé `duck` d’une méthode `hasOwnProperty()`, alors d’où vient-elle ? + +`duck` a été créé avec la syntaxe littérale `{…}`, qui définit un objet, de sorte qu’il hérite automatiquement de `Object.prototype` : + +```js +var object = {quack: true} + +Object.getPrototypeOf(object) === Object.prototype // => true +object.hasOwnProperty('quack') // => true +``` + +Mais qu’en serait-il pour un objet qui n’hérite pas de `Object.prototype` ? + +```js +// Créons un objet avec un prototype `null` +var object = Object.create(null) +object.quack = function() { + console.log('quack') +} + +Object.getPrototypeOf(object) === Object.prototype // => false +Object.getPrototypeOf(object) === null // => true + +object.hasOwnProperty('quack') +// => TypeError: Object object has no method 'hasOwnProperty' +``` + +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. + +```js +// Le premier argument de `call` sera le `this` +// Le reste des arguments est passé à la fonction + +Object.prototype.hasOwnProperty.call(object, 'quack') // => true +``` + +# Défi + +É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. + +Exemple : + +```js +var notDuck = Object.create({quack: true}) +var duck = {quack: true} +duckCount(duck, notDuck) // 1 +``` +## Arguments + +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()`. + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne maintenez pas de variable pour le compteur / l’accumulateur. +* Ne créez aucune fonction superflue + +## Conseil + +La variable automatique `arguments`, disponible dans toute fonction, est un *objet* qui ressemble à un tableau sans en être vraiment un : + +```js +{ + 0: 'argument0', + 1: 'argument1', // etc. + length: 2 +} +``` + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/call +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/hasOwnProperty +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/L_op%C3%A9rateur_in +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/slice#Array-like +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/arguments + +## Base de travail + +```js +function duckCount() { + // VOTRE SOLUTION ICI +} + +module.exports = duckCount +``` diff --git a/problems/basic_call/problem.md b/exercises/basic_call/problem.md similarity index 100% rename from problems/basic_call/problem.md rename to exercises/basic_call/problem.md diff --git a/problems/basic_call/solution.js b/exercises/basic_call/solution/solution.js similarity index 100% rename from problems/basic_call/solution.js rename to exercises/basic_call/solution/solution.js diff --git a/exercises/basic_every_some/exercise.js b/exercises/basic_every_some/exercise.js new file mode 100644 index 0000000..f68b2c8 --- /dev/null +++ b/exercises/basic_every_some/exercise.js @@ -0,0 +1,39 @@ +"use strict" + +var random = require('../randomizer') +var runner = require('../runner') + +function makeUser() { + return { + id: random.int(0, 1000), + name: random.words(2, { capitalized: true }) + } +} + +function makeListOfUsers() { + return random.arrayOf(10, 100, makeUser) +} + +var good = makeListOfUsers() +var bad = makeListOfUsers() +var lists = random.arrayOf(20, function() { + return random.arrayOf(20, function() { + if (Math.random() < 0.95) { + return good[random.int(0, 10)] + } else { + return bad[random.int(0, 10)] + } + }) +}) + +var exercise = module.exports = runner.custom(function(fx, good, lists) { + var test = fx(good) + + var goodLists = 0 + + lists.forEach(function(list) { + test(list) && ++goodLists + }) + + return exercise.__('found_good_lists', goodLists) +}).hideInput(good, lists) diff --git a/exercises/basic_every_some/problem.fr.md b/exercises/basic_every_some/problem.fr.md new file mode 100644 index 0000000..7675000 --- /dev/null +++ b/exercises/basic_every_some/problem.fr.md @@ -0,0 +1,59 @@ +# Défi + +É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. + +Vous n’aurez qu’à examiner la correspondance des propriétés `id`. + +## Exemple + +```js +var goodUsers = [ + { id: 1 }, + { id: 2 }, + { id: 3 } +] + +// `checkUsersValid` est la fonction que vous allez écrire +var testAllValid = checkUsersValid(goodUsers) + +testAllValid([ + { id: 2 }, + { id: 1 } +]) +// => true + +testAllValid([ + { id: 2 }, + { id: 4 }, + { id: 1 } +]) +// => false +``` + +## Arguments + +* `goodUsers` : une liste d’utilisateurs valides + +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. + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/every +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/some + +## Base de travail + +```js +function checkUsersValid(goodUsers) { + return function allUsersValid(submittedUsers) { + // VOTRE SOLUTION ICI + }; +} + +module.exports = checkUsersValid +``` diff --git a/problems/basic_every_some/problem.md b/exercises/basic_every_some/problem.md similarity index 95% rename from problems/basic_every_some/problem.md rename to exercises/basic_every_some/problem.md index 6a13b32..101193c 100644 --- a/problems/basic_every_some/problem.md +++ b/exercises/basic_every_some/problem.md @@ -50,7 +50,7 @@ Use array#some and Array#every to check every user passed to your returned funct ```js function checkUsersValid(goodUsers) { - return function(submittedUsers) { + return function allUsersValid(submittedUsers) { // SOLUTION GOES HERE }; } diff --git a/problems/basic_every_some/solution.js b/exercises/basic_every_some/solution/solution.js similarity index 82% rename from problems/basic_every_some/solution.js rename to exercises/basic_every_some/solution/solution.js index 6dbdfa3..99643dc 100644 --- a/problems/basic_every_some/solution.js +++ b/exercises/basic_every_some/solution/solution.js @@ -1,5 +1,5 @@ module.exports = function checkUsersValid(goodUsers) { - return function(submittedUsers) { + return function allUsersValid(submittedUsers) { return submittedUsers.every(function(submittedUser) { return goodUsers.some(function(goodUser) { return goodUser.id === submittedUser.id diff --git a/exercises/basic_filter/exercise.js b/exercises/basic_filter/exercise.js new file mode 100644 index 0000000..cee349f --- /dev/null +++ b/exercises/basic_filter/exercise.js @@ -0,0 +1,11 @@ +"use strict" + +var loremIpsum = require('lorem-ipsum') +var random = require('../randomizer') +var runner = require('../runner') + +var input = random.arrayOf(10, 30, function() { + return { message: loremIpsum() } +}) + +module.exports = runner.hideInput(input) diff --git a/exercises/basic_filter/problem.fr.md b/exercises/basic_filter/problem.fr.md new file mode 100644 index 0000000..60291c0 --- /dev/null +++ b/exercises/basic_filter/problem.fr.md @@ -0,0 +1,50 @@ +# Défi + +Utilisez `Array#filter` pour écrire une fonction `getShortMessages()` + +`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*. + +Votre fonction doit renvoyer un tableau contenant les messages eux-mêmes, *sans leurs objets conteneurs*. + +## Arguments + +* `messages` : un tableau d’objets aléatoires ayant l’aspect suivant : + +```js +{ + message: 'Esse id amet quis eu esse aute officia ipsum.' // aléatoire +} +``` + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Conseil + +* Essayez d’enchaîner des méthodes de `Array` ! + +## Exemple de résultat + +``` +[ 'Tempor quis esse consequat sunt ea eiusmod.', + 'Id culpa ad proident ad nulla laborum incididunt.', + 'Ullamco in ea et ad anim anim ullamco est.', + 'Est ut irure irure nisi.' ] +``` + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/filter +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map + +## Base de travail + +```js +function getShortMessages(messages) { + // VOTRE SOLUTION ICI +} + +module.exports = getShortMessages +``` diff --git a/problems/basic_filter/problem.md b/exercises/basic_filter/problem.md similarity index 99% rename from problems/basic_filter/problem.md rename to exercises/basic_filter/problem.md index 8de868f..6d54c0a 100644 --- a/problems/basic_filter/problem.md +++ b/exercises/basic_filter/problem.md @@ -3,6 +3,8 @@ Use Array#filter to write a function called `getShortMessages`. `getShortMessages` takes an array of objects with '.message' properties and returns an array of messages that are *less than < 50 characters long*. +The function should return an array containing the messages themselves, *without their containing object*. + ## Arguments * messages: an Array of 10 to 100 random objects that look something like this: @@ -24,9 +26,6 @@ Use Array#filter to write a function called `getShortMessages`. ## Example -The function should return an array containing the messages themselves, *without their containing object*. - -e.g. ``` [ 'Tempor quis esse consequat sunt ea eiusmod.', 'Id culpa ad proident ad nulla laborum incididunt.', diff --git a/problems/basic_filter/solution.js b/exercises/basic_filter/solution/solution.js similarity index 100% rename from problems/basic_filter/solution.js rename to exercises/basic_filter/solution/solution.js diff --git a/problems/basic_inheritance_with_objectcreate/setup.js b/exercises/basic_inheritance_with_objectcreate/exercise.js similarity index 100% rename from problems/basic_inheritance_with_objectcreate/setup.js rename to exercises/basic_inheritance_with_objectcreate/exercise.js diff --git a/problems/basic_inheritance_with_objectcreate/problem.md b/exercises/basic_inheritance_with_objectcreate/problem.md similarity index 100% rename from problems/basic_inheritance_with_objectcreate/problem.md rename to exercises/basic_inheritance_with_objectcreate/problem.md diff --git a/problems/basic_inheritance_with_objectcreate/solution.js b/exercises/basic_inheritance_with_objectcreate/solution/solution.js similarity index 100% rename from problems/basic_inheritance_with_objectcreate/solution.js rename to exercises/basic_inheritance_with_objectcreate/solution/solution.js diff --git a/problems/basic_inheritance_without_objectcreate/setup.js b/exercises/basic_inheritance_without_objectcreate/exercise.js similarity index 100% rename from problems/basic_inheritance_without_objectcreate/setup.js rename to exercises/basic_inheritance_without_objectcreate/exercise.js diff --git a/problems/basic_inheritance_without_objectcreate/problem.md b/exercises/basic_inheritance_without_objectcreate/problem.md similarity index 100% rename from problems/basic_inheritance_without_objectcreate/problem.md rename to exercises/basic_inheritance_without_objectcreate/problem.md diff --git a/problems/basic_inheritance_without_objectcreate/solution.js b/exercises/basic_inheritance_without_objectcreate/solution/solution.js similarity index 100% rename from problems/basic_inheritance_without_objectcreate/solution.js rename to exercises/basic_inheritance_without_objectcreate/solution/solution.js diff --git a/exercises/basic_map/exercise.js b/exercises/basic_map/exercise.js new file mode 100644 index 0000000..cc6df66 --- /dev/null +++ b/exercises/basic_map/exercise.js @@ -0,0 +1,23 @@ +"use strict" + +var random = require('../randomizer') +var runner = require('../runner') + +var input = random.arrayOfInts(19, 0, 9) + +var regularMap = Array.prototype.map, usedMap +Array.prototype.map = function() { + usedMap = true + return regularMap.apply(this, arguments) +} + +module.exports = runner.init(function() { + usedMap = false +}).wrapUp(function(callback) { + if (!usedMap) { + this.emit('fail', this.__('didnt_use_map')); + } else { + this.emit('pass', this.__('used_map')); + } + callback(null, usedMap) +})(input) diff --git a/exercises/basic_map/problem.fr.md b/exercises/basic_map/problem.fr.md new file mode 100644 index 0000000..9eb71f3 --- /dev/null +++ b/exercises/basic_map/problem.fr.md @@ -0,0 +1,39 @@ +# Défi + +Convertissez le code suivant pour utiliser `Array#map` plutôt qu’une boucle : + +```js +function doubleAll(numbers) { + var result = [] + for (var i = 0; i < numbers.length; i++) { + result.push(numbers[i] * 2) + } + return result +} + +module.exports = doubleAll +``` + +## Arguments + +* `numbers` : Un tableau d’entiers + +## Conditions + +* Votre solution doit utiliser `Array.prototype.map` +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map + +## Base de travail + +```js +function doubleAll(numbers) { + // VOTRE SOLUTION ICI +} + +module.exports = doubleAll +``` diff --git a/problems/basic_map/problem.md b/exercises/basic_map/problem.md similarity index 100% rename from problems/basic_map/problem.md rename to exercises/basic_map/problem.md diff --git a/problems/basic_map/solution.js b/exercises/basic_map/solution/solution.js similarity index 100% rename from problems/basic_map/solution.js rename to exercises/basic_map/solution/solution.js diff --git a/exercises/basic_recursion/exercise.js b/exercises/basic_recursion/exercise.js new file mode 100644 index 0000000..eb3524f --- /dev/null +++ b/exercises/basic_recursion/exercise.js @@ -0,0 +1,16 @@ +"use strict" + +var loremIpsum = require('lorem-ipsum') +var runner = require('../runner') + +var input = loremIpsum({count: 1, units:'paragraphs'}) + .replace(/([^\w ])/g, '')// remove non-words and spaces + .toLowerCase() // lowercase I guess + .split(' ') // create array of words + +module.exports = runner.custom(function(fx, input) { + return fx(input, function(prev, curr) { + prev[curr] = ++prev[curr] || 1 + return prev + }, {}) +})(input) diff --git a/exercises/basic_recursion/problem.fr.md b/exercises/basic_recursion/problem.fr.md new file mode 100644 index 0000000..b9ae85b --- /dev/null +++ b/exercises/basic_recursion/problem.fr.md @@ -0,0 +1,65 @@ +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. + +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. + +```js +function toUpperArray(items) { + if (!items.length) return [] // condition de fin + var head = items[0] // élément sur lequel travailler + head = head.toUpperCase() // exécution du travail + var tail = items.slice(1) // passage à l’étape suivante + return [head].concat(toUpperArray(tail)) // récursion terminale +} + +toUpperArray(['bonjour', 'monde']) // => ['BONJOUR', 'MONDE'] +``` + +Cet exercice vise à vous faire découvrir la récursivité en implémentant une fonction familière à l’aide d’une fonction récursive. + +# Défi + +Implémentez `Array#reduce()` de façon récursive. + +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. + +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. + +## Arguments + +* `arr` : Un tableau sur lequel appliquer la réduction +* `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. +* `init` : Valeur initiale de la réduction. Contrairement à `Array#reduce()`, cet argument est ici obligatoire (vous pouvez supposer qu’il sera toujours fourni). + +## Exemple + +```js +// Votre fonction `reduce()` devrait se comporter comme la méthode +// standard `Array#reduce()`, à ceci près qu’elle recevra le tableau +// à traiter en premier argument : + +reduce([1,2,3], function(prev, curr, index, arr) { + return prev + curr +}, 0) +// => 6 +``` + +## Conditions + +* N’utilisez pas de boucle (`for`, `while`…) +* N’utilisez aucune méthode de `Array`, du genre `Array#map()` ou `Array#reduce()` +* Ne créez aucune fonction superflue + +## Ressources + +* https://fr.wikipedia.org/wiki/R%C3%A9cursivit%C3%A9 +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce + +## Base de travail + +```js +function reduce(arr, fn, initial) { + // VOTRE SOLUTION ICI +} + +module.exports = reduce +``` diff --git a/problems/basic_recursion/problem.md b/exercises/basic_recursion/problem.md similarity index 100% rename from problems/basic_recursion/problem.md rename to exercises/basic_recursion/problem.md diff --git a/problems/basic_recursion/solution.js b/exercises/basic_recursion/solution/solution.js similarity index 100% rename from problems/basic_recursion/solution.js rename to exercises/basic_recursion/solution/solution.js diff --git a/exercises/basic_recursion/solution_fr/solution.js b/exercises/basic_recursion/solution_fr/solution.js new file mode 100644 index 0000000..6654748 --- /dev/null +++ b/exercises/basic_recursion/solution_fr/solution.js @@ -0,0 +1,11 @@ +function reduce(arr, fn, initial) { + return (function reduceOne(index, value) { + // condition de fin + if (index > arr.length - 1) return value + + // calculer les valeurs et les passer à l’étape suivante + return reduceOne(index + 1, fn(value, arr[index], index, arr)) + })(0, initial) // IIFE. Démarrer la récursion avec les valeurs de départ +} + +module.exports = reduce diff --git a/exercises/basic_reduce/exercise.js b/exercises/basic_reduce/exercise.js new file mode 100644 index 0000000..a0aeba7 --- /dev/null +++ b/exercises/basic_reduce/exercise.js @@ -0,0 +1,11 @@ +"use strict" + +var loremIpsum = require('lorem-ipsum') +var runner = require('../runner') + +var input = loremIpsum({ count: 1, units: 'paragraphs' }) + .replace(/([^\w ])/g, '')// remove non-words and spaces + .toLowerCase() // lowercase I guess + .split(' ') // create array of words + +module.exports = runner(input) diff --git a/exercises/basic_reduce/problem.fr.md b/exercises/basic_reduce/problem.fr.md new file mode 100644 index 0000000..ddab05f --- /dev/null +++ b/exercises/basic_reduce/problem.fr.md @@ -0,0 +1,42 @@ +# Défi + +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()`). + +## Exemple + +```js +var inputWords = ['Apple', 'Banana', 'Apple', 'Durian', 'Durian', 'Durian'] + +console.log(countWords(inputWords)) + +// => +// { +// Apple: 2, +// Banana: 1, +// Durian: 3 +// } +``` + +## Arguments + +* `inputWords` : un tableau de `String`s aléatoires. + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce +* https://en.wikipedia.org/wiki/Reduce_(higher-order_function) + +## Base de travail + +```js +function countWords(inputWords) { + // VOTRE SOLUTION ICI +} + +module.exports = countWords +``` diff --git a/problems/basic_reduce/problem.md b/exercises/basic_reduce/problem.md similarity index 100% rename from problems/basic_reduce/problem.md rename to exercises/basic_reduce/problem.md diff --git a/problems/basic_reduce/solution.js b/exercises/basic_reduce/solution/solution.js similarity index 100% rename from problems/basic_reduce/solution.js rename to exercises/basic_reduce/solution/solution.js diff --git a/exercises/basic_reduce/solution_fr/solution.js b/exercises/basic_reduce/solution_fr/solution.js new file mode 100644 index 0000000..c7f37f3 --- /dev/null +++ b/exercises/basic_reduce/solution_fr/solution.js @@ -0,0 +1,8 @@ +function countWords(arr) { + return arr.reduce(function(countMap, word) { + countMap[word] = ++countMap[word] || 1 // incrémenter ou initialiser à 1 + return countMap + }, {}) // le 2e argument de reduce initialise `countMap` à `{}` +} + +module.exports = countWords diff --git a/exercises/blocking_event_loop/exercise.js b/exercises/blocking_event_loop/exercise.js new file mode 100644 index 0000000..e8ddc9b --- /dev/null +++ b/exercises/blocking_event_loop/exercise.js @@ -0,0 +1,8 @@ +"use strict" + +"use strict" + +var path = require('path') +var runner = require('../runner') + +module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), { localized: true }) diff --git a/exercises/blocking_event_loop/problem.fr.md b/exercises/blocking_event_loop/problem.fr.md new file mode 100644 index 0000000..ed7dc66 --- /dev/null +++ b/exercises/blocking_event_loop/problem.fr.md @@ -0,0 +1,33 @@ +# Défi + +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. + +Un timeout sera déclenché après une seconde, qui affichera les résultats du test et terminara le processus. `repeat()` doit relâcher son contrôle sur la boucle événementielle de telle sorte que ce timeout puisse s’exécuter avant que 1500 millisecondes soient passées. + +Essayez d’exécuter l’opération passée à `repeat()` autant de fois que possible avant le timeout ! + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Conseils + +Si votre programme prend trop de temps à s’exécuter, vous avez probablement un souci. Utilisez Ctrl+C pour arrêter le processus Node. + +## Ressources + +* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Timers + +## Base de travail + +```js +function repeat(operation, num) { + // modifiez cette fonction pour la rendre interruptible + if (num <= 0) return + operation() + return repeat(operation, --num) +} + +module.exports = repeat +``` diff --git a/problems/blocking_event_loop/problem.md b/exercises/blocking_event_loop/problem.md similarity index 100% rename from problems/blocking_event_loop/problem.md rename to exercises/blocking_event_loop/problem.md diff --git a/problems/blocking_event_loop/solution.js b/exercises/blocking_event_loop/solution/solution.js similarity index 100% rename from problems/blocking_event_loop/solution.js rename to exercises/blocking_event_loop/solution/solution.js diff --git a/exercises/blocking_event_loop/solution_fr/solution.js b/exercises/blocking_event_loop/solution_fr/solution.js new file mode 100644 index 0000000..b02307a --- /dev/null +++ b/exercises/blocking_event_loop/solution_fr/solution.js @@ -0,0 +1,17 @@ +function repeat(operation, num) { + if (num <= 0) return + + operation() + + // relâcher le contrôle tous les 10 tours + // (10 est une taille de lot arbitraire). + if (num % 10 === 0) { + setTimeout(function() { + repeat(operation, --num) + }) + } else { + repeat(operation, --num) + } +} + +module.exports = repeat diff --git a/exercises/blocking_event_loop/wrapper.js b/exercises/blocking_event_loop/wrapper.js new file mode 100644 index 0000000..fdf6dce --- /dev/null +++ b/exercises/blocking_event_loop/wrapper.js @@ -0,0 +1,35 @@ +'use strict' + +var path = require('path') + +var repeat = require(path.resolve(process.cwd(), process.argv[2])) + +var count = 0 +var CYCLES = 100000 + +function operation() { + for (var i = 0; i < 1000000; i++) {} // burn some CPU cycles + count++ // count how many times this function was called +} + +console.log() +console.log('the operation:') +console.log(operation.toString()) +console.log() +console.log('Trying to repeat the operation %d times...', CYCLES) +console.log('Press control+c to kill.') +console.log() + +var start = Date.now() +repeat(operation, CYCLES) + +setTimeout(function() { + var end = Date.now() + console.error('Performed %d operations.', count) + if (count === CYCLES) console.log('Fail! Should not have completed all operations!') + // TODO calculate ideal ops per second? + //if (count < 1000) console.log('Fail! Should have performed more operations!') + if (end - start < 1500) console.log('Interrupted in approximately 1 second!') + else console.log('Fail! Interrupted in %d milliseconds', end - start) + process.exit() +}, 1000) diff --git a/exercises/blocking_event_loop/wrapper_fr.js b/exercises/blocking_event_loop/wrapper_fr.js new file mode 100644 index 0000000..e8a1c98 --- /dev/null +++ b/exercises/blocking_event_loop/wrapper_fr.js @@ -0,0 +1,33 @@ +'use strict' + +var path = require('path') + +var repeat = require(path.resolve(process.cwd(), process.argv[2])) + +var count = 0 +var CYCLES = 100000 + +function operation() { + for (var i = 0; i < 1000000; i++) {} // burn some CPU cycles + count++ // count how many times this function was called +} + +console.log() +console.log('l’opération :') +console.log(operation.toString()) +console.log() +console.log('J’essaie de répéter l’opération %d fois…', CYCLES) +console.log('Pressez Ctrl+C pour interrompre.') +console.log() + +var start = Date.now() +repeat(operation, CYCLES) + +setTimeout(function() { + var end = Date.now() + console.error('J’ai effectué %d opérations.', count) + if (count === CYCLES) console.log('Raté ! Je n’aurais pas du pouvoir aller au bout !') + if (end - start < 1500) console.log('Interruption au bout d’environ 1 seconde !') + else console.log('Raté ! Interruption au bout de %d millisecondes', end - start) + process.exit() +}, 1000) diff --git a/exercises/currying/exercise.js b/exercises/currying/exercise.js new file mode 100644 index 0000000..5efc6dc --- /dev/null +++ b/exercises/currying/exercise.js @@ -0,0 +1,36 @@ +/** + * Created by naor on 10/10/13. + * Migrated by tdd on 03/16/15. + */ +"use strict"; + +var runner = require('../runner') + +var exercise = module.exports = runner.custom(function(curryN) { + function add3(one, two, three) { + return one + two + three + } + // console.log("var curryC = curryN(add3)") + // console.log("var curryB = curryC(1)") + // console.log("var curryA = curryB(2))") + var curryC = curryN(add3) + var curryB = curryC(1) + var curryA = curryB(2) + + var result = [] + result.push("curryA(3) => " + curryA(3)) // => 6 + result.push("curryA(10) => " + curryA(10)) // => 13 + result.push("curryN(add3)(1)(2)(3) => " + curryN(add3)(1)(2)(3)) // => 6 + + function strConcat(){ + var args = Array.prototype.slice.call(arguments); + return Array.prototype.concat.apply([], args).join(" "); + } + + var words = exercise.__('five_words').split(',') + var call = words.map(function(word) { return "('" + word + "')" }).join('') + result.push( + "curryN(strConcat, 5)" + call + ") => " + + curryN(strConcat, 5)(words[0])(words[1])(words[2])(words[3])(words[4])) + return result +}).hideInput() diff --git a/exercises/currying/problem.fr.md b/exercises/currying/problem.fr.md new file mode 100644 index 0000000..55d33bf --- /dev/null +++ b/exercises/currying/problem.fr.md @@ -0,0 +1,74 @@ +Voici un exemple d’implémentation de `curry3()`, qui « curryfie » jusqu’à 3 arguments : + +```js +function curry3(fun){ + return function(three){ + return function(two){ + return function (one){ + return fun(one, two, three) + } + } + } +} +``` + +Si nous devions utiliser cette implémentation avec la fonction d’exemple suivante : + +```js +function abc(one, two, three) { + return one/two/three +} +``` + +Ça donnerait quelque chose comme ça : + +```js +var curryC = curry3(abc) +var curryB = curryC(2) +var curryA = curryB(3) + +console.log(curryA(6)) // => 1 +``` + +# Défi + +Dans cet exercice, nous allons implémenter la fonction `curry()` pour un nombre quelconque d’arguments. + +`curryN()` acceptera deux paramètres : + +* `fn` : la fonction à « curryfier ». +* `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). + +## Exemple + +```js +function add3(one, two, three) { + return one + two + three +} + +var curryC = curryN(add3) +var curryB = curryC(1) +var curryA = curryB(2) +console.log(curryA(3)) // => 6 +console.log(curryA(10)) // => 13 + +console.log(curryN(add3)(1)(2)(3)) // => 6 +``` + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` + +## Conseil + +On peut détecter l’arité d’une fonction avec la propriété `.length` de celle-ci. + +## Base de travail + +```js +function curryN(fn, n) { + // VOTRE SOLUTION ICI +} + +module.exports = curryN +``` diff --git a/problems/currying/problem.md b/exercises/currying/problem.md similarity index 100% rename from problems/currying/problem.md rename to exercises/currying/problem.md diff --git a/problems/currying/solution.js b/exercises/currying/solution/solution.js similarity index 100% rename from problems/currying/solution.js rename to exercises/currying/solution/solution.js diff --git a/exercises/currying/solution_fr/solution.js b/exercises/currying/solution_fr/solution.js new file mode 100644 index 0000000..3e6e46e --- /dev/null +++ b/exercises/currying/solution_fr/solution.js @@ -0,0 +1,26 @@ +function curryN(fn, n) { + // Si l’argument `n` est absent, on utilise la propriété `.length` de la fonction. + if (typeof n !== 'number') n = fn.length + + function getCurriedFn(prev) { + return function(arg) { + // Concatène l’argument qui vient d’être passé avec le tableau des arguments + // déjà spécifiés. + var args = prev.concat(arg) + // Si tous les arguments ne sont pas encore pré-remplis, renvoie + // la version currifiée de la fonction originale. + if (args.length < n) return getCurriedFn(args) + // Sinon, invoque la fonction d’origine avec les arguments, et + // renvoie sa valeur de retour. + else return fn.apply(this, args) + }; + } + + // Renvoie une version currifiée de la fonction d’origine. + return getCurriedFn([]) +} + +module.exports = curryN + +// Source de cette solution : +// http://benalman.com/news/2012/09/partial-application-in-javascript/#manually-specifying-function-arity diff --git a/exercises/function_call/exercise.js b/exercises/function_call/exercise.js new file mode 100644 index 0000000..431bbd5 --- /dev/null +++ b/exercises/function_call/exercise.js @@ -0,0 +1,21 @@ +"use strict" + +var loremIpsum = require('lorem-ipsum') +var runner = require('../runner') + +var words = loremIpsum({ count: 5, units:'words'}) + .replace(/([^\w ])/g, '')// remove non-words and spaces + .toLowerCase() // lowercase I guess + .split(' ') // create array of words + +module.exports = runner.custom(function(slice, words) { + var result = [] + words.forEach(function(word) { + result.push('word:', word); + result.push('slice(word):', slice(word)) + result.push('slice(word, 0, 1):', slice(word, 0, 1)) + result.push('slice(word, 2):', slice(word, 2)) + result.push('slice(word, -1):', slice(word, -1)) + }) + return result +})(words) diff --git a/exercises/function_call/problem.fr.md b/exercises/function_call/problem.fr.md new file mode 100644 index 0000000..e02ff34 --- /dev/null +++ b/exercises/function_call/problem.fr.md @@ -0,0 +1,69 @@ +## Défi + +Écrivez une fonction qui vous permet d’utiliser `Array.prototype.slice` sans avoir à utiliser + `.call` pour l’invoquer. + +En temps normal, vous devriez appeler `slice()` à l’aide de `call()` ou `apply()` : + +```js +var slice = Array.prototype.slice + +function() { + var args = slice.call(arguments) // ça marche +} +``` + +Nous, on veut que le code suivant marche : + +```js +var slice = yourFunction + +function() { + var args = slice(arguments) // ça marche +} +``` + +## Exemple + +Votre fonction `slice()` devrait avoir le comportement suivant : + +```js +var nums = [1,2,3,4,5] + +// Votre fonction slice doit correspondre au comportement +// de la fonction slice standard, à ceci près qu'elle reçoit +// le tableau comme premier argument. + +slice(nums, 0, 2) // [1, 2] +slice(nums, 1, 2) // [2] + +// Avec le slice standard, pour comparaison +nums.slice(0, 2) // [1, 2] +nums.slice(1, 2) // [2] +``` + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* N’utilisez pas le mot-clé `function` :D + +## Conseils + +* La solution fait une seule ligne. Même pas longue. +* Tout fonction JavaScript hérite de méthodes telles que `call()`, `apply()` et `bind()`, provenant de l’objet `Function.prototype`. +* `Function#call()` fonctionne quelle que soit la fonction sur laquelle on l’appelle (par exemple dans `someFunction.call()`, `this` à l’exécution sera `someFunction`). +* `Function#call()` est elle-même une fonction, et hérite donc de `Function.prototype` : + +```js +function myFunction() { + console.log("J’ai appelé ma fonction") +} + +Function.prototype.call.call(myFunction) // => "J’ai appelé ma fonction" +``` + +## Base de travail + +```js +module.exports = // VOTRE SOLUTION ICI ! +``` diff --git a/problems/function_call/problem.md b/exercises/function_call/problem.md similarity index 100% rename from problems/function_call/problem.md rename to exercises/function_call/problem.md diff --git a/problems/function_call/solution.js b/exercises/function_call/solution/solution.js similarity index 100% rename from problems/function_call/solution.js rename to exercises/function_call/solution/solution.js diff --git a/exercises/function_call/solution_fr/solution.js b/exercises/function_call/solution_fr/solution.js new file mode 100644 index 0000000..b16d548 --- /dev/null +++ b/exercises/function_call/solution_fr/solution.js @@ -0,0 +1,14 @@ +// Explication : +// +// La valeur de `this` dans `Function#call()` est la fonction +// sur laquelle on appelle `call()`. +// +// `bind()` renvoie une nouvelle fonction dont la valeur de +// `this` est définie à ce qu’on lui a passé comme premier argument. +// +// Toute fonction « hérite » `Function.prototype`, et donc toute fonction, +// y compris `call()`, `apply()` et `bind()`, a les méthodes `apply()` +// et `bind()`. +// +// Function.prototype.call === Function.call +module.exports = Function.call.bind(Array.prototype.slice) diff --git a/exercises/function_spies/exercise.js b/exercises/function_spies/exercise.js new file mode 100644 index 0000000..4d8bda2 --- /dev/null +++ b/exercises/function_spies/exercise.js @@ -0,0 +1,38 @@ +"use strict" + +var deepEqual = require('deep-eql') +var random = require('../randomizer') +var runner = require('../runner') +var loremIpsum = require('lorem-ipsum') +var util = require('util') + +var input = random.arrayOf(20, function() { return loremIpsum().split(' ') }) + +var exercise = module.exports = runner.custom(function(Spy, input) { + var count = 0, slice = Array.prototype.slice + var parent = { + test: function() { + if (!deepEqual(slice.call(arguments), input[count])) { + exercise.emit('fail', exercise.__('not_all_args')) + } + if (this !== parent) { + exercise.emit('fail', exercise.__('incorrect_this')) + } + return arguments + } + } + var originalFn = parent.test.bind(parent) + var spy = Spy(parent, 'test') + + var result = [] + input.forEach(function(args, i) { + result.push(util.format.apply(util, args)) + count = i + if (!deepEqual(originalFn.apply(parent, args), parent.test.apply(parent, args))) { + exercise.emit('fail', exercise.__('incorrect_return')) + } + }) + + result.push(exercise.__('call_times', spy.count)) + return result +}).quiet(input) diff --git a/exercises/function_spies/problem.fr.md b/exercises/function_spies/problem.fr.md new file mode 100644 index 0000000..79235b8 --- /dev/null +++ b/exercises/function_spies/problem.fr.md @@ -0,0 +1,39 @@ +# Défi + +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. + +## Exemple + +```js +var spy = Spy(console, 'error') + +console.error('appel de console.error') +console.error('appel de console.error') +console.error('appel de console.error') + +console.log(spy.count) // 3 +``` + +## Arguments + +* `target` : un objet contenant la méthode `method`. +* `method` : une `String` indiquant le nom de la méthode de `target` à espionner. + +## Conditions + +* N’utilisez ni boucle (`for`, `while`…) ni `Array.prototype.forEach` +* Ne créez aucune fonction superflue + +## Conseils + +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. + +## Base de travail + +```js +function Spy(target, method) { + // VOTRE SOLUTION ICI +} + +module.exports = Spy +``` diff --git a/problems/function_spies/problem.md b/exercises/function_spies/problem.md similarity index 100% rename from problems/function_spies/problem.md rename to exercises/function_spies/problem.md diff --git a/problems/function_spies/solution.js b/exercises/function_spies/solution/solution.js similarity index 100% rename from problems/function_spies/solution.js rename to exercises/function_spies/solution/solution.js diff --git a/exercises/function_spies/solution_fr/solution.js b/exercises/function_spies/solution_fr/solution.js new file mode 100644 index 0000000..3a6f0af --- /dev/null +++ b/exercises/function_spies/solution_fr/solution.js @@ -0,0 +1,22 @@ +function Spy(target, method) { + var originalFunction = target[method] + + // Utilisons un objet pour pouvoir le renvoyer par référence, + // et non par copie de valeur. Ainsi nous pouvons renvoyer + // `result`, mais mettre à jour `count` depuis cette portée. + var result = { + count: 0 + } + + // remplaçons la méthode par sa version « espion » + target[method] = function() { + // On comptabilise l’appel + result.count++ + // On invoque la version d’origine + return originalFunction.apply(this, arguments) + } + + return result +} + +module.exports = Spy diff --git a/exercises/hello_world/exercise.js b/exercises/hello_world/exercise.js new file mode 100644 index 0000000..4486886 --- /dev/null +++ b/exercises/hello_world/exercise.js @@ -0,0 +1,5 @@ +"use strict" + +var runner = require('../runner') + +module.exports = runner(require('lorem-ipsum')()) diff --git a/exercises/hello_world/problem.fr.md b/exercises/hello_world/problem.fr.md new file mode 100644 index 0000000..40591d4 --- /dev/null +++ b/exercises/hello_world/problem.fr.md @@ -0,0 +1,17 @@ +## Défi + +Écrivez une fonction qui prend une `String` en entrée et la renvoie en majuscules. + +## Arguments + +* `input` : une `String` contenant un texte aléatoire (type lorem ipsum). + +## Base de travail + +```js +function upperCaser(input) { + // VOTRE SOLUTION ICI +} + +module.exports = upperCaser +``` diff --git a/problems/hello_world/problem.md b/exercises/hello_world/problem.md similarity index 100% rename from problems/hello_world/problem.md rename to exercises/hello_world/problem.md diff --git a/problems/hello_world/solution.js b/exercises/hello_world/solution/solution.js similarity index 100% rename from problems/hello_world/solution.js rename to exercises/hello_world/solution/solution.js diff --git a/exercises/higher_order_functions/exercise.js b/exercises/higher_order_functions/exercise.js new file mode 100644 index 0000000..02924bb --- /dev/null +++ b/exercises/higher_order_functions/exercise.js @@ -0,0 +1,13 @@ +"use strict" + +var random = require('../randomizer') +var runner = require('../runner') + +var counter + +var exercise = module.exports = runner.init(function() { + console.log("------------------------") + counter = 0 +}).quiet(function count() { + console.log(exercise.__('call_log', ++counter)) +}, random.int(3, 10)) diff --git a/exercises/higher_order_functions/problem.fr.md b/exercises/higher_order_functions/problem.fr.md new file mode 100644 index 0000000..2333733 --- /dev/null +++ b/exercises/higher_order_functions/problem.fr.md @@ -0,0 +1,48 @@ +Une fonction d’ordre supérieur est une fonction qui fait au moins l’une des deux choses suivantes : + +* Prendre une ou plusieurs fonctions comme arguments +* Renvoyer une fonction + +Toutes les autres fonctions sont des fonctions de premier ordre. + +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. + +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. + +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. + +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. + +## Défi + +É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. + +Utilisez le code de base fourni ci-dessous pour démarrer. La majorité des exercices de cet atelier vous fourniront une base de travail. + +## Arguments + +* `operation` : Une `Function`, qui ne prend pas d’arguments et ne renvoie rien d’utile. +* `num` : le nombre de fois qu’il vous faudra appeler `operation` + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/prototype + +## Conseils + +* La solution est vraiment simple. +* Vous avez le droit d’utiliser une boucle ; points bonus si vous préférez la récursivité. +* Vous verrez sans doute un affichage à l’exécution. Il viendra de la fonction qui vous est passée. +* Vous n’avez pas besoin de faire de `console.log(…)` vous-même. + +## Base de travail + +```js +function repeat(operation, num) { + // VOTRE SOLUTION ICI +} + +// Ne retirez pas la ligne ci-dessous +module.exports = repeat +``` diff --git a/problems/higher_order_functions/problem.md b/exercises/higher_order_functions/problem.md similarity index 100% rename from problems/higher_order_functions/problem.md rename to exercises/higher_order_functions/problem.md diff --git a/problems/higher_order_functions/solution.js b/exercises/higher_order_functions/solution/solution.js similarity index 100% rename from problems/higher_order_functions/solution.js rename to exercises/higher_order_functions/solution/solution.js diff --git a/exercises/implement_map_with_reduce/exercise.js b/exercises/implement_map_with_reduce/exercise.js new file mode 100644 index 0000000..4e4a277 --- /dev/null +++ b/exercises/implement_map_with_reduce/exercise.js @@ -0,0 +1,13 @@ +"use strict" + +var runner = require('../runner') + +function randomInt(min, max) { + return Math.floor((Math.random() * (max - min)) + min) +} + +var numbers = Array.apply(null, {length: Math.random() * 20 + 1}).map(function() { + return randomInt(0, 9) +}) + +module.exports = runner(numbers, function(item) { return item * 3 }) diff --git a/exercises/implement_map_with_reduce/problem.fr.md b/exercises/implement_map_with_reduce/problem.fr.md new file mode 100644 index 0000000..05e1490 --- /dev/null +++ b/exercises/implement_map_with_reduce/problem.fr.md @@ -0,0 +1,36 @@ +# Défi + +Utilisez `Array#reduce()` pour implémenter une version simple de `Array#map()`. + +## Résultat attendu + +Une fonction `map()` applique une fonction quelconque à chaque élément d’un tableau, et renvoie le tableau des résultats obtenus. + +```js +var nums = [1,2,3,4,5] + +// `map` est la fonction que vous exportez +var output = map(nums, function double(item) { + return item * 2 +}) + +console.log(output) // => [2,4,6,8,10] +``` + +## Arguments + +* `input` : un tableau de données quelconques, de types variés. +* `operation` : une fonction quelconque, à appliquer aux éléments de `input`. + +## Ressources + +* https://en.wikipedia.org/wiki/Reduce_(higher-order_function) +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce + +## Base de travail + +```js +module.exports = function arrayMap(arr, fn) { + // VOTRE SOLUTION ICI +} +``` diff --git a/problems/implement_map_with_reduce/problem.md b/exercises/implement_map_with_reduce/problem.md similarity index 100% rename from problems/implement_map_with_reduce/problem.md rename to exercises/implement_map_with_reduce/problem.md diff --git a/problems/implement_map_with_reduce/solution.js b/exercises/implement_map_with_reduce/solution/solution.js similarity index 100% rename from problems/implement_map_with_reduce/solution.js rename to exercises/implement_map_with_reduce/solution/solution.js diff --git a/menu.json b/exercises/menu.json similarity index 100% rename from menu.json rename to exercises/menu.json diff --git a/exercises/partial_application_with_bind/exercise.js b/exercises/partial_application_with_bind/exercise.js new file mode 100644 index 0000000..99d919a --- /dev/null +++ b/exercises/partial_application_with_bind/exercise.js @@ -0,0 +1,9 @@ +"use strict" + +var path = require('path') +var random = require('../randomizer') +var runner = require('../runner') + +var input = random.arrayOfLorems(1, 20) + +module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), input) diff --git a/exercises/partial_application_with_bind/problem.fr.md b/exercises/partial_application_with_bind/problem.fr.md new file mode 100644 index 0000000..36d23de --- /dev/null +++ b/exercises/partial_application_with_bind/problem.fr.md @@ -0,0 +1,41 @@ +# Défi + +**Utilisez `Function#bind()`** pour implémenter une fonction de log qui vous permet de préfixer vos messages. + +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`. + +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. + +**Affichez le résultat directement sur la console.** + +## Arguments + +* `namespace` : une `String` qui préfixe les messages passés à la fonction qui sera retournée. + +## Exemple + +```js +var info = logger('INFO :') +info('ceci est un message d’information') +// INFO : ceci est un message d’information + +var warn = logger('WARN :') +warn('ceci est un avertissement', 'avec du rab') +// WARN : ceci est un avertissement avec du rab +``` + +## Conditions + +* Utilisez `Function#bind()` + +## Base de travail + +```js +module.exports = function logger(namespace) { + // VOTRE SOLUTION ICI +} +``` + +## Resources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/bind diff --git a/problems/partial_application_with_bind/problem.md b/exercises/partial_application_with_bind/problem.md similarity index 100% rename from problems/partial_application_with_bind/problem.md rename to exercises/partial_application_with_bind/problem.md diff --git a/problems/partial_application_with_bind/solution.js b/exercises/partial_application_with_bind/solution/solution.js similarity index 100% rename from problems/partial_application_with_bind/solution.js rename to exercises/partial_application_with_bind/solution/solution.js diff --git a/exercises/partial_application_with_bind/wrapper.js b/exercises/partial_application_with_bind/wrapper.js new file mode 100644 index 0000000..9363802 --- /dev/null +++ b/exercises/partial_application_with_bind/wrapper.js @@ -0,0 +1,14 @@ +'use strict' + +var fs = require('fs') +var path = require('path') + +var fx = require(path.resolve(process.cwd(), process.argv[2])) +var input = JSON.parse(fs.readFileSync(process.argv[3], { encoding: 'utf-8' }))[0] + +var info = fx('INFO:') +var warn = fx('WARN:') +input.forEach(function(message, i) { + if (i % 2 === 0) info.apply(null, message.split(' ')) + else warn.apply(null, message.split(' ')) +}) diff --git a/exercises/partial_application_without_bind/exercise.js b/exercises/partial_application_without_bind/exercise.js new file mode 100644 index 0000000..99d919a --- /dev/null +++ b/exercises/partial_application_without_bind/exercise.js @@ -0,0 +1,9 @@ +"use strict" + +var path = require('path') +var random = require('../randomizer') +var runner = require('../runner') + +var input = random.arrayOfLorems(1, 20) + +module.exports = runner.wrapWith(path.join(__dirname, 'wrapper.js'), input) diff --git a/exercises/partial_application_without_bind/problem.fr.md b/exercises/partial_application_without_bind/problem.fr.md new file mode 100644 index 0000000..81348ce --- /dev/null +++ b/exercises/partial_application_without_bind/problem.fr.md @@ -0,0 +1,109 @@ +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. + +Plus formellement : l’application partielle désigne le pré-remplissage d’arguments d’une fonction pour produire une fonction de moindre arité. + +À titre d’exemple, imaginons que nous avons une fonction `add()` qui accepte deux arguments et renvoie leur somme : + +```js +function add(x, y) { + return x + y +} + +add(10, 20) // => 30 +``` + +À 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). + +Dans le code qui suit, nous pré-remplissons le premier argument de notre fonction `add()`, à savoir `x` : + +```js +var addTen = partiallyApply(add, 10) // pré-remplit `x` à 10 +``` + +`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 ! + +Une fois que nous passons l’argument pour `y`, la fonction `add()` originale peut être appelée : + +```js +addTen(20) // => 30 +addTen(100) // => 110 +addTen(0) // => 10 +// etc. +``` + +Tous les exemples ci-dessus reviennent à appeler `add(10, y)`, ou `y` serait fourni dans l’appel à la bien-nommée `addTen()`. + +# Défi + +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. + +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`. + +Vous aurez besoin de `Function#apply()` pour implémenter l’application partielle. + +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. + +**Affichez le résultat directement sur la console.** + +## Arguments + +* `namespace` : une `String` qui préfixe les messages passés à la fonction qui sera retournée. + +## Exemple + +```js +var info = logger('INFO :') +info('ceci est un message d’information') +// INFO : ceci est un message d’information + +var warn = logger('WARN :') +warn('ceci est un avertissement', 'avec du rab') +// WARN : ceci est un avertissement avec du rab +``` + +## Conditions + +* N’utilisez pas `Function#bind()` +* Utilisez `Function#apply()` + +## Base de travail + +```js +var slice = Array.prototype.slice + +function logger(namespace) { + // VOTRE SOLUTION ICI +} + +module.exports = logger +``` + +## Ressources + +* https://en.wikipedia.org/wiki/Partial_application +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Function/apply + +## Conseils + +Gardez à l’esprit que `console.log()` accepte un nombre quelconque d’arguments et les affiche, séparés par des espaces : + +```js +console.log('bonjour', 'monde') // => 'bonjour monde' +console.log(1, 2, 3) // => 1 2 3 +``` + +Nous voulons simplement pré-remplir le premier argument de `console.log()`. + +`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** : + +```js +add(10, 20) // => 30 +add.apply(null, [10, 20]) // => 30 +``` + +Faites bien la différence entre `apply()` et `call()` : + +```js +add.apply(null, [10, 20]) // => 30 +add.call(null, 10, 20) // => 30 +``` diff --git a/problems/partial_application_without_bind/problem.md b/exercises/partial_application_without_bind/problem.md similarity index 100% rename from problems/partial_application_without_bind/problem.md rename to exercises/partial_application_without_bind/problem.md diff --git a/problems/partial_application_without_bind/solution.js b/exercises/partial_application_without_bind/solution/solution.js similarity index 100% rename from problems/partial_application_without_bind/solution.js rename to exercises/partial_application_without_bind/solution/solution.js diff --git a/exercises/partial_application_without_bind/wrapper.js b/exercises/partial_application_without_bind/wrapper.js new file mode 100644 index 0000000..13c7092 --- /dev/null +++ b/exercises/partial_application_without_bind/wrapper.js @@ -0,0 +1,18 @@ +'use strict' + +var fs = require('fs') +var path = require('path') + +var fx = require(path.resolve(process.cwd(), process.argv[2])) +var input = JSON.parse(fs.readFileSync(process.argv[3], { encoding: 'utf-8' }))[0] + +console.log.bind = function() { + throw new Error('Try implementing this without bind!') +} + +var info = fx('INFO:') +var warn = fx('WARN:') +input.forEach(function(message, i) { + if (i % 2 === 0) info.apply(null, message.split(' ')) + else warn.apply(null, message.split(' ')) +}) diff --git a/problems/prototypical_inheritance_by_hand/setup.js b/exercises/prototypical_inheritance_by_hand/exercise.js similarity index 100% rename from problems/prototypical_inheritance_by_hand/setup.js rename to exercises/prototypical_inheritance_by_hand/exercise.js diff --git a/problems/prototypical_inheritance_by_hand/problem.md b/exercises/prototypical_inheritance_by_hand/problem.md similarity index 100% rename from problems/prototypical_inheritance_by_hand/problem.md rename to exercises/prototypical_inheritance_by_hand/problem.md diff --git a/problems/prototypical_inheritance_by_hand/solution.js b/exercises/prototypical_inheritance_by_hand/solution/solution.js similarity index 100% rename from problems/prototypical_inheritance_by_hand/solution.js rename to exercises/prototypical_inheritance_by_hand/solution/solution.js diff --git a/exercises/randomizer.js b/exercises/randomizer.js new file mode 100644 index 0000000..28e53be --- /dev/null +++ b/exercises/randomizer.js @@ -0,0 +1,49 @@ +"use strict" + +var loremIpsum = require('lorem-ipsum') + +function randomArrayOf(minCells, maxCells, callback) { + if ('function' === typeof maxCells && 'undefined' === typeof callback) { + callback = maxCells + maxCells = minCells + } + return Array.apply(null, { length: randomInt(minCells, maxCells) }).map(callback) +} + +function randomArrayOfInts(maxCells, minInt, maxInt) { + return randomArrayOf(0, maxCells, function() { + return randomInt(minInt, maxInt) + }) +} + +function randomArrayOfLorems(minCells, maxCells) { + var loremOptions = Array.prototype.slice(arguments, 2) + return randomArrayOf(minCells, maxCells, function() { + return loremIpsum.apply(loremIpsum, loremOptions) + }) +} + +function randomInt(min, max) { + if (min === max) return max + return Math.floor((Math.random() * (max - min + 1)) + min) +} + +function randomWords(count, options) { + options = options || {} + var result = loremIpsum().split(' ').slice(0, count) + if (options.capitalized) { + result = result.map(function(word) { + word[0] = word[0].toUpperCase() + return word + }) + } + return result.join(' ') +} + +module.exports = { + int: randomInt, + arrayOf: randomArrayOf, + arrayOfInts: randomArrayOfInts, + arrayOfLorems: randomArrayOfLorems, + words: randomWords +} diff --git a/problems/recursion/setup.js b/exercises/recursion/exercise.js similarity index 78% rename from problems/recursion/setup.js rename to exercises/recursion/exercise.js index c5299e9..0e00537 100644 --- a/problems/recursion/setup.js +++ b/exercises/recursion/exercise.js @@ -1,10 +1,7 @@ "use strict" -var input = require('../../input') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} +var random = require('../randomizer') +var runner = require('../runner') var data = { "name": "functional-javascript-workshop", @@ -105,20 +102,23 @@ var data = { } function getRandomSubTree() { - return randomInt(0, Object.keys(data.dependencies.workshopper.dependencies).length) + return random.int(0, Object.keys(data.dependencies.workshopper.dependencies).length) } -module.exports = input(data, [getRandomSubTree(), getRandomSubTree(), getRandomSubTree(), getRandomSubTree()]).wrap(function(input, getDependencies) { - var data = input[0] - var items = input[1] +var items = [getRandomSubTree(), getRandomSubTree(), getRandomSubTree(), getRandomSubTree()] + +module.exports = runner.custom(function(getDependencies) { var dependencyTree1 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[0]]] var dependencyTree2 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[1]]] var dependencyTree3 = data.dependencies.workshopper.dependencies[Object.keys(data.dependencies.workshopper.dependencies)[items[2]]] var dependencyTree4 = data.dependencies[Object.keys(data.dependencies)[1]] var fullTree = data - console.log("getDependencies(dependencyTree1)", getDependencies(dependencyTree1)) - console.log("getDependencies(dependencyTree2)", getDependencies(dependencyTree2)) - console.log("getDependencies(dependencyTree3)", getDependencies(dependencyTree3)) - console.log("getDependencies(dependencyTree4)", getDependencies(dependencyTree4)) - console.log("getDependencies(fullTree)", getDependencies(fullTree)) -}) + + var result = [] + result.push("getDependencies(dependencyTree1)", getDependencies(dependencyTree1)) + result.push("getDependencies(dependencyTree2)", getDependencies(dependencyTree2)) + result.push("getDependencies(dependencyTree3)", getDependencies(dependencyTree3)) + result.push("getDependencies(dependencyTree4)", getDependencies(dependencyTree4)) + result.push("getDependencies(fullTree)", getDependencies(fullTree)) + return result +}).hideInput(data) diff --git a/exercises/recursion/problem.fr.md b/exercises/recursion/problem.fr.md new file mode 100644 index 0000000..11819a6 --- /dev/null +++ b/exercises/recursion/problem.fr.md @@ -0,0 +1,55 @@ +## Défi + +É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`. + +On autorise des versions multiples d’un même module, mais les doublons (versions identiques) doivent être retirés. + +## Arguments + +* `tree` : Une arborescence de dépendances. Voir ci-dessous pour sa structure. + +## Exemple + +```js +var loremIpsum = { + "name": "lorem-ipsum", + "version": "0.1.1", + "dependencies": { + "optimist": { + "version": "0.3.7", + "dependencies": { + "wordwrap": { + "version": "0.0.2" + } + } + }, + "inflection": { + "version": "1.2.6" + } + } +} + +getDependencies(loremIpsum) // => [ 'inflection@1.2.6', 'optimist@0.3.7', 'wordwrap@0.0.2' ] + +``` + +## Conditions + +* N’utilisez pas de boucle `for`/`while`. + +## Base de travail + +```js +function getDependencies(tree) { + // VOTRE SOLUTION ICI + // Note : n’hésitez pas à ajouter des arguments dont vous auriez besoin dans + // vos appels récursifs. Mais ça n’a rien d’obligatoire ! Il y a bien des + // manières de faire de la récursivité. +} + +module.exports = getDependencies +``` + +## Ressources + +* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/keys diff --git a/problems/recursion/problem.md b/exercises/recursion/problem.md similarity index 100% rename from problems/recursion/problem.md rename to exercises/recursion/problem.md diff --git a/problems/recursion/solution.js b/exercises/recursion/solution/solution.js similarity index 86% rename from problems/recursion/solution.js rename to exercises/recursion/solution/solution.js index 816192f..e6f7dfd 100644 --- a/problems/recursion/solution.js +++ b/exercises/recursion/solution/solution.js @@ -1,6 +1,6 @@ function getDependencies(mod, result) { result = result || [] - var dependencies = mod.dependencies || [] + var dependencies = mod && mod.dependencies || [] Object.keys(dependencies).forEach(function(dep) { var key = dep + '@' + mod.dependencies[dep].version if (result.indexOf(key) === -1) result.push(key) diff --git a/exercises/runner.js b/exercises/runner.js new file mode 100644 index 0000000..3222558 --- /dev/null +++ b/exercises/runner.js @@ -0,0 +1,137 @@ +var compareStdOut = require('workshopper-exercise/comparestdout') +var deepEqual = require('deep-eql') +var execute = require('workshopper-exercise/execute') +var exerciser = require('workshopper-exercise') +var filecheck = require('workshopper-exercise/filecheck') +var fs = require('fs') +var os = require('os') +var path = require('path') +var util = require('util') + +var verbose = true, showInput = true, initFx, wrapUpFx, customFx, wrapperModule + +function runner() { + var exercise = execute(filecheck(exerciser())) + var input = Array.prototype.slice.call(arguments) + var submittedFx, __ + + exercise.addProcessor(function(mode, callback) { + __ = exercise.__.bind(exercise) + try { + submittedFx = require(path.resolve(process.cwd(), this.args[0])); + } catch (e) { + var message = (e.code === 'MODULE_NOT_FOUND' + ? __('fail.module_not_found') + : __('fail.missing_deps')) + + this.emit('fail', message) + return callback(null, false) + } + + if (typeof submittedFx !== 'function') { + this.emit('fail', __('fail.must_export_function')) + return callback(null, false) + } + + callback(null, true) + }); + + if (wrapperModule && wrapperModule.path) { + exercise.addSetup(function setupWrapperModule(mode, callback) { + var modulePath = wrapperModule.path + if (wrapperModule.options && wrapperModule.options.localized) { + var localizedPath = modulePath.replace(/\.\w+$/, '_' + exercise.lang + '$&') + if (fs.existsSync(path.resolve(process.cwd(), localizedPath))) { + modulePath = localizedPath + } + } + this.solutionCommand = [ modulePath, this.solution ].concat(this.solutionArgs) + this.submissionCommand = [ modulePath, this.submission ].concat(this.submissionArgs) + + if (input.length > 0) { + var file = path.join(os.tmpdir(), path.basename(this.solution)) + '.input.json' + fs.writeFileSync(file, JSON.stringify(input), { encoding: 'utf-8' }) + exercise.addCleanup(function(mode, pass, callback) { + fs.unlink(file, callback) + }) + this.solutionCommand.splice(2, 0, file) + this.submissionCommand.splice(2, 0, file) + } + process.nextTick(callback) + }) + + return compareStdOut(exercise) + } + + exercise.addProcessor(function(mode, callback) { + if (initFx) { initFx(); } + var submittedResult = obtainResult(submittedFx, input) + if (verbose) { + if (showInput) { + var displayInput = input.length === 1 ? input[0] : + input.map(function(o) { return 'function' === typeof o ? o.toString() : o }) + console.log(__('input'), util.inspect(displayInput, { colors: true }).replace(/,\n\s*/g, ", ")) + } + console.log(__('submission'), util.inspect(submittedResult, { colors: true }).replace(/,\n\s*/g, ", ")) + } + + if ('run' === mode) { + return callback(null, true) + } + + if (initFx) { initFx(); } + var solutionFx = require(this.solution) + var solutionResult = obtainResult(solutionFx, input) + if (verbose) { + console.log(__('solution'), util.inspect(solutionResult, { colors: true }).replace(/,\n\s*/g, ", ")) + } + callback(null, deepEqual(submittedResult, solutionResult)) + }) + + if (wrapUpFx) { + exercise.addVerifyProcessor(wrapUpFx) + } + + return exercise +} + +function obtainResult(fx, input) { + if (customFx) { + input = [fx].concat(input) + return customFx.apply(null, input) + } + return fx.apply(null, input) +} + +runner.custom = function custom(fx) { + customFx = fx + return runner +} + +runner.hideInput = function quiet() { + showInput = false + return runner.apply(null, arguments) +} + +runner.init = function init(fx) { + initFx = fx + return runner +} + +runner.quiet = function quiet() { + verbose = false + return runner.apply(null, arguments) +} + +runner.wrapWith = function wrapWith(modulePath, options) { + verbose = false + wrapperModule = { path: modulePath, options: options } + return runner.apply(null, Array.prototype.slice.call(arguments, 1)) +} + +runner.wrapUp = function wrapUp(fx) { + wrapUpFx = fx + return runner +} + +module.exports = runner diff --git a/exercises/trampoline/exercise.js b/exercises/trampoline/exercise.js new file mode 100644 index 0000000..307510d --- /dev/null +++ b/exercises/trampoline/exercise.js @@ -0,0 +1,13 @@ +"use strict" + +var runner = require('../runner') + +var exercise = module.exports = runner.custom(function(repeat) { + var COUNT = 100000 + console.log(exercise.__('intro', COUNT)) + var count = 0 + repeat(function() { + count++ + }, COUNT) + console.log(exercise.__('result', count)) +}).quiet() diff --git a/exercises/trampoline/problem.fr.md b/exercises/trampoline/problem.fr.md new file mode 100644 index 0000000..ed0a08f --- /dev/null +++ b/exercises/trampoline/problem.fr.md @@ -0,0 +1,59 @@ +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 : + +```js +var count = 0 +repeat(function() { + count++ +}, 100) + +console.log('exécuté %d fois.', count) +// => exécuté 100 fois. +``` + +MAIS vous remarquerez qu’exécuter `repeat()` avec un nombre trop grand de répétitions causera un débordement de pile (*stack overflow*) : + +``` +var count = 0 +repeat(function() { + count++ +}, 100000) + +console.log('exécuté %d fois', count) +// => RangeError: Maximum call stack size exceeded +``` + +# Défi + +Modifiez la base de travail ci-dessous pour qu’elle utilise un trampoline afin que la fonction s’appelle elle-même continuellement. + +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. + +## Conditions + +Vos modifications de l’implémentation de `repeat()` ne doivent pas utiliser de boucles ou d’itérateurs (ex. `forEach()`). + +## Conseils + +- Modifiez `repeat()` pour qu’elle envoie la « prochaine étape », s’il y en a une. +- 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 ! +- Si votre programme prend trop de temps à s’exécuter, vous avez probablement un souci. Utilisez Ctrl+C pour arrêter le processus Node. + +## Base de travail + +```js +function repeat(operation, num) { + // Modifiez ce code pour qu’il ne cause pas de débordement de pile ! + if (num <= 0) return + operation() + return repeat(operation, --num) +} + +function trampoline(fn) { + // Voilà où implémenter votre trampoline ; une boucle est autorisée ici. +} + +module.exports = function(operation, num) { + // Et voilà où vous voudrez sans doute plutôt appeler votre trampoline ! + return repeat(operation, num) +} +``` diff --git a/problems/trampoline/problem.md b/exercises/trampoline/problem.md similarity index 100% rename from problems/trampoline/problem.md rename to exercises/trampoline/problem.md diff --git a/problems/trampoline/solution.js b/exercises/trampoline/solution/solution.js similarity index 100% rename from problems/trampoline/solution.js rename to exercises/trampoline/solution/solution.js diff --git a/functional-javascript.js b/functional-javascript.js index 7efccf2..df516cb 100755 --- a/functional-javascript.js +++ b/functional-javascript.js @@ -7,6 +7,6 @@ var path = require('path') Workshopper({ name : 'functional-javascript' - , title : 'FUNCTIONAL JAVASCRIPT IS GOOD' - , appDir : path.join(__dirname) -}).init() + , appDir : __dirname + , languages : ['en', 'fr'] +}) diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..6e1f8ee --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,49 @@ +{ + "title": "FUNCTIONAL JAVASCRIPT IS GOOD", + "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin", + "common": { + "exercise": { + "fail": { + "missing_deps": "You need to install all of the dependencies you are using in your solution (e.g. lodash)", + "module_not_found": "Could not find your file. Make sure the path is correct.", + "must_export_function": "You should always return a function using the module.exports object." + }, + "input": "input: %s", + "submission": "submission: %s", + "solution": "solution: %s" + } + }, + "exercises": { + "Basic: Map": { + "didnt_use_map": "You did not use Array#map", + "used_map": "Yay! You used Array#map" + }, + "Basic: Every Some": { + "found_good_lists": "found %d good lists!" + }, + "Basic: Call": { + "matched_objects": "Matched %d of %d valid objects from %d total." + }, + "Higher Order Functions": { + "call_log": "Called function %d times." + }, + "Function Spies": { + "call_times": "Method called %d times.", + "incorrect_return": "Check your function's return value!", + "incorrect_this": "Check the function's this! Hint: Function#apply", + "not_all_args": "Check you are passing ALL the arguments! Hint: Function#apply" + }, + "Trampoline": { + "intro": "repeating %d times", + "result": "Successfully executed %d times." + }, + "Async Loops": { + "all_loaded": "All %d users loaded!", + "bad_result": "expected: \n%s\n but got:\n%s", + "took_too_long": "Took too long!" + }, + "Currying": { + "five_words": "This,problem,has,been,solved" + } + } +} diff --git a/i18n/fr.json b/i18n/fr.json new file mode 100644 index 0000000..1c9d769 --- /dev/null +++ b/i18n/fr.json @@ -0,0 +1,69 @@ +{ + "title": "LE JAVASCRIPT FONCTIONNEL C’EST LE BIEN", + "subtitle" : "\u001b[23mSélectionnez un exercice et tapez \u001b[3mEnter\u001b[23m pour démarrer", + "common": { + "exercise": { + "fail": { + "missing_deps": "Vous avez besoin d’installer toutes les dépendances que vous utilisez dans votre solution (ex. lodash)", + "module_not_found": "Impossible de trouver votre fichier. Assurez-vous que le chemin d’accès au fichier est correct.", + "must_export_function": "Vous devriez toujours renvoyer une fonction au travers de `module.exports`." + }, + "input": "entrée: %s", + "submission": "votre résultat : %s", + "solution": "résultat attendu : %s" + } + }, + "exercise": { + "Hello World": "Bonjour Monde", + "Higher Order Functions": "Fonctions d’Ordre Supérieur", + "Basic: Map": "Les bases : Map", + "Basic: Filter": "Les bases : Filter", + "Basic: Every Some": "Les bases : Every, Some", + "Basic: Reduce": "Les bases : Reduce", + "Basic: Recursion": "Les bases : Récursivité", + "Basic: Call": "Les bases : Call", + "Partial Application without Bind": "Application Partielle sans Bind", + "Partial Application with Bind": "Application Partielle avec Bind", + "Implement Map with Reduce": "Implémenter Map à l’aide de Reduce", + "Function Spies": "Espions sur Fonctions", + "Blocking Event Loop": "Boucle d’Événements Bloquée", + "Trampoline": "Trampoline", + "Async Loops": "Boucles Asynchrones", + "Recursion": "Récursivité", + "Currying": "Currying", + "Function Call": "Appel de Fonction" + }, + "exercises": { + "Basic: Map": { + "didnt_use_map": "Vous n’avez pas utilisé Array#map", + "used_map": "Youpi ! Vous avez utilisé Array#map" + }, + "Basic: Every Some": { + "found_good_lists": "%d bonnes listes trouvées !" + }, + "Basic: Call": { + "matched_objects": "Détection de %d des %d objets valides, sur un total de %d." + }, + "Higher Order Functions": { + "call_log": "Fonction appelée %d fois." + }, + "Function Spies": { + "call_times": "Méthode appelée %d fois.", + "incorrect_return": "Vérifiez la valeur de retour de votre fonction !", + "incorrect_this": "Vérifiez le `this` de votre fonction ! Conseil : `Function#apply()`.", + "not_all_args": "Vérifiez que vous transmettez bien TOUS les arguments ! Conseil : `Function#apply()`." + }, + "Trampoline": { + "intro": "Répétition de la fonction %d fois", + "result": "La fonction a été exécutée avec succès %d fois." + }, + "Async Loops": { + "all_loaded": "L’ensemble des %d utilisateurs a été chargé !", + "bad_result": "j’attendais : \n%s\n mais j’ai reçu :\n%s", + "took_too_long": "Ça a pris trop longtemps !" + }, + "Currying": { + "five_words": "Ce,problème,est,désormais,résolu" + } + } +} diff --git a/package.json b/package.json index 30d7af2..746c7ea 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "functional-javascript": "functional-javascript.js", "functional-javascript-workshop": "functional-javascript.js" }, - "scripts": { - }, + "scripts": {}, "keywords": [ "workshopper", "tutorial", @@ -17,8 +16,10 @@ "author": "Tim Oxley", "license": "MIT", "dependencies": { - "workshopper": "~0.7.2", - "lorem-ipsum": "~0.1.1" + "deep-eql": "^0.1.3", + "lorem-ipsum": "^1.0.1", + "workshopper": "^2.3.1", + "workshopper-exercise": "^2.3.0" }, "repository": { "type": "git", diff --git a/problems/async_loops/setup.js b/problems/async_loops/setup.js deleted file mode 100644 index b1bee62..0000000 --- a/problems/async_loops/setup.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -function randomInt(min, max) { - return Math.floor((Math.random() * (max - min + 1)) + min) -} - -module.exports = input(new Array(randomInt(10, 20)) -.join(',') -.split(',') -.map(function() { - return { - id: randomInt(0, 1000), - name: lorem().split(' ').slice(0, 2).map(function(word) {word[0] = word[0].toUpperCase(); return word;}).join(' ') - } -})).wrap(function(input, mod) { - - var assert = require('assert') - var inspect = require('util').inspect - - var users = input[0] - var ids = users.map(function(user) {return user.id}) - var load = function(id, fn) { - setTimeout(function() { - var match = users.filter(function(user) {return user.id === id}) - if (match.length) fn(match[0]) - else fn(null) - }, Math.floor(Math.random() * 1000)) - } - var done = function(submittedUsers) { - clearTimeout(tooLong) - console.log(submittedUsers) - - assert.deepEqual(submittedUsers, users, 'expected: \n' + inspect(users) + '\n but got: \n'+ inspect(submittedUsers)) - - console.log('All %d users loaded!', submittedUsers.length) - } - - mod.call(mod, ids, load, done) - var tooLong = setTimeout(function() { - throw new Error('Took too long!') - }, 1000) -}) diff --git a/problems/basic_every_some/setup.js b/problems/basic_every_some/setup.js deleted file mode 100644 index 381dbe2..0000000 --- a/problems/basic_every_some/setup.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -function randomInt(min, max) { - return Math.floor((Math.random() * (max - min + 1)) + min) -} - -function makeUser() { - return { - id: randomInt(0, 1000), - name: lorem().split(' ').slice(0, 2).map(function(word) {word[0] = word[0].toUpperCase(); return word;}).join(' ') - } -} - -function makeListOfUsers() { - return new Array(randomInt(10, 100)) - .join(',') - .split(',') - .map(makeUser) -} - -var good = makeListOfUsers() -var bad = makeListOfUsers() -var lists = Array.apply(null, {length: 20}).map(function() { - return Array.apply(null, {length: 20}).map(function() { - if (Math.random() < 0.95) { - return good[randomInt(0, 10)] - } else { - return bad[randomInt(0, 10)] - } - }) -}) - -module.exports = input(good, lists).wrap(function(input, mod) { - var good = input[0] - var lists = input[1] - var assert = require('assert') - var inspect = require('util').inspect - var test = mod.call(mod, good) - - var goodLists = 0 - - lists.forEach(function(list) { - test(list) && goodLists++ - }) - - console.log('found %d good lists!', goodLists) -}) diff --git a/problems/basic_filter/setup.js b/problems/basic_filter/setup.js deleted file mode 100644 index 3001ac6..0000000 --- a/problems/basic_filter/setup.js +++ /dev/null @@ -1,14 +0,0 @@ -var input = require('../../input') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} - -module.exports = input(new Array(randomInt(10, 100)) -.join(',').split(',') -.map(function() { - return { message: require('lorem-ipsum')() } -})).wrap(function(input, getShortMessages) { - var messages = input[0] - console.log(getShortMessages(messages)) -}) diff --git a/problems/basic_map/setup.js b/problems/basic_map/setup.js deleted file mode 100644 index cc91fbf..0000000 --- a/problems/basic_map/setup.js +++ /dev/null @@ -1,31 +0,0 @@ -"use strict" - -var input = require('../../input') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} - -module.exports = input(new Array(randomInt(0, 19)) -.join(',') -.split(',') -.map(function() { - return randomInt(0, 9) -})).wrap(function(input, doubleAll) { - var numbers = input[0] - var map = Array.prototype.map - var usedMap = false - Array.prototype.map = function() { - usedMap = true - return map.apply(this, arguments) - } - - console.log('input:', numbers) - console.log('output:', doubleAll(numbers)) - - setTimeout(function() { - if (!usedMap) { - console.log('You didn\'t use Array#map!!??') - } - }) -}) diff --git a/problems/basic_recursion/setup.js b/problems/basic_recursion/setup.js deleted file mode 100644 index 7932981..0000000 --- a/problems/basic_recursion/setup.js +++ /dev/null @@ -1,14 +0,0 @@ -var input = require('../../input') -var lorem = require('lorem-ipsum') - -module.exports = input(lorem({count: 1, units:'paragraphs'}) - .replace(/([^\w ])/g, '')// remove non-words and spaces - .toLowerCase() // lowercase I guess - .split(' ') // create array of words - ).wrap(function(input, reduce) { - var result = reduce(input[0], function(prev, curr) { - prev[curr] = ++prev[curr] || 1 - return prev - }, {}) - console.log(result) - }) diff --git a/problems/basic_reduce/setup.js b/problems/basic_reduce/setup.js deleted file mode 100644 index 935fa14..0000000 --- a/problems/basic_reduce/setup.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -module.exports = input(lorem({count: 1, units:'paragraphs'}) - .replace(/([^\w ])/g, '')// remove non-words and spaces - .toLowerCase() // lowercase I guess - .split(' ') // create array of words - ).wrap(function(input, countStrings) { - console.log(countStrings(input[0])) - }) diff --git a/problems/blocking_event_loop/setup.js b/problems/blocking_event_loop/setup.js deleted file mode 100644 index 1fdc92b..0000000 --- a/problems/blocking_event_loop/setup.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict" - -var input = require('../../input') - - - -module.exports = input().wrap(function(input, repeat) { - - var count = 0 - var CYCLES = 100000 - - function operation() { - for (var i = 0; i < 1000000; i++) {} // burn some CPU cycles - count++ // count how many times this function was called - } - - console.log() - console.log('the operation:') - console.log(operation.toString()) - console.log() - console.log('Trying to repeat the operation %d times...', CYCLES) - console.log('Press control+c to kill.') - console.log() - - var start = Date.now() - repeat(operation, CYCLES) - - setTimeout(function() { - var end = Date.now() - console.error('Performed %d operations.', count) - if (count === CYCLES) console.log('Fail! Should not have completed all operations!') - // TODO calculate ideal ops per second? - //if (count < 1000) console.log('Fail! Should have performed more operations!') - if (end - start < 1500) console.log('Interrupted in approximately 1 second!') - else console.log('Fail! Interrupted in %d milliseconds', end - start) - process.exit() - }, 1000) -}) - diff --git a/problems/currying/setup.js b/problems/currying/setup.js deleted file mode 100644 index 47fab97..0000000 --- a/problems/currying/setup.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Created by naor on 10/10/13. - */ -"use strict"; - -var input = require('../../input'); - -module.exports = input().wrap(function(input, curryN) { - - function add3(one, two, three) { - return one + two + three - } - console.log("var curryC = curryN(add3)") - console.log("var curryB = curryC(1)") - console.log("var curryA = curryB(2))") - var curryC = curryN(add3) - var curryB = curryC(1) - var curryA = curryB(2) - - console.log("curryA(3) => ", curryA(3)) // => 6 - console.log("curryA(10) =>", curryA(10)) // => 13 - console.log("curryN(add3)(1)(2)(3) =>", curryN(add3)(1)(2)(3)) // => 6 - - - function strConcat(){ - var args = Array.prototype.slice.call(arguments); - return Array.prototype.concat.apply([], args).join(" "); - } - - console.log("curryN(strConcat, 5)('This')('problem')('has')('been')('solved')) =>") - console.log(curryN(strConcat, 5)('This')('problem')('has')('been')('solved')); -}); diff --git a/problems/function_call/setup.js b/problems/function_call/setup.js deleted file mode 100644 index c82c918..0000000 --- a/problems/function_call/setup.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} - -function listOfWords() { - return lorem({count: 5, units:'words'}) - .replace(/([^\w ])/g, '')// remove non-words and spaces - .toLowerCase() // lowercase I guess - .split(' ') // create array of words -} - -var words = listOfWords() -module.exports = input(listOfWords()) -.wrap(function(words, slice) { - words.forEach(function(words) { - console.log('words:', words) - console.log('') - console.log('slice(words):') - console.log(slice(words)) - console.log('') - console.log('slice(words, 0, 1):') - console.log(slice(words, 0, 1)) - console.log('') - console.log('slice(words, 2):') - console.log(slice(words, 2)) - console.log('') - console.log('slice(words, -1):') - console.log(slice(words, -1)) - }) -}) diff --git a/problems/function_spies/setup.js b/problems/function_spies/setup.js deleted file mode 100644 index 3f07c24..0000000 --- a/problems/function_spies/setup.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -module.exports = input(Array.apply(null, {length: Math.random() * 20}).map(function() { - return lorem().split(' ') -})).wrap(function(input, Spy) { - input = input[0] - var assert = require('assert') - - console.log.bind = function () { - throw new Error('Try implementing this without bind!') - } - - var count = 0 - var parent = { - test: function() { - assert.deepEqual([].slice.call(arguments), input[count], "Check you are passing ALL the arguments! Hint: Function#apply") - assert.strictEqual(this, parent, "Check the function's this! Hint: Function#apply") - return arguments - } - } - var originalFn = parent.test.bind(parent) - var spy = Spy(parent, 'test') - - input.forEach(function(args, i) { - console.log.apply(console, args) - count = i - assert.deepEqual(originalFn.apply(parent, args), parent.test.apply(parent, args), "Check your function's return value!") - }) - - console.log('Method called %d times. ', spy.count) -}) diff --git a/problems/hello_world/setup.js b/problems/hello_world/setup.js deleted file mode 100644 index 244d36c..0000000 --- a/problems/hello_world/setup.js +++ /dev/null @@ -1,6 +0,0 @@ -var input = require('../../input') - -module.exports = input(require('lorem-ipsum')()).wrap(function(input, mod) { - console.log('input:', input[0]) - console.log('output:', mod(input[0])) -}) diff --git a/problems/higher_order_functions/setup.js b/problems/higher_order_functions/setup.js deleted file mode 100644 index 10574c4..0000000 --- a/problems/higher_order_functions/setup.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} - -module.exports = input(function count() { - count.counter = count.counter || 0 - console.log("Called function %d times.", (count.counter++) + 1) -}, randomInt(3, 10)).wrap(function(input, repeat) { - repeat(input[0], input[1]) -}) diff --git a/problems/implement_map_with_reduce/setup.js b/problems/implement_map_with_reduce/setup.js deleted file mode 100644 index c750efa..0000000 --- a/problems/implement_map_with_reduce/setup.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict" - -var input = require('../../input') - -function randomInt(min, max) { - return Math.floor((Math.random() * max - min) + min) -} - -module.exports = input(function(item, index, arr) { - return item * 3 -}, new Array(randomInt(0, 19)) -.join(',') -.split(',') -.map(function() { - return randomInt(0, 9) -})).wrap(function(input, mod) { - var operation = input[0] - var nums = input[1] - console.log(mod(nums, operation)) -}) diff --git a/problems/partial_application_with_bind/setup.js b/problems/partial_application_with_bind/setup.js deleted file mode 100644 index e822158..0000000 --- a/problems/partial_application_with_bind/setup.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -module.exports = input(Array.apply(null, {length: Math.random() * 20 + 1}).map(function() { - return lorem() -})).wrap(function(input, logger) { - var info = logger('INFO:') - var warn = logger('WARN:') - input[0].forEach(function(message, i) { - var args = message.split(' ') - if (i % 2 === 0) info.apply(null, args) - else warn.apply(null, args) - }) -}) diff --git a/problems/partial_application_without_bind/setup.js b/problems/partial_application_without_bind/setup.js deleted file mode 100644 index 2806ac7..0000000 --- a/problems/partial_application_without_bind/setup.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict" - -var input = require('../../input') -var lorem = require('lorem-ipsum') - -module.exports = input(Array.apply(null, {length: Math.random() * 20 + 1}).map(function() { - return lorem() - })).wrap(function(input, logger) { - - var assert = require('assert') - - console.log.bind = function () { - throw new Error('Try implementing this without bind!') - } - - var info = logger('INFO:') - var warn = logger('WARN:') - input[0].forEach(function(message, i) { - if (i % 2 === 0) info.apply(null, message.split(' ')) - else warn.apply(null, message.split(' ')) - }) -}) diff --git a/problems/trampoline/setup.js b/problems/trampoline/setup.js deleted file mode 100644 index 342e32f..0000000 --- a/problems/trampoline/setup.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict" - -var input = require('../../input') - -module.exports = input().wrap(function(input, repeat) { - console.log('repeating 100000 times') - var count = 0 - repeat(function() { - count++ - }, 100000) - console.log('Sucessfully executed %d times.', count) -}) -