-
Notifications
You must be signed in to change notification settings - Fork 4
/
coderoad.json
540 lines (540 loc) · 35.2 KB
/
coderoad.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
{
"info": {
"title": "Functional School",
"description": "A trip through functional programming in Javascript using common built-in Javascript array methods such as `map` & `reduce`.\n\nBy the end, you should have an understanding of how to use array methods to manipulate semi-complex data.\n\nLevel: Intermediate\nKeywords: javascript, functional\nLength: 1-2 hours"
},
"pages": [
{
"title": "Start",
"description": "Understanding the Data Set\n\nOver this tutorial series, we'll be changing and working with two different data sets. It'll be a big help to first understand what the data looks like.\n\n```json\nvar students = [\n {\n \"title\": \"Relational Databases\",\n \"instructor\": \"Sean Quentin Lewis\",\n \"name\": \"Ada Lovelace\",\n \"score\": 91,\n \"grade\": \"A\"\n },\n ...\n]\n```\n\nHere we have an array of \"student\" objects. To get the first item in the array, you can use the array index. Array indexes start at 0.\n\n```js\nconsole.log(\n 'first instructor', students[0].instructor\n);\n// first instructor Sean Quentin Lewis\n```",
"tasks": [
{
"description": "Look in \"data/students.js\". This is the data we will be working with. Run save to continue.",
"tests": [
"00/01"
],
"actions": [
"writeFromFile('data/students.js', '00/data.js')"
]
},
{
"description": "Set `first` to the first item in the `students` array.",
"tests": [
"00/02"
],
"actions": [
"open('setup.js')",
"set('// Welcome to CodeRoad!\nconst students = require('./data/students').default;\n\nvar first = ::>\n')"
],
"hints": [
"Get the first item in students using the array index",
"Access the title of `students[0]`"
]
},
{
"description": "Set `myName` to the \"name\" of the first student in the list.",
"tests": [
"00/03"
],
"actions": [
"insert('var myName = ::>\n')"
],
"hints": [
"Get the first \"name\" in the students using the array index",
"Access the \"name\" of `first`",
"Try `first.name`"
]
},
{
"description": "Log your name to the console.",
"tests": [
"00/04"
],
"actions": [
"insert('\nconsole.log(::>);\n')"
],
"hints": [
"Use `console.log`",
"Use `console.log(myName)`"
]
}
],
"onPageComplete": "Now we're ready to get started with `filter`ing our data."
},
{
"title": "Filter",
"description": "Array -> Array of items that match a condition\n\nYou've hacked into the school's computer system, and just in time. The grades are in, but you're not too proud of your performance. That's okay, you have a plan: you're going to create a fake report card.\n\nIt would be great if you could `filter` the scores that your parents will see.\n\n`filter` takes a matching condition function and only returns items that result in true. As an example, look at `isA` below:\n\n```js\nfunction isA(x) {\n return x === 'a';\n}\n```\n\n\nLike all of the methods in this chapter, `filter` is already part of the `Array.prototype`, so you can run it following any array. Each item in the array is passed into the params of the condition function, one by one. [Learn more](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter).\n\n```js\nconst list = ['a', 'b'];\nlist.filter(isA);\n\n// if isA(list[0]), add to output array\n// if isA(list[1]), add to output array\n//\n//> ['a']\n```\n\nIf your data was composed of objects, we could use dot notation to find matches. Checkout `isB` below.\n\n```js\nfunction isB(x) {\n return x.item === 'b'\n}\n\nconst list = [{item: 'a'}, {item: 'b'}];\nlist.filter(isB);\n//> [{item: 'b'}]\n```\n\nWhere were we? Back to filtering our grades.\n\nThere's too much student data in the computer system. We'll have to sort through it. Have a look at an example below:\n\n```js\nconsole.log(students[0]);\n//> { course: 'Web Security',\n// instructor: 'Sue Denim',\n// name: 'Rebecca Heineman',\n// score: 93,\n// grade: 'A' }\n```",
"tasks": [
{
"description": "Write a filter condition function called `isAda` that returns true only if the name matches your name: \"Ada Lovelace\".",
"tests": [
"01/01"
],
"actions": [
"open('01-filter.js')",
"set('import students from './data/students';\n// Array.filter(fn)\n\nfunction isAda(student) {\n // return true if student name\n // matches \"Ada Lovelace\"\n ::>\n}\n')"
],
"hints": [
"Some tasks have hints",
"Check if `student.name` matches \"Ada Lovelace\"",
"Use `===` to check equality"
]
},
{
"description": "Set `const myData` to filter with the `isAda` function.",
"tests": [
"01/02"
],
"actions": [
"insert('// run the function name in filter\nconst myData = students.filter(::>);\n')"
],
"hints": [
"Add a function to the `filter` call: `Array.filter(function() {})`",
"Pass `isAda` into your `filter` call"
]
},
{
"description": "Write a filter condition called `isGoodGrade` that will filter out any \"D\" or \"F\" grades.",
"tests": [
"01/03"
],
"actions": [
"insert('\n\n// return true if student.grade is not a \"D\" or \"F\"\nfunction isGoodGrade(student) {\n ::>\n}\n')"
],
"hints": [
"match for `student.grade` that isn't \"D\" or \"F\"",
"use `!==` to check non-equality",
"Match for both: `student.grade !== \"D\" && student.grade !== \"F\"`"
]
},
{
"description": "Set `const myBest` to your scores, excluding any grades that are \"D\" or \"F\".",
"tests": [
"01/04"
],
"actions": [
"insert('// filter out \"D\"'s and \"F\"'s here\nconst myBest = myData.filter(::>);\n\n')"
]
}
],
"onPageComplete": "In the next step we'll look at how to `sort` data"
},
{
"title": "Sort",
"description": "Array -> sorted Array\n\nYour grades are filtered down to your name and good scores - but wouldn't it be better if your best grades were displayed first, at the top? Besides, your parents rarely read anything through.\n\nYou can use the array method `sort` to arrange your data. Let's see how it works.\n\n```js\n['c', 'b', 'a'].sort();\n//> ['a', 'b', 'c']\n\n[3, 2, 1].sort();\n//> [1, 2, 3]\n```\n\nBut what about sorting scores inside of an object?\n\n```js\n[{a: 3}, {a: 1}, {a: 2}].sort();\n//> [{a: 3}, {a: 1}, {a: 2}]\n```\n\nThat didn't work. Instead, you can write a custom `compareScore` function.\n\nA sort function takes two params, and compares the first to the second. It should return values saying where the second value should go in the array:\n\n * -1 : sort to a lower index (front)\n * 1 : sort to a higher index (back)\n * 0 : stay the same\n\nAlright, now time to sort your best grades to the top.\n\nFirst you'll need to write a sort condition function called `compareScore`.",
"tasks": [
{
"description": "look at the data we will work with next: `myBest`. Save to continue.\n@open('data/myBest.js')",
"tests": [
"02/01"
],
"actions": [
"writeFromFile('data/myBest.js', '02/myBest.js')"
]
},
{
"description": "`compareScore` should return 1 if the first score is less than the second",
"tests": [
"02/02"
],
"actions": [
"open('02-sort.js')",
"set('import myBest from './data/myBest';\n// Array.sort(fn)\n\nfunction compareScore(a, b) {\n switch (true) {\n case b.score > a.score:\n // it should return 1 if b's score is more than a's\n return ::>\n case 'set condition here':\n // it should return -1 if b's score is less than a's\n\n default:\n // it should return 0 if b and a have the same score\n\n }\n}\n')"
]
},
{
"description": "`compareScore` should return -1 if the first score is more than the second",
"tests": [
"02/03"
],
"hints": [
"set the second case to `b.score < a.score`"
]
},
{
"description": "`compareScore` should return 0 if the first score is the same as the second",
"tests": [
"02/04"
],
"hints": [
"no case is necessary, use the `default` case"
]
},
{
"description": "Set `mySorted` to the result of `myBest` sorted by `compareScore`",
"tests": [
"02/05"
],
"actions": [
"insert('// use the compare function to sort myBest\nconst mySorted = myBest::>\n')"
],
"hints": [
"try using `myBest.sort()`"
]
}
],
"onPageComplete": "In the next step we'll look at changing data with `map`"
},
{
"title": "Map",
"description": "Array -> run a function over each item -> Array\n\nYou've filtered and sorted our data, but neither of those actually change the data.\n\nWouldn't it be simpler if you could just change your grades?\n\nYou can use the array method `map` to run a function that returns changes to your data.\n\nAs an example, let's look at how you would increment each number in an array.\n\n```js\nfunction addOne(num) {\n return num + 1;\n}\n\n[1, 2, 3].map(addOne);\n//> [2, 3, 4]\n\nfunction addToVal(obj) {\n obj.val += 1;\n return obj;\n}\n[{ val: 1}].map(addToVal);\n//> [{ val: 2 }]\n```\n\n`map` can change data, and it can also alter structure of the data you're working with.\n\n```js\nfunction makeObject(num) {\n return { val: num };\n}\n\n[1, 2].map(makeObject);\n//> [{ val: 1 }, { val: 2 }]\n```\n\nSimilarly, `map` can also restrict the data you want to work with. See the example below to see another way scores could be sorted.\n\n```js\nmyBest\n .map(function(student) {\n return student.score;\n })\n .sort()\n .reverse()\n//> [93, 91, 88, 88, 82, 81, 73]\n```\n\nIn this example, `map` transformed an object with keys of 'title', 'instructor', 'name', 'score' and 'grade', to an array of just scores. Values weren't changed, but rather limited to a smaller subset of scores.\n\n`map` is powerful. Let's see what you can do with it.\n\nThose D & F grades would look a lot better if they suddenly became A's.\n\nLet's go back to before we filtered out the bad grades, and instead change the grades to A's.",
"tasks": [
{
"description": "load \"myCourses\"",
"tests": [
"03/01"
],
"actions": [
"writeFromFile('data/myCourses.js', '03/myCourses.js')",
"open('data/myCourses.js')"
]
},
{
"description": "Make a function `changeGrade` that takes a course and changes the grade to an \"A\"",
"tests": [
"03/02"
],
"actions": [
"open('03-map.js')",
"set('import myCourses from './data/myCourses';\n// Array.map(fn)\n\n/*\n * change any the `course.grade` into an 'A'\n *\n * for example:\n * changeGrade({ grade: 'F' }) === { grade: 'A' };\n*/\n\nfunction changeGrade(course) {\n ::>\n}\n\n')"
],
"hints": [
"give `changeGrade` a parameter, call it \"course\"",
"set `course.grade` to \"A\"",
"return the changed course"
]
},
{
"description": "Map over the `myCourses` with the `changeGrade` function. Set `myChanged` to the result.",
"tests": [
"03/03"
],
"actions": [
"insert('// map over `myCourses` and call `changeGrade` for each item\nconst myChanged = myCourses.map(::>);\n')"
],
"hints": [
"simply call `.map(changeGrade)`"
]
},
{
"description": "Hold up. An A in \"Data Science\" class looks way to suspicious. Your parents might catch on to your cheating.\n\nLet's go back to `myCourses` and instead increment each score by 12 points.",
"tests": [
"03/04"
],
"actions": [
"insert('\nfunction increaseScore(course) {\n ::>\n}\n\n// map over `mySlightlyChanged` with a function `increaseScore` to increment each score by 12\nconst mySlightlyChanged = myCourses;\n')"
],
"hints": [
"give `increaseScore` a parameter, call it \"course\"",
"it should increase `course.score`",
"return `course`"
]
},
{
"description": "Wait. Now you're getting 105 in \"Algorithm Design\" class. Fix `increaseScore` so that the maximum score is 95. That should be less suspicious.",
"tests": [
"03/05"
],
"hints": [
"Use `Math.min(x, y)`",
"set `course.score` to `Math.min(95, course.score + 12)`"
]
},
{
"description": "One more problem. Now the scores don't match the grades. you have 95 score in \"3D Computer Graphics\", but only a \"B\" grade. Update your `increaseScore` function to also update the grade by using the `getGrade` function",
"tests": [
"03/06"
],
"actions": [
"insert('\n// use getGrade to set the course grade\n// update `increaseScore` to also update the grade\nfunction getGrade(score) {\n switch (true) {\n case (score >= 90):\n return \"A\";\n case (score >= 80):\n return \"B\";\n case (score >= 70):\n return \"C\";\n case (score >= 60):\n return \"D\";\n default:\n return \"F\";\n }\n}\n\n')"
],
"hints": [
"call `getGrade` inside of `increaseScore`",
"the `increaseScore` function should set course.grade equal to `getGrade(course.score)`"
]
},
{
"description": "Check to make sure everything is working. Set `scoresAndGrades` to an array of scores and grades only.",
"tests": [
"03/07"
],
"actions": [
"insert('\n// set `scoresAndGrades` to an array of scores and grades\n// it should return an array of objects like this: {score: 75, grade: 'C'}\nconst scoresAndGrades = mySlightlyChanged.map(::>)\n')"
],
"hints": [
"use `map` to return only the \"score\" & \"grade\" fields",
"map with a function with a parameter, call it \"student\"",
"you can destructure the param to be `function({score, grade})`",
"then simply return { score, grade }"
]
}
],
"onPageComplete": "In the next step we'll compare `map` with `forEach`"
},
{
"title": "forEach",
"description": "Array -> run a function for each item\n\nYou've updated your grades, but they're still in an array. It's time to loop over them and log them to the console.\n\nTo open the console, go to *View* > *Developer* > *Toggle Developer Tools*. Or press *cmd+opt+I* on Mac, *ctrl+alt+I* on Windows.\n\n`forEach` has a lot in common with `map`, but there is a big difference. Understanding that difference is important for grasping the difference between:\n\n * **functional** & **imperative** programming\n * **pure** & **impure** functions\n\nKnow it or not, you're probably already used to \"imperative\" programming.\n\n> **Imperative** programming describes the order of actions\n\nImperative code tells the computer what to do, step by step.\n\n```js\nlet x = 1; // make a variable\nx = x + 1; // add one\nx = x + 1; // add another\nconsole.log(x);\n//> 3\n```\n\n> **Functional** programming describes the data transformation\n\nFunctional programming is a lot like writing math equations. As in math, 1 + 1 always equals 2.\n\nIn the same way, a **pure** function will always have the same result from a given input. Input 1 -> output 2. Every time.\n\n```js\n// a pure function\nfunction addOne(x) {\n return x + 1;\n}\naddOne(1)\n//> 2\naddOne(1)\n//> 2\n```\n\nA function is \"pure\" if it doesn't change anything outside of its scope. Pure functions are easy to test, reuse and reason about. In other words, they make your job easier.\n\nOn the other hand, **impure** functions are less predictable. The result may be different if you call it at a later time.\n\n```js\nlet y = 1;\n// impure function\nfunction increment(x) {\n y += x;\n return y;\n}\nincrement(1)\n//> 2\nincrement(1)\n//> 3\n```\n\nIt's good practice to ensure your `map` functions remain pure.\n\nBut `forEach` can be a little more dangerous. Why? Let's have a look.\n\n```js\n[1, 2, 3].map(addOne);\n//> [2, 3, 4]\n\n[1, 2, 3].forEach(addOne);\n//> undefined\n```\n\nWhat? `undefined`? `forEach` runs a function on each item in the array, and doesn't care what the function returns. Functions called by `forEach` must make changes, called **side effects**, to even be noticed.\n\n```js\n// impure function, changes log\nfunction addOneToLog(x) {\n console.log(x);\n}\n\n[1, 2, 3].forEach(addOneToLog);\n//> 2\n//> 3\n//> 4\n```\n\nNow that we see how `forEach` works, let's use it to make calls to the `console`.",
"tasks": [
{
"description": "checkout the data we'll use next: \"myFixed\". Save to continue.",
"tests": [
"04/01"
],
"actions": [
"writeFromFile('data/myFixed.js', '04/myFixed.js')",
"open('data/myFixed.js')"
]
},
{
"description": "Use `forEach` to log out your report card to the console",
"tests": [
"04/02"
],
"actions": [
"open('04-forEach.js')",
"set('import myFixed from './data/myFixed';\n// Array.forEach(fn)\n\nfunction logCourse(course) {\n console.log(`${course.grade} ${course.score} ${course.title}`);\n}\n\n// log your grades to the console\nmyFixed.forEach(::>);\n')"
],
"hints": [
"call `forEach` with `logCourse`"
]
},
{
"description": "Add a second parameter to `logCourseWithIndex` called `index`. Then call the function with `myFixed.forEach`.",
"tests": [
"04/03"
],
"actions": [
"insert('\n// add a second param called 'index' to the function\nfunction logCourseWithIndex(course::>) {\n console.log(`${index + 1} ${course.grade} ${course.score} ${course.title}`);\n}\n\n// log your grades to the console with an index\nmyFixed.forEach(logCourseWithIndex);\n')"
],
"hints": [
"Array methods can take more than one parameter",
"Add a second parameter to `logCourseWithIndex`"
]
},
{
"description": "Add a third parameter called `array` to `logCourseWithIndexAndArray`, then call the function with `myFixed.forEach`.",
"tests": [
"04/04"
],
"actions": [
"insert('\n// add a third param called 'array' to the function\nfunction logCourseWithIndexAndArray(course, index::>) {\n console.log(`${index + 1}/${array.length} ${course.grade} ${course.score} ${course.title}`);\n}\n\n// log your grades to the console with an index and array length\nmyFixed.forEach(logCourseWithIndexAndArray);\n')"
],
"hints": [
"Array methods can take more than one parameter",
"Add a third parameter to `logCourseWithIndexAndArray`"
]
},
{
"description": "What??? Suddenly Your data has all disappeared!\n\nIt seems `myFixed` relies on a chain of methods.\n\n```js\nmyFixed = students\n .filter(isAda)\n .sort(compareScore)\n .map(increaseScore)\n .map(getGrade)\n .forEach(logCourseWithIndexAndArray)\n```\n\nThis is why side-effects are dangerous. Students data must have changed, and now all of your transformations are effected.",
"tests": [
"04/05"
],
"actions": [
"insert('\nconsole.log(myFixed);\n')"
]
}
],
"onPageComplete": "Something strange is going on. In the next step we'll try to `find` your data."
},
{
"title": "find",
"description": "Array -> first element that matches a condition\n\nSomehow your name has disappeared from the computer system. We'll have to `find` a way to get it back.\n\nYou quickly put together a list of other students in class. If someone changed your name, it'll be the name that is not in that list.\n\n`find` works similar to `filter`, but returns only the first match.\n\n```js\nconst data = [1, 2, 3, 4, 5, 6];\n\nfunction isEven(num) {\n return num % 2 === 0;\n}\n\n// returns all matching data to a condition\ndata.filter(isEven);\n//> [2, 4, 6]\n\n// returns the first match\ndata.find(isEven);\n//> [2]\n```\n\nFind is great for performantly matching unique values in data, such as an \"id\", or in our case: a name.",
"tasks": [
{
"description": "load \"students\" data. Save to continue.",
"tests": [
"05/01"
],
"actions": [
"writeFromFile('data/myCourses2.js', '05/courses.js')",
"open('data/myCourses2.js')"
]
},
{
"description": "`filter` to `courses` in the class titled \"Web Security\"",
"tests": [
"05/02"
],
"actions": [
"open('05-find.js')",
"set('import courses from './data/myCourses2';\n// Array.find(fn)\n\n// filter for the course title matching \"Web Security\"\nconst myClass = courses.filter(::>);\n')"
],
"hints": [
"create a `filter` function that takes a param `course`",
"return `true` if a condition matches, otherwise `false`",
"filter for `course.title === \"Web Security\"`"
]
},
{
"description": "`find` the name in `myClass` that isn't in the list of known students",
"tests": [
"05/03"
],
"actions": [
"insert('\nconst otherStudents = [\"Albert Gonzalez\", \"Brian Kernaghan\", \"Danielle Bunten Berry\", \"Donald Knuth\", \"Grace Hopper\", \"Hack Kerr\", \"James Gosling\", \"Ken Thompson\", \"Kevin Mitnick\", \"Linus Torvalds\", \"Niklaus Wirth\", \"Rebecca Heineman\", \"Tim Berners-Lee\", \"Xiao Tian\", \"Ying Cracker\"];\n\n')",
"insert('// search for a student with a name\n// not matching students in `otherStudents`\nfunction notInList(::>) {\n\n}\n\n// find using `notInList`\nconst unknownStudent = myClass.find();\n')"
],
"hints": [
"use `indexOf` to find what doesn't match",
"use `otherStudents.indexOf(x) === -1` to find what doesn't match",
"match for `student.name`"
]
},
{
"description": "`filter` down to students from courses without known names",
"tests": [
"05/04"
],
"actions": [
"insert('\n// filter using `notInList`\nconst unknownStudentList = courses.filter(::>);\n')"
],
"hints": [
"consider reusing a function"
]
},
{
"description": "`map` over the result to get only the `name` property",
"tests": [
"05/05"
],
"actions": [
"insert('\n// list only student names\nconst unknownStudentNames = unknownStudentList.map(::>);\n')"
],
"hints": [
"use `map` to return only the `student.name`",
"try this inside of your map call: `student => student.name`"
]
},
{
"description": "`join('')` the array of names to output the result as a string",
"tests": [
"05/06"
],
"actions": [
"insert('\n// use `.join('')` to join the array of strings\nconst decodedName = unknownStudentNames::>;\nconsole.log(decodedName);\n')"
],
"hints": [
"call `join` following `unknownStudentNames`"
]
}
],
"onPageComplete": "Very strange. In the next step, let's find out who wants revenge, and give it to him!"
},
{
"title": "concat",
"description": "Array + Array -> Array\n\nBefore we've been working on a structured set of student data.\n\n```js\n// array of students\n[\n {\n \"title\": \"Relational Databases\",\n \"instructor\": \"Sean Quentin Lewis\",\n \"name\": \"Rebecca Heineman\",\n \"score\": 71,\n \"grade\": \"C\"\n }\n// students in courses...\n]\n```\n\nTo be safe, let's now work on the original data set. Notice how it is structured differently.\n\n```js\n// array of courses\n[\n {\n \"title\": \"Relational Databases\",\n \"instructor\": \"Sean Quentin Lewis\",\n \"students\": [\n {\n \"name\": \"Rebecca Heineman\",\n \"score\": 71,\n \"grade\": \"C\"\n }\n // students...\n ]\n }\n // courses...\n]\n```\n\nIn this data set, there is an array of students within an array of courses. So how can we recreate our original array of students from the courses?\n\nWeird things happen when you start combining arrays. We can use `concat` to bring sanity.\n\n```js\n[1, 2] + [3, 4];\n//> \"1, 23, 4\"\n\n[1, 2].push([3, 4]);\n//> 3\n\n[1, 2].join([3, 4]);\n//> \"13, 42\"\n\n[1, 2].concat([3, 4]);\n//> [1, 2, 3, 4]\n```\n\nUnfortunately, Javascript is missing a built in array method to concat multiple arrays together: let's call it `flatten` (sometimes called `concatAll`).\n\n`flatten` should loop over an array and `concat` each element.\n\nLet's look at an abstraction of what we need to do:\n\n```js\nconst start = [{\n a: 1,\n c: [\n { b: 1 }\n ]\n}, {\n a: 2,\n c: [\n { b: 2 }, { b: 3 }\n ]\n}];\n\nconst middle = start.map(function(outer) {\n return outer.c.map(function(inner) {\n return {\n a: outer.a,\n b: inner.b\n };\n });\n});\n//> [ [{ a: 1, b: 1 }], [{a: 2, b: 2}, {a: 2, b: 3}] ]\n\nconst end = pre.flatten();\n//> [{a: 1, b: 1}, {a: 2, b: 2}, {a: 2, b: 3}]\n```\n\nBack to business.\n\nWe have a suspect in mind: a classmate named \"Hack Kerr\". He's a nice guy, and he's always been friendly to you - but there's something suspicious about him: his name.\n\nWe'll test out flatten, then re-create our student array of data from the original course data.",
"tasks": [
{
"description": "load \"courses\"",
"tests": [
"06/01"
],
"actions": [
"writeFromFile('data/courses2.js', '06/courses2.js')",
"open('data/courses2.js')"
]
},
{
"description": "First, test out `flatten` on the `flattenedArray`\nArray -> anything\n\nWe know our likely suspect is also in the school computer system. Perhaps our suspect also changed his grades.\n\nYou can't be sure who is a cheater, but you can assume if the grades are well above the average, the person is likely to be the culprit. For this, we'll have to do some basic statistical calculations. We'll need a new tool for transforming arrays into different data representations.\n\n`map` has a major limitation: it will always output the same number of elements as the input array.\n\nWhen you want to transform data into something different, you'll likely want to use `reduce`.\n\nReduce requires two parameters:\n\n * the running total (set by an initialValue)\n * the next value in the array\n\n```js\nfunction add(total, next) {\n console.log(`add(${total}, ${next}) -> ${total + next}`)\n return total + next\n}\n\nconst initialValue = 100;\n[1, 5, 10].reduce(add, initialValue); // initial value\n\n// add(100, 1) -> 101\n// add(101, 5) -> 106\n// add(106, 10) -> 116\n//> 116\n```\n\nNotice in the example we input an array of 3 items and output a single number. The data has been transformed.\n\nIt takes a while to wrap your head around `reduce`, but once you do, you'll see it's usefulness everywhere.\n\nYou may have noticed we've already used `reduce` to `flatten` our arrays.\n\n```js\nArray.prototype.flatten = function() {\n return this.reduce((a, b) => a.concat(b), []);\n};\n```\n\nWith `flatten`, the initialValue was set to an empty array which each value was `concat` onto.\n\nDo some practice with `reduce`, before you use it to narrow down a cheating suspect.",
"tests": [
"06/02"
],
"actions": [
"open('06-concat.js')",
"set('```\nimport courses from './data/courses2');\n// Array.concat(any)\n\n// Array.prototype can be used to create new Array methods\nArray.prototype.flatten = function() {\n return this.reduce((a, b) => a.concat(b), []);\n};\n```\n))\n@action(insert(\n```\n\nconst numberedList = [[1, 2], [3, 4]];\n\n// use `flatten` on `numberedList`\nconst flattenedArray = numberedList::>;\n``` \n))\n@hint('call `.flatten()` on `numberedList`')\n\n\n+ Now `map` over the courses array, and `map` over the students array inside of it.\nReturn the fields:\n\n * title\n * instructor\n * name\n * grade\n * score\n@test('06/03')\n@action(insert(\n```\n\n// map over courses then\n// map over students inside of courses\nconst doubleArray = courses.map((course) => {\n return course.students.map((student) => {\n return {\n // fill in the fields\n title: ::>'',\n instructor: '',\n name: '',\n score: '',\n grade: ''\n };\n });\n});\n\n```\n))\n@hint('pair `course.title`')\n@hint('pair `student.name`')\n\n+ Use `flatten` to put all data into a single array. Set `students` to the result.\n@test('06/04')\n@action(insert(\n```\n// `flatten` doubleArray\nconst students = doubleArray::>;\n```\n))\n@hint('call `.flatten()` on `doubleArray`')\n\n+ Use the `suspects` array to `filter` to only data matching the names in the `suspects` array\n@test('06/05')\n@action(insert(\n```\n\nconst suspects = [\"Hack Kerr\"];\n// filter to data matching `suspects`\n\nconst suspectData = students::>;\n```\n))\n\n+ You just thought of two more suspects! Make a new variable called `newSuspects` and add it above `suspects`.\n\n```js\nconst newSuspects = ['Albert Gonzalez', 'Kevin Mitnick'];\n```\n\n`concat` the `newSuspects` onto the `suspects` list.\n@test('06/06')\n@hint('call `suspects.concat()` with `newSuspects`')\n\n\n## redu')"
]
},
{
"description": "load suspectData. We will come back to this after some practice;",
"tests": [
"07/01"
],
"actions": [
"writeFromFile('data/suspectData.js', '07/suspectData.js')",
"open('data/suspectData.js')"
]
},
{
"description": "Use `reduce` to sum the numbers in the `practice` array",
"tests": [
"07/02"
],
"actions": [
"open('07-reduce.js')",
"set('import courses from './data/courses2';\n// Array.reduce(fn(a, b), initialValue)\n\nconst practice = [1, 1, 2, 3, 5, 8, 13, 21];\n\nfunction add(a, b) {\n return a + b;\n}\n\n// total the numbers using a reduce function\nconst total = practice.reduce(::>);\n')"
],
"hints": [
"with only numbers, the initialValue defaults to 0",
"just call `reduce` with `add`"
]
},
{
"description": "Not all reduce functions are so easy. `reduce` is a little more difficult to master.\n\n`map` over each course and use `reduce` to calculate the class averages for each class. Set `averages` to the resulting array of all class averages.",
"tests": [
"07/03"
],
"actions": [
"insert('\nconst averages = courses.map((course) => {\n const sum = course.students.reduce((total, student) => {\n ::>\n\n });\n return Math.round(sum / course.students.length, 0);\n});\n')"
],
"hints": [
"set the initialValue to 0",
"like this: `reduce(function () {}, 0)`",
"return the sum of `student.score` and `total`"
]
},
{
"description": "`reduce` to an array of suspect scores from the `suspectData` we collected previously.",
"tests": [
"07/04"
],
"actions": [
"open('07-reduce.js')",
"insert('\n// [{ name: 'suspectName', scores: [ 50, 65, 75, 85...] } ...]\nconst suspectScores = suspectData.reduce((total, next) => {\n // see if suspect name has a list yet\n const index = total.findIndex((suspect) => suspect.name === next.name);\n if (index < 0) {\n total.push({\n ::>\n\n });\n } else {\n // push the next score onto the suspects scores\n total[index].scores.push();\n }\n return total;\n}, []);\n\n')"
],
"hints": [
"if the name is new, push an object with name & scores: `{ name: '', scores: [42]}`",
"match for `next.name` & `next.score`",
"you can concat the scores onto an array: `[].concat(next.score)`",
"if the name is already in the list, just add the `next.score`"
]
},
{
"description": "`map` over suspect data to find the `\"difference\"` from subtracting the students score from the average score. Add this to `suspectScores` using the key `difference`. The resulting array should look like this:\n```js\n[{\n name: 'suspectName',\n scores: [50, 65, 75 ...],\n difference: 15\n}]\n```",
"tests": [
"07/05"
],
"actions": [
"insert('\nconst suspectStats = suspectScores.map((suspect) => {\n // calculate the total difference in scores from the averages\n const difference = suspect.scores.reduce(::>);\n\n return {\n name: suspect.name,\n scores: suspect.scores,\n difference: difference\n };\n});\n')"
],
"hints": [
"You may want to use a second param: `index`",
"Compare the `suspect.scores[index]` with the `averages[index]`",
"To get a sum, start your `reduce` function at 0"
]
},
{
"description": "`reduce` down to likely suspect names by filtering with the `isCheater` function.\n\nThis could be done with a `filter` & `map`, but it is simpler to just use one `reduce`.",
"tests": [
"07/06"
],
"actions": [
"insert('\nfunction isCheater(suspect) {\n return suspect.difference > 200;\n}\n\n// reduce down to a string of likely suspects\nconst likelySuspects = suspectStats.reduce((::>) => {}, []);\n')"
],
"hints": [
"use `.join(', ')`"
]
},
{
"description": "It looks like we have a likely suspect.",
"tests": [
"07/07"
],
"actions": [
"insert('console.log(likelySuspects);\n')"
]
}
],
"onPageComplete": "In the next step, we'll look at using one of the most powerful methods: `reduce`"
}
]
}