├── ' ├── .eslintrc.json ├── .gitignore ├── 700c20f2e4b3b5d9258b70cad5e5e9f5.json ├── README.md ├── _.js ├── _testing_in_action_.png ├── appa.html ├── build.json ├── ch01.html ├── ch02.html ├── ch03.html ├── ch04.html ├── ch05.html ├── ch06.html ├── ch07.html ├── ch08.html ├── ch09.html ├── ch10.html ├── ch11.html ├── ch12.html ├── code ├── ch01 │ └── 1_all.js ├── ch04 │ ├── 1_new_code_from_scratch │ │ ├── 1_check_hand.js │ │ └── 2_check_hand.js │ ├── 2_new_code_tdd │ │ ├── 1_new_code_tdd.js │ │ └── 2_check_hand_with_mocha.js │ ├── 3_untested_code │ │ ├── 1_random_hand.js │ │ └── 2_random_hand.js │ └── 4_debugging_regression │ │ └── 1_random_hand.js ├── ch05 │ ├── 1_general │ │ └── general.js │ ├── 2_context │ │ ├── 1_background.js │ │ ├── 2_another_object_variants.js │ │ └── 3_fluent_and_composed.js │ └── 3_context │ │ ├── 1_diary.js │ │ ├── 2_diary.js │ │ ├── 3_diary.js │ │ ├── 4_diary.js │ │ ├── 5_diary.js │ │ ├── 6_diary_module.js │ │ ├── 7_diary_reader.js │ │ ├── 8_possible_future_privacy.js │ │ └── short-parts.js ├── ch06 │ ├── 1_initial.js │ ├── 2_after_renaming.js │ ├── 3_after_useless_code.js │ ├── 4_after_variables.js │ ├── 5_after_strings.js │ └── 6_after_working_with_arrays.js ├── ch07 │ ├── 10_after_shorthand_syntax.js │ ├── 11_after_getting_new_obj_constructor_func.js │ ├── 12_after_constructor_vs_factory.js │ ├── 13_after_class_for_classifier.js │ ├── 14_after_choosing_api.js │ ├── 15_after_privacy.js │ ├── 15_after_privacy_test.js │ ├── 16_after_new_domain.js │ ├── 16_after_new_domain_test.js │ ├── 1_initial.js │ ├── 2_after_array_object_alternatives.js │ ├── 3_after_testing_setup.js │ ├── 4_after_extracting_functions.js │ ├── 5_after_handling_remaining_globals.js │ ├── 6_after_independent_data.js │ ├── 7_after_bringing_classify_in.js │ ├── 8_after_untangling_coupled_values.js │ ├── 9_after_bringing_other_functions_and_vars.js │ └── secrets.js ├── ch08 │ ├── 1_lets_build_a_hierarchy.js │ ├── 2_lets_wreck_our_hierarchy.js │ ├── 3_object_literals.js │ ├── 4_factory_functions.js │ ├── 4_factory_functions_with_prototype.js │ ├── 5_multiple_inheritance.js │ ├── 6_hyperextension.js │ ├── 6_hyperextension_fixed.js │ ├── 7_goat_cabbage_wolf.js │ └── 7_goat_cabbage_wolf_fixed.js ├── ch09 │ ├── 1_template.js │ ├── 1_template_final.js │ ├── 1_template_functional.js │ ├── 2_strategy.js │ ├── 3_state.js │ ├── 3_state_final.js │ ├── 4_null_object.js │ ├── 4_null_object_final.js │ ├── 5_adapter.js │ ├── 5_wrapper_dog.js │ ├── 5_wrapper_null_object.js │ └── 6_facade.js ├── ch10 │ ├── 1_why_async.js │ ├── 2_fixing_pyramid_of_doom.js │ ├── 3_callbacks_and_testing.js │ └── 4_promises.js └── ch11 │ ├── 10_functional_version_of_nbc.js │ ├── 10_functional_version_of_nbc_test.js │ ├── 1_restrictions_and_benefits.js │ ├── 2_the_basics.js │ ├── 3_currying.js │ ├── 4_function_composition.js │ ├── 5_types.js │ ├── 6_introducing_sanctuary.js │ ├── 7_null_object_again.js │ ├── 8_functional_refactoring_with_maybe.js │ └── 9_functional_refactoring_with_either.js ├── console_with_context.png ├── copyright.html ├── cover.html ├── cover.png ├── dedication.html ├── diagramming_language.afdesign ├── diagrams ├── diary_creating_function.png ├── function.png ├── function_anonymous.png ├── function_attached_to_anObject.png ├── function_diary_privateUnlock.png ├── function_diary_privateUnlock_2.png ├── function_diary_privateUnlock_3.png ├── function_diary_read.png ├── function_diary_tryLock.png ├── function_returning_number.png ├── function_setIt.png ├── function_with_bulk_1.png ├── function_with_bulk_2.png ├── function_with_complex_output.png ├── function_with_global_implicit.png ├── function_with_no_return.png ├── function_with_on_explicit_input_one_implicit_input.png ├── function_with_side_effect.png ├── function_with_two_explicit_inputs.png ├── function_with_two_non_local_inputs.png └── function_with_undefined_implicit.png ├── diary.png ├── diary10.png ├── diary3.png ├── diary4.png ├── diary5.png ├── diary6.png ├── diary7.png ├── diary8.png ├── diary9.png ├── diary_2.png ├── dynamic_this.png ├── fantasy_land_diagram.png ├── function-circle.png ├── function.png ├── functions_with_bulk.png ├── haskell_diagram.png ├── images ├── __red_green_refactor.png ├── _red_green_refactor.png ├── _testing_in_action_.png ├── cover.png ├── red_green_refactor.afdesign ├── red_green_refactor_.png ├── tarsier.png ├── testing_in_action.afdesign ├── testing_in_action.png └── testing_in_action_.png ├── ix.html ├── mocha_results.png ├── mocha_test_output.png ├── nbc.js ├── not_global_context.png ├── null.png ├── preface.html ├── private_function.png ├── red_green_refactor_.png ├── side_effect_to_other_this.png ├── testing_in_action.png ├── testing_in_action_.png ├── theme ├── epub │ └── epub.css ├── html │ └── html.css ├── mobi │ └── mobi.css └── pdf │ └── pdf.css ├── timeline.png ├── titlepage.html ├── toc.html ├── todo ├── using_strict.png ├── with_inputs.png ├── with_outputs.png └── with_side_effects.png /': -------------------------------------------------------------------------------- 1 | function addOne(addend){ 2 | new Error 'afdsf' 3 | return addend + 1; 4 | } 5 | 6 | function four(){ 7 | return new Promise((resolve, _reject) => { 8 | setTimeout(() => resolve(4), 500); 9 | }) 10 | } 11 | 12 | four() 13 | .then(addOne) 14 | .then((valueFromAddOne) => console.log(valueFromAddOne)); 15 | 16 | 17 | //four() 18 | //.then(addOne) 19 | //.then(addOne) 20 | //.then(addOne) 21 | //.then(console.log); 22 | 23 | //.then(result => addOne(result)) 24 | //.then(result => console.log(result)); 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | //three.then((x) => addOne(x, console.log)); 35 | 36 | //const test = require('tape'); 37 | //const testdouble = require('testdouble'); 38 | 39 | //test('our addOne function', (assert) => { 40 | //addOne(3, (result) => { 41 | //assert.equal(result, 4); 42 | //assert.end(); 43 | //}); 44 | //}); 45 | 46 | //test('our three function', (assert) => { 47 | //three((result, callback) => { 48 | //assert.equal(result, 3); 49 | //assert.equal(callback, console.log); 50 | //assert.end(); 51 | //}); 52 | //}); 53 | //test('our end-to-end test', (assert) => { 54 | //testdouble.replace(console, 'log') 55 | //three((result, callback) => { 56 | //addOne(result, callback) 57 | //testdouble.verify(console.log(4)); 58 | //testdouble.reset(); 59 | //assert.end(); 60 | //}); 61 | //}); 62 | 63 | 64 | //function addOneSync(addend){ 65 | //return addend + 1; 66 | //} 67 | 68 | //test('our addOneSync function', (assert) => { 69 | //assert.equal(addOneSync(3), 4); 70 | //assert.end(); 71 | //}); 72 | 73 | //function three(){ 74 | //setTimeout(function(){ 75 | //return 3 76 | //}, 77 | //500); 78 | //} 79 | //function two(callback){ 80 | //callback(2); 81 | //} 82 | 83 | //two(addOne); 84 | 85 | //addOne(three); 86 | 87 | 88 | //one((addend) => console.log(4 + addend)); 89 | 90 | //x = [1, 2, 3, 4, 5]; 91 | 92 | //function myMap(array, callback){ 93 | //const newArray = []; 94 | //array.forEach(function(element){ 95 | //newArray.push(callback(element)); 96 | //}) 97 | //return newArray; 98 | //} 99 | 100 | //function myMap(array, callback){ 101 | //return array.reduce(function(acc, element){ 102 | //acc.push(callback(element)); 103 | //return acc; 104 | //}, []) 105 | //} 106 | 107 | //y = myMap(x, function(element){ return element * 2}) 108 | //console.log(y); 109 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "import" 5 | ], 6 | 7 | 8 | "rules": { 9 | "space-before-blocks": 0, 10 | "no-bitwise": 1, 11 | "no-extra-semi": 0, 12 | "import/no-extraneous-dependencies": 0, 13 | "import/newline-after-import": 0, 14 | "semi": 2, 15 | "space-before-function-paren": 0, 16 | "brace-style": 0, 17 | "space-infix-ops": 0, 18 | "indent": 0, 19 | "keyword-spacing": 0, 20 | "no-var": 1, 21 | "no-else-return": 0, 22 | "prefer-arrow-callback": 1, 23 | "no-use-before-define": 1, 24 | "prefer-template": 1, 25 | "comma-dangle": 0, 26 | "quotes": 1, 27 | "no-underscore-dangle": 0, 28 | "max-len": ["error", 70], 29 | "operator-assignment": 1, 30 | "no-plusplus": 0, 31 | "no-mixed-operators": 0, 32 | "vars-on-top": 1, 33 | "object-curly-spacing": 0, 34 | "space-in-parens": 0, 35 | "no-param-reassign": 1 // this flags reduce calls 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_* 2 | ._.DS_* 3 | -------------------------------------------------------------------------------- /700c20f2e4b3b5d9258b70cad5e5e9f5.json: -------------------------------------------------------------------------------- 1 | {"debug":[],"info":[],"warn":[],"error":[]} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # refactoringjavascript 2 | The code and text for the book, Refactoring JavaScript 3 | 4 | 5 | This repo is a mirror of the text, rather than a living document. If for no other reason, this is because the book, via the publisher, is the "upstream branch" of this. 6 | 7 | There is a way to submit errata to O'Reilly here: http://shop.oreilly.com/product/0636920053262.do 8 | 9 | If you feel like submitting an issue, although I'm happy that you're engaging with the text and appreciate your ideas, I'm unlikely to accept any PRs. 10 | 11 | But if there's anything in the text that gets in the way of your understanding, you can contact me through refactoringjs.com for questions. I always respond. 12 | -------------------------------------------------------------------------------- /_.js: -------------------------------------------------------------------------------- 1 | //const http = require('http'); 2 | //const getBody = { 3 | //bodyArray: [], 4 | //saveBody: function(chunk){ 5 | //this.bodyArray.push(chunk); 6 | //}, 7 | //getResult: function(result){ 8 | //result.on('data', this.saveBody.bind(this)); 9 | //result.on('end', this.printBody.bind(this)); 10 | //}, 11 | //printBody: function(){ 12 | //console.log(this.bodyArray.join('')) 13 | //this.allDone(); 14 | //}, 15 | //allDone: function(){} 16 | 17 | //}; 18 | //const test = require('tape'); 19 | //test('our async routine', function (assert) { 20 | //getBody.allDone = function(){ 21 | //assert.equal(getBody.bodyArray.length, 2); 22 | //assert.end(); 23 | //} 24 | //http.get('http://refactoringjs.com', getBody.getResult.bind(getBody)); 25 | //}); 26 | 27 | //test('our async routine two', function (assert) { 28 | //getBody.bodyArray = []; 29 | //getBody.allDone = function(){ }; 30 | //http.get('http://refactoringjs.com', getBody.getResult.bind(getBody)); 31 | //assert.equal(getBody.bodyArray.length, 0); 32 | //assert.end(); 33 | //}); 34 | 35 | 36 | //addOne(console.log, four()); 37 | 38 | 39 | //function three(callback, innerCallback){ 40 | //setTimeout(() => { 41 | //callback(3, innerCallback); 42 | //}, 43 | //500); 44 | //} 45 | //three(addOne(console.log)); 46 | //three(addOne); 47 | 48 | four(){j 49 | function addOne(addend, callback){ 50 | callback(addend + 1); 51 | }; 52 | 53 | function three(callback, innerCallback){ 54 | callback(3, innerCallback); 55 | }; 56 | three(addOne, console.log); 57 | 58 | 59 | -------------------------------------------------------------------------------- /_testing_in_action_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/_testing_in_action_.png -------------------------------------------------------------------------------- /appa.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |
11 |

Further Reading and Resources

12 | 13 |

This is a collection of books, tools, videos, and other resources for additional background on the topics covered in Refactoring JavaScript.

14 | 15 |

If any of the links go dead, try them on the wayback machine.

16 | 17 |

For sources without links, try searching the words given (with and without surrounding quotes) and you should be able to find them.

18 | 19 |

Most resources are free, but some are only available for purchase (or with clever searching). In the cases of non-free resources, I have not provided a link and have included the author name for easier searching.

20 | 21 |

Origins of Refactoring

22 | 23 | 28 | 29 |

Baseline JavaScript(s)

30 | 31 | 37 | 38 |

Keeping Up With JavaScript

39 | 40 | 46 | 47 |

JavaScript Reference

48 | 49 | 57 | 58 |

Object-Oriented Programs/Patterns (Including Anti-Class stances)

59 | 60 | 73 | 74 |

Async

75 | 76 | 83 | 84 |

Functional

85 | 86 | 100 | 101 |

Tools

102 | 103 | 120 | 121 |

Other Non-JS specific, but Relevant Sources

122 | 123 | 129 | 130 |

Me

131 | 132 | 135 |
136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /ch12.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |
11 |

Conclusion

12 | 13 |

The techniques we covered ranged from things as small as renaming variables, to as large as applying paradigms like FP and OOP. Between paradigms, architectures, libraries, frameworks, and even individual style your "JavaScript" might be wildly different than someone else's. You might even find yourself in two very different modes from one project to another. If you write a simple 3 line shell script in JavaScript, you might not even want tests or write any functions. You might completely leave off scoping declarations, even a simple var. You might be working with a team that thinks well in OOP. In that case, ES2015 classes and applying appropriate design patterns might be the way to go. For more functional (or just function-curious) teams, FP is a great approach. It doesn't have to be all-or-nothing, and it doesn't have to be scary. Turn your linter off every once in a while.

14 | 15 |

Whether you're making decisions about small stylistic changes or large architectural changes, ideally this book has provided you with a good basis for making choices. The alternatives of simply using a framework, rewriting into another framework, or putting up with JavaScript Jenga are comparatively expensive and demoralizing.

16 | 17 |

"JavaScript" is basically a mythological animal. "Your JavaScript" starts with whatever styles, tools, platforms, paradigms, ideologies, and purposes you decide to pursue. Having a well developed "your JavaScript" opens up a tremendous amount of possibilities.

18 | 19 |

Hopefully, this book has helped you craft "your JavaScript," leading to creating more maintainable, confidence-inspiring code bases.

20 | 21 |

 

22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /code/ch01/1_all.js: -------------------------------------------------------------------------------- 1 | function byTwo(number){ 2 | return number * 2; 3 | } 4 | 5 | function byTwo(number){ 6 | return number << 1; 7 | } 8 | 9 | assert = require('assert'); 10 | assert(2 + 2 === 4); 11 | 12 | _ = require('underscore'); 13 | assert(_.first([3, 2] === 3)); 14 | 15 | function doesThings(args, callback){ 16 | doesOtherThings(args); 17 | doesOtherOtherThings(args, callback); 18 | return 5; 19 | }; 20 | -------------------------------------------------------------------------------- /code/ch04/1_new_code_from_scratch/1_check_hand.js: -------------------------------------------------------------------------------- 1 | var checkHand = function(hand){ 2 | if (checkStraightFlush(hand)){ 3 | return 'straight flush'; 4 | } 5 | else if (checkFourOfKind(hand)){ 6 | return 'four of a kind'; 7 | } 8 | else if (checkFullHouse(hand)){ 9 | return 'full house'; 10 | } 11 | else if (checkFlush(hand)){ 12 | return 'flush'; 13 | } 14 | else if (checkStraight(hand)){ 15 | return 'straight'; 16 | } 17 | else if (checkThreeOfKind(hand)){ 18 | return 'three of a kind'; 19 | } 20 | else if (checkTwoPair(hand)){ 21 | return 'two pair'; 22 | } 23 | else if (checkPair(hand)){ 24 | return 'pair'; 25 | } 26 | else { 27 | return 'high card'; 28 | } 29 | }; 30 | 31 | console.log(checkHand(['2-H', '3-C', '4-D', '5-H', '2-C'])); 32 | -------------------------------------------------------------------------------- /code/ch04/1_new_code_from_scratch/2_check_hand.js: -------------------------------------------------------------------------------- 1 | // not just multiples 2 | checkStraightFlush = function(){ 3 | return false; 4 | }; 5 | checkFullHouse = function(){ 6 | return false; 7 | }; 8 | checkFlush = function(){ 9 | return false; 10 | }; 11 | checkStraight = function(){ 12 | return false; 13 | }; 14 | checkStraightFlush = function(){ 15 | return false; 16 | }; 17 | checkTwoPair = function(){ 18 | return false; 19 | }; 20 | 21 | // just multiples 22 | checkFourOfKind = function(){ 23 | return false; 24 | }; 25 | checkThreeOfKind = function(){ 26 | return false; 27 | }; 28 | checkPair = function(){ 29 | return false; 30 | }; 31 | 32 | // get just the values 33 | var getValues = function(hand){ 34 | console.log(hand); 35 | var values = []; 36 | for(var i=0;i numberOfDuplicates){ 68 | numberOfDuplicates = duplicatesOfThisCard; 69 | } 70 | } 71 | return numberOfDuplicates; 72 | }; 73 | 74 | var checkHand = function(hand){ 75 | var values = getValues(hand); 76 | var number = countDuplicates(values); 77 | console.log(number); 78 | 79 | if (checkStraightFlush(hand)){ 80 | return 'straight flush'; 81 | } 82 | else if (number==4){ 83 | return 'four of a kind'; 84 | } 85 | else if (checkFullHouse(hand)){ 86 | return 'full house'; 87 | } 88 | else if (checkFlush(hand)){ 89 | return 'flush'; 90 | } 91 | else if (checkStraight(hand)){ 92 | return 'straight'; 93 | } 94 | else if (number==3){ 95 | return 'three of a kind'; 96 | } 97 | else if (checkTwoPair(hand)){ 98 | return 'two pair'; 99 | } 100 | else if (number==2){ 101 | return 'pair'; 102 | } 103 | else { 104 | return 'high card'; 105 | } 106 | }; 107 | // debugger; 108 | var assert = require('assert'); 109 | assert(checkHand(['2-H', '3-C', '4-D', '5-H', '2-C'])==='pair'); 110 | assert(checkHand(['3-H', '3-C', '3-D', 111 | '5-H', '2-H'])==='three of a kind'); 112 | -------------------------------------------------------------------------------- /code/ch04/2_new_code_tdd/1_new_code_tdd.js: -------------------------------------------------------------------------------- 1 | var checkHand = function(hand){ 2 | if(hand[0]==='2-H' && hand[1]==='3-C' 3 | && hand[2]==='4-D' && hand[3]==='5-H' 4 | && hand[4]==='2-C'){ 5 | return 'pair'; 6 | }else{ 7 | return 'three of a kind'; 8 | } 9 | }; 10 | var wish = require('wish'); 11 | wish(checkHand(['2-H', '3-C', '4-D', '5-H', '2-C'])==='pair'); 12 | wish(checkHand(['3-H', '3-C', '3-D', '5-H', '2-H'])==='three of a kind'); 13 | -------------------------------------------------------------------------------- /code/ch04/2_new_code_tdd/2_check_hand_with_mocha.js: -------------------------------------------------------------------------------- 1 | var wish = require('wish'); 2 | var deepEqual = require('deep-equal'); 3 | 4 | function checkHand(hand) { 5 | if(isTwoPair(hand)){ 6 | return 'two pair'; 7 | } else if(isPair(hand)){ 8 | return 'pair'; 9 | }else if(isFullHouse(hand)){ 10 | return 'full house'; 11 | }else if(isTriple(hand)){ 12 | return 'three of a kind'; 13 | }else if(isQuadruple(hand)){ 14 | return 'four of a kind'; 15 | }else if(isStraightFlush(hand)){ 16 | return 'straight flush'; 17 | }else if(isFlush(hand)){ 18 | return 'flush'; 19 | }else if(isStraight(hand)){ 20 | return 'straight'; 21 | }else{ 22 | return 'high card'; 23 | } 24 | }; 25 | 26 | function isTwoPair(hand){ 27 | var theCounts = allCounts(valuesFromHand(hand)); 28 | return(theCounts[0]===2 && theCounts[1]===2); 29 | }; 30 | 31 | function allCounts(values){ 32 | var counts = {}; 33 | values.forEach(function(value, index){ 34 | counts[value]= 0; 35 | if(value == values[0]){ 36 | counts[value] = counts[value] + 1; 37 | }; 38 | if(value == values[1]){ 39 | counts[value] = counts[value] + 1; 40 | }; 41 | if(value == values[2]){ 42 | counts[value] = counts[value] + 1; 43 | }; 44 | if(value == values[3]){ 45 | counts[value] = counts[value] + 1; 46 | }; 47 | if(value == values[4]){ 48 | counts[value] = counts[value] + 1; 49 | }; 50 | }); 51 | var totalCounts = Object.keys(counts).map(function(key){ 52 | return counts[key]; 53 | }); 54 | return totalCounts.sort(function(a,b){return b-a}); 55 | }; 56 | 57 | function isFullHouse(hand){ 58 | var theCounts = allCounts(valuesFromHand(hand)); 59 | return(theCounts[0]===3 && theCounts[1]===2); 60 | }; 61 | 62 | function isStraightFlush(hand){ 63 | return isStraight(hand) && isFlush(hand); 64 | } 65 | 66 | function cardsInSequence(values){ 67 | var sortedValues = values.sort(); 68 | return fourAway(sortedValues) && noMultiples(values); 69 | }; 70 | 71 | function isStraight(hand){ 72 | return cardsInSequence(valuesFromHand(hand)); 73 | }; 74 | 75 | function isFlush(hand){ 76 | return allTheSameSuit(suitsFor(hand)); 77 | }; 78 | 79 | function isQuadruple(hand){ 80 | return multiplesIn(hand) === 4; 81 | }; 82 | 83 | function isPair(hand){ 84 | return multiplesIn(hand) === 2; 85 | }; 86 | 87 | function isTriple(hand){ 88 | return multiplesIn(hand) === 3; 89 | }; 90 | 91 | function multiplesIn(hand){ 92 | return highestCount(valuesFromHand(hand)); 93 | }; 94 | 95 | function highestCount(values){ 96 | var counts = {}; 97 | values.forEach(function(value, index){ 98 | counts[value]= 0; 99 | if(value == values[0]){ 100 | counts[value] = counts[value] + 1; 101 | }; 102 | if(value == values[1]){ 103 | counts[value] = counts[value] + 1; 104 | }; 105 | if(value == values[2]){ 106 | counts[value] = counts[value] + 1; 107 | }; 108 | if(value == values[3]){ 109 | counts[value] = counts[value] + 1; 110 | }; 111 | if(value == values[4]){ 112 | counts[value] = counts[value] + 1; 113 | }; 114 | }); 115 | var totalCounts = Object.keys(counts).map(function(key){ 116 | return counts[key]; 117 | }); 118 | return totalCounts.sort(function(a,b){return b-a})[0]; 119 | }; 120 | 121 | function valuesFromHand(hand){ 122 | return hand.map(function(card){ 123 | return card.split('-')[0]; 124 | }) 125 | }; 126 | 127 | function suitsFor(hand){ 128 | return hand.map(function(card){ 129 | return card.split('-')[1]; 130 | }) 131 | }; 132 | 133 | function allTheSameSuit(suits){ 134 | var toReturn = true; 135 | suits.forEach(function(suit){ 136 | if(suit !== suits[0]){ 137 | toReturn = false; 138 | } 139 | }) 140 | return toReturn; 141 | } 142 | function fourAway(values){ 143 | return ((+values[values.length-1] - 4 - +values[0])===0); 144 | }; 145 | function noMultiples(values){ 146 | return highestCount(values)==1; 147 | }; 148 | 149 | describe('noMultiples()', function() { 150 | it('reports true when all elements are different', function() { 151 | var result = noMultiples(['2', '6']); 152 | wish(result); 153 | }); 154 | it('reports false when two elements are the same', function() { 155 | var result = noMultiples(['2', '2']); 156 | wish(!result); 157 | }); 158 | }); 159 | describe('fourAway()', function() { 160 | it('reports true if first and last are 4 away', function() { 161 | var result = fourAway(['2', '6']); 162 | wish(result); 163 | }); 164 | }); 165 | describe('allTheSameSuit()', function() { 166 | it('reports true if elements are the same', function() { 167 | var result = allTheSameSuit(['D', 'D', 'D', 'D', 'D']); 168 | wish(result); 169 | }); 170 | it('reports false if elements are not the same', function() { 171 | var result = allTheSameSuit(['D', 'H', 'D', 'D', 'D']); 172 | wish(!result); 173 | }); 174 | 175 | }); 176 | 177 | describe('valuesFromHand()', function() { 178 | it('returns just the values from a hand', function() { 179 | var result = valuesFromHand(['2-H', '3-C', '4-D', '5-H', '2-C']); 180 | wish(deepEqual(result, ['2', '3', '4', '5', '2'])); 181 | }); 182 | }); 183 | 184 | describe('highestCount()', function() { 185 | it('returns count of the most common card from array', 186 | function() { 187 | var result = highestCount(['2', '4', '4', '4', '2']); 188 | wish(result === 3); 189 | } 190 | ); 191 | }); 192 | 193 | describe('multiplesIn()', function() { 194 | it('finds a duplicate', function() { 195 | var result = multiplesIn(['2-H', '3-C', '4-D', '5-H', '2-C']); 196 | wish(result === 2); 197 | }); 198 | }); 199 | describe('isPair()', function() { 200 | it('finds a pair', function() { 201 | var result = isPair(['2-H', '3-C', '4-D', '5-H', '2-C']); 202 | wish(result); 203 | }); 204 | }); 205 | 206 | 207 | describe('checkHand()', function() { 208 | it('handles pairs', function() { 209 | var result = checkHand(['2-H', '3-C', '4-D', '5-H', '2-C']); 210 | wish(result === 'pair'); 211 | 212 | var anotherResult = checkHand(['3-H', '3-C', 213 | '4-D', '5-H', '2-C']); 214 | wish(anotherResult === 'pair'); 215 | }); 216 | it('handles three of a kind', function() { 217 | var result = checkHand(['3-H', '3-C', '3-D', '5-H', '2-H']); 218 | wish(result === 'three of a kind'); 219 | }); 220 | it('handles four of a kind', function() { 221 | var result = checkHand(['3-H', '3-C', '3-D', '3-S', '2-H']); 222 | wish(result === 'four of a kind'); 223 | }); 224 | it('handles high card', function() { 225 | var result = checkHand(['2-H', '5-C', '9-D', '7-S', '3-H']); 226 | wish(result === 'high card'); 227 | }); 228 | it('handles flush', function() { 229 | var result = checkHand(['2-H', '5-H', '9-H', '7-H', '3-H']); 230 | wish(result === 'flush'); 231 | }); 232 | it('handles straight', function() { 233 | var result = checkHand(['1-H', '2-H', '3-H', '4-H', '5-D']); 234 | wish(result === 'straight'); 235 | }); 236 | it('handles straight flush', function() { 237 | var result = checkHand(['1-H', '2-H', '3-H', '4-H', '5-H']); 238 | wish(result === 'straight flush'); 239 | }); 240 | it('handles full house', function() { 241 | var result = checkHand(['2-D', '2-H', '3-H', '3-D', '3-C']); 242 | wish(result === 'full house'); 243 | }); 244 | it('handles two pair', function() { 245 | var result = checkHand(['2-D', '2-H', '3-H', '3-D', '8-D']); 246 | wish(result === 'two pair'); 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /code/ch04/3_untested_code/1_random_hand.js: -------------------------------------------------------------------------------- 1 | var s = ['H', 'D', 'S', 'C']; 2 | var v = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; 3 | var c = []; 4 | var rS = function(){ 5 | return s[Math.floor(Math.random()*(s.length))]; 6 | }; 7 | var rV = function(){ 8 | return v[Math.floor(Math.random()*(v.length))]; 9 | }; 10 | var rC = function(){ 11 | return rV() + '-' + rS(); 12 | }; 13 | 14 | var doIt = function(){ 15 | c.push(rC()); 16 | c.push(rC()); 17 | c.push(rC()); 18 | c.push(rC()); 19 | c.push(rC()); 20 | }; 21 | doIt(); 22 | console.log(c); 23 | -------------------------------------------------------------------------------- /code/ch04/3_untested_code/2_random_hand.js: -------------------------------------------------------------------------------- 1 | var s = ['H', 'D', 'S', 'C']; 2 | var v = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; 3 | var c = []; 4 | var rS = function(){ 5 | return s[Math.floor(Math.random()*(s.length))]; 6 | }; 7 | var rV = function(){ 8 | return v[Math.floor(Math.random()*(v.length))]; 9 | }; 10 | var rC = function(){ 11 | return rV() + '-' + rS(); 12 | }; 13 | 14 | var doIt = function(){ 15 | var c = []; 16 | c.push(rC()); 17 | c.push(rC()); 18 | c.push(rC()); 19 | c.push(rC()); 20 | c.push(rC()); 21 | return c; 22 | }; 23 | console.log(doIt()); 24 | 25 | const wish = require('wish'); 26 | describe('doIt()', function() { 27 | it('returns something with a length of 5', function() { 28 | wish(doIt().length === 5); 29 | }); 30 | }); 31 | describe('rC()', function() { 32 | it('returns match for card', function() { 33 | wish(rC().match(/\w{1,2}-[HDSC]/)); 34 | }); 35 | }); 36 | describe('rV()', function() { 37 | it('returns match for card value', function() { 38 | wish(rV().match(/\w{1,2}/)); 39 | }); 40 | }); 41 | describe('rS()', function() { 42 | it('returns match for suit', function() { 43 | wish(rS().match(/[HDSC]/)); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /code/ch04/4_debugging_regression/1_random_hand.js: -------------------------------------------------------------------------------- 1 | var suits = ['H', 'D', 'S', 'C']; 2 | var values = ['1', '2', '3', '4', '5', '6', 3 | '7', '8', '9', '10', 'J', 'Q', 'K']; 4 | var randomSuit = function(){ 5 | return suits[Math.floor(Math.random()*(suits.length))]; 6 | }; 7 | var randomValue = function(){ 8 | return values[Math.floor(Math.random()*(values.length))]; 9 | }; 10 | var randomCard = function(){ 11 | return randomValue() + '-' + randomSuit(); 12 | }; 13 | 14 | var randomHand = function(){ 15 | var cards = []; 16 | var cardArray = buildCardArray(); 17 | [cards[0], cardArray] = spliceCard(cardArray); 18 | [cards[1], cardArray] = spliceCard(cardArray); 19 | [cards[2], cardArray] = spliceCard(cardArray); 20 | [cards[3], cardArray] = spliceCard(cardArray); 21 | [cards[4], cardArray] = spliceCard(cardArray); 22 | return cards; 23 | }; 24 | 25 | var buildCardArray = function(){ 26 | var tempArray = []; 27 | for(var i=0; i < values.length; i++){ 28 | for(var j=0; j < suits.length; j++){ 29 | tempArray.push(values[i]+'-'+suits[j]) 30 | } 31 | }; 32 | return tempArray; 33 | }; 34 | var spliceCard = function(cardArray){ 35 | var takeAway = cardArray.splice( 36 | Math.floor(Math.random()*cardArray.length), 1)[0]; 37 | return [takeAway, cardArray]; 38 | }; 39 | console.log(randomHand()); 40 | 41 | var wish = require('wish'); 42 | var deepEqual = require('deep-equal'); 43 | 44 | describe('randomHand()', function() { 45 | it('returns 5 randomCards', function() { 46 | wish(randomHand().length === 5); 47 | }); 48 | for(var i=0; i<100; i++){ 49 | it('should not have the first two cards be the same', function() { 50 | var result = randomHand(); 51 | wish(result[0] !== result[1]); 52 | }); 53 | }; 54 | }); 55 | describe('randomCard()', function() { 56 | it('returns nothing', function() { 57 | wish(randomCard().match(/\w{1,2}-[HDSC]/)); 58 | }); 59 | }); 60 | describe('randomValue()', function() { 61 | it('returns nothing', function() { 62 | wish(randomValue().match(/\w{1,2}/)); 63 | }); 64 | }); 65 | describe('randomSuit()', function() { 66 | it('returns nothing', function() { 67 | wish(randomSuit().match(/[HDSC]/)); 68 | }); 69 | }); 70 | describe('buildCardArray()', function() { 71 | it('gives a card array', function() { 72 | wish(deepEqual(buildCardArray(), [ '1-H', '1-D', '1-S', '1-C', 73 | '2-H', '2-D', '2-S', '2-C', 74 | '3-H', '3-D', '3-S', '3-C', '4-H', '4-D', '4-S', '4-C', 75 | '5-H', '5-D', '5-S', '5-C', '6-H', '6-D', '6-S', '6-C', 76 | '7-H', '7-D', '7-S', '7-C', '8-H', '8-D', '8-S', '8-C', 77 | '9-H', '9-D', '9-S', '9-C', '10-H', '10-D', '10-S', '10-C', 78 | 'J-H', 'J-D', 'J-S', 'J-C', 'Q-H', 'Q-D', 'Q-S', 'Q-C', 79 | 'K-H', 'K-D', 'K-S', 'K-C' ])); 80 | }); 81 | it('returns a full deck', function() { 82 | wish(buildCardArray().length === 52); 83 | }); 84 | }); 85 | describe('spliceCard()', function() { 86 | it('returns two things', function() { 87 | wish(spliceCard(buildCardArray()).length === 2); 88 | }); 89 | it('returns the selected card', function() { 90 | wish(spliceCard(buildCardArray())[0].match(/\w{1,2}-[HDSC]/)); 91 | }); 92 | it('returns an array with one card gone', function() { 93 | wish(spliceCard(buildCardArray())[1].length === 94 | buildCardArray().length - 1); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /code/ch05/1_general/general.js: -------------------------------------------------------------------------------- 1 | var calculator = { 2 | add: function(a, b){ 3 | return a + b; 4 | }, 5 | addTwo: function(a){ 6 | return this.add(a, this.two); 7 | }, 8 | two: 2 9 | }; 10 | 11 | var name = "Max"; 12 | var punctuation = "!"; 13 | function sayHi(){ 14 | return "Hi " + name + punctuation; 15 | }; 16 | 17 | function trueForTruthyThings(sometimesTruthyThing){ 18 | return !!(sometimesTruthyThing); 19 | }; 20 | 21 | function add(a, b){ 22 | return a + b; 23 | }; 24 | 25 | function doesSomething(options){ 26 | if(options.a){ 27 | var a = options.a; 28 | } 29 | if(options.b){ 30 | var b = options.b; 31 | } 32 | // ... 33 | } 34 | 35 | search('something', 20); 36 | //vs. 37 | search({query: 'something', pageSize: 20}); 38 | 39 | function search({query: query, pageSize: pageSize}){ 40 | console.log(query, pageSize); 41 | }; 42 | search({query: 'something', pageSize: 20}); 43 | 44 | function add(a, b){ 45 | a + b; 46 | }; 47 | 48 | function moreThanThree(number){ 49 | if(number > 3){ 50 | return true; 51 | } else { 52 | return "No. The number was only " + number + "."; 53 | } 54 | }; 55 | 56 | function printValue(value){ 57 | console.log(value); 58 | }; 59 | -------------------------------------------------------------------------------- /code/ch05/2_context/1_background.js: -------------------------------------------------------------------------------- 1 | console.log('blah'); 2 | this.console.log('blah'); 3 | window.console.log('blah'); // in a browser 4 | global.console.log('blah'); // in a node shell 5 | 6 | var x = function(){ 7 | console.log(this); 8 | }; 9 | x(); // here, we'll see our global object, even in node files 10 | 11 | var x = function(){ 12 | 'use strict' 13 | console.log(this); 14 | } 15 | x(); 16 | 'use strict' 17 | var x = function(){ 18 | console.log(this); 19 | } 20 | x(); 21 | -------------------------------------------------------------------------------- /code/ch05/2_context/2_another_object_variants.js: -------------------------------------------------------------------------------- 1 | 2 | var anObject = { 3 | number: 5, 4 | getNumber: function(){ return this.number } 5 | } 6 | 7 | console.log(anObject.getNumber()); 8 | 9 | 10 | var anObject = 11 | Object.create(null, {"number": {value: 5}, 12 | "getNumber": {value: function(){return this.number}}}) 13 | console.log(anObject.getNumber()); 14 | 15 | 16 | class AnObject{ 17 | constructor(){ 18 | this.number = 5; 19 | this.getNumber = function(){return this.number} 20 | } 21 | } 22 | anObject = new AnObject; 23 | console.log(anObject.getNumber()); 24 | 25 | var anObject = { 26 | number: 5 27 | } 28 | var anotherObject = { 29 | getNumber: function(){ return this.number } 30 | } 31 | console.log(anotherObject.getNumber.call(anObject)); 32 | console.log(anotherObject.getNumber.apply(anObject)); 33 | var callForTheFirstObject = anotherObject.getNumber.bind(anObject); 34 | console.log(callForTheFirstObject()); 35 | 36 | anotherObject.getNumber.call(anObject); 37 | 38 | anotherObject.getNumber(); 39 | 40 | var anObject = { 41 | number: 5 42 | } 43 | var anotherObject = { 44 | getIt: function(){ return this.number }, 45 | setIt: function(value){ this.number = value; return this; } 46 | } 47 | console.log(anotherObject.setIt.call(anObject, 3)); 48 | -------------------------------------------------------------------------------- /code/ch05/2_context/3_fluent_and_composed.js: -------------------------------------------------------------------------------- 1 | // fluent 2 | object.setIt(3).setIt(4).setIt(5); 3 | 4 | // composed 5 | f(g(h())); 6 | -------------------------------------------------------------------------------- /code/ch05/3_context/1_diary.js: -------------------------------------------------------------------------------- 1 | var diary = (function(){ 2 | var key = 12345; 3 | var secrets = 'rosebud'; 4 | 5 | function privateUnlock(keyAttempt){ 6 | if(key===keyAttempt){ 7 | console.log('unlocked'); 8 | diary.open = true; 9 | }else{ 10 | console.log('no'); 11 | } 12 | }; 13 | 14 | function privateTryLock(keyAttempt){ 15 | privateUnlock(keyAttempt); 16 | }; 17 | 18 | function privateRead(){ 19 | if(this.open){ 20 | console.log(secrets); 21 | }else{ 22 | console.log('no'); 23 | } 24 | }; 25 | 26 | return { 27 | open: false, 28 | read: privateRead, 29 | tryLock: privateTryLock 30 | } 31 | 32 | })(); 33 | 34 | // run with 35 | diary.tryLock(12345); 36 | diary.read(); 37 | -------------------------------------------------------------------------------- /code/ch05/3_context/2_diary.js: -------------------------------------------------------------------------------- 1 | var diary = (function(){ 2 | var key = 12345; 3 | var secrets = 'programming is just syntactic sugar for labor'; 4 | 5 | function privateUnlock(keyAttempt, that){ 6 | if(key===keyAttempt){ 7 | console.log('unlocked'); 8 | that.open = true; 9 | }else{ 10 | console.log('no'); 11 | } 12 | }; 13 | 14 | function privateTryLock(keyAttempt){ 15 | privateUnlock(keyAttempt, this); 16 | }; 17 | 18 | function privateRead(){ 19 | if(this.open){ 20 | console.log(secrets); 21 | }else{ 22 | console.log('no'); 23 | } 24 | } 25 | 26 | return { 27 | open: false, 28 | read: privateRead, 29 | tryLock: privateTryLock 30 | } 31 | 32 | })(); 33 | diary.tryLock(12345); 34 | diary.read(); 35 | -------------------------------------------------------------------------------- /code/ch05/3_context/3_diary.js: -------------------------------------------------------------------------------- 1 | var diary = (function(){ 2 | var key = 12345; 3 | var secrets = 'sitting for 8 hrs/day straight considered harmful'; 4 | 5 | function privateUnlock(keyAttempt){ 6 | if(key===keyAttempt){ 7 | console.log('unlocked'); 8 | this.open = true; 9 | }else{ 10 | console.log('no'); 11 | } 12 | }; 13 | 14 | function privateTryLock(keyAttempt){ 15 | privateUnlock.call(this, keyAttempt); 16 | }; 17 | 18 | function privateRead(){ 19 | if(this.open){ 20 | console.log(secrets); 21 | }else{ 22 | console.log('no'); 23 | } 24 | }; 25 | 26 | return { 27 | open: false, 28 | read: privateRead, 29 | tryLock: privateTryLock 30 | }; 31 | 32 | })(); 33 | diary.tryLock(12345); 34 | diary.read(); 35 | 36 | // bind variants 37 | // function privateTryLock(keyAttempt){ 38 | // var boundUnlock = privateUnlock.bind(this); 39 | // boundUnlock(keyAttempt); 40 | // }; 41 | // 42 | // function privateTryLock(keyAttempt){ 43 | // privateUnlock.bind(this)(keyAttempt); 44 | // }; 45 | -------------------------------------------------------------------------------- /code/ch05/3_context/4_diary.js: -------------------------------------------------------------------------------- 1 | class Diary { 2 | constructor(){ 3 | this.open = false; 4 | this._key = 12345; 5 | this._secrets = 'the average human lives around 1000 months'; 6 | }; 7 | 8 | _unlock(keyAttempt){ 9 | if(this._key===keyAttempt){ 10 | console.log('unlocked'); 11 | this.open = true; 12 | }else{ 13 | console.log('no'); 14 | } 15 | }; 16 | tryLock(keyAttempt){ 17 | this._unlock(keyAttempt); 18 | }; 19 | 20 | read(){ 21 | if(this.open){ 22 | console.log(this._secrets); 23 | }else{ 24 | console.log('no') 25 | } 26 | } 27 | } 28 | d = new Diary(); 29 | d.tryLock(12345); 30 | d.read(); 31 | -------------------------------------------------------------------------------- /code/ch05/3_context/5_diary.js: -------------------------------------------------------------------------------- 1 | var key = 12345; 2 | var secrets = 'rectangles are popular with people, but not nature'; 3 | function globalUnlock(keyAttempt){ 4 | if(key===keyAttempt){ 5 | console.log('unlocked'); 6 | this.open = true; 7 | }else{ 8 | console.log('no'); 9 | } 10 | }; 11 | 12 | class Diary { 13 | constructor(){ 14 | this.open = false; 15 | }; 16 | tryLock(keyAttempt){ 17 | globalUnlock.bind(this)(keyAttempt); 18 | }; 19 | read(){ 20 | if(this.open){ 21 | console.log(secrets); 22 | }else{ 23 | console.log('no'); 24 | } 25 | } 26 | }; 27 | d = new Diary(); 28 | d.tryLock(12345); 29 | d.read(); 30 | -------------------------------------------------------------------------------- /code/ch05/3_context/6_diary_module.js: -------------------------------------------------------------------------------- 1 | var key = 12345; 2 | var secrets='how to win friends/influence people is for psychopaths'; 3 | function globalUnlock(keyAttempt){ 4 | if(key===keyAttempt){ 5 | console.log('unlocked'); 6 | this.open = true; 7 | }else{ 8 | console.log('no'); 9 | } 10 | }; 11 | 12 | module.exports = class Diary { 13 | constructor(){ 14 | this.open = false; 15 | }; 16 | 17 | tryLock(keyAttempt){ 18 | globalUnlock.bind(this)(keyAttempt); 19 | }; 20 | 21 | read(){ 22 | if(this.open){ 23 | console.log(secrets); 24 | }else{ 25 | console.log('no') 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /code/ch05/3_context/7_diary_reader.js: -------------------------------------------------------------------------------- 1 | const Diary = require('./diary_module.js'); 2 | let d = new Diary(); 3 | d.tryLock(12345); 4 | d.read(); 5 | -------------------------------------------------------------------------------- /code/ch05/3_context/8_possible_future_privacy.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | #a; 3 | #b; 4 | #sum() { return #a + #b; } 5 | printSum() { console.log(#sum()); } 6 | constructor(a, b) { #a = a; #b = b; } 7 | }; 8 | -------------------------------------------------------------------------------- /code/ch05/3_context/short-parts.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | console.log('hi'); 3 | })(); 4 | 5 | (function(){ 6 | (function(){ 7 | console.log('hi'); 8 | })(); 9 | })(); 10 | 11 | function hi(){ 12 | console.log(hello); 13 | }; 14 | hi(); 15 | 16 | // ReferenceError: hello is not defined 17 | 18 | var hello = "hi"; 19 | hi(); 20 | // logs "hi" 21 | -------------------------------------------------------------------------------- /code/ch06/1_initial.js: -------------------------------------------------------------------------------- 1 | fs = require('fs'); 2 | // songs 3 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 4 | somewhere_over_the_rainbow = ['c', 'em', 'f', 'g', 'am']; 5 | tooManyCooks = ['c', 'g', 'f']; 6 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 7 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 8 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 9 | army = ['ab', 'ebm7', 'dbadd9', 'fm7', 'bbm', 'abmaj7', 'ebm']; 10 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 11 | 'em7', 'a7', 'f7', 'b']; 12 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 13 | 'g7']; 14 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 15 | song_11 = []; 16 | 17 | var songs = []; 18 | var labels = []; 19 | var allChords = []; 20 | var labelCounts = []; 21 | var labelProbabilities = []; 22 | var chordCountsInLabels = {}; 23 | var probabilityOfChordsInLabels = {}; 24 | 25 | function train(chords, label){ 26 | songs.push([label, chords]); 27 | labels.push(label); 28 | for (var i = 0; i < chords.length; i++){ 29 | if(!allChords.includes(chords[i])){ 30 | allChords.push(chords[i]); 31 | } 32 | } 33 | if(!!(Object.keys(labelCounts).includes(label))){ 34 | labelCounts[label] = labelCounts[label] + 1; 35 | } else { 36 | labelCounts[label] = 1; 37 | } 38 | }; 39 | 40 | function getNumberOfSongs(){ 41 | return songs.length; 42 | }; 43 | 44 | function setLabelProbabilities(){ 45 | Object.keys(labelCounts).forEach(function(label){ 46 | var numberOfSongs = getNumberOfSongs(); 47 | labelProbabilities[label] = labelCounts[label] / numberOfSongs; 48 | }); 49 | }; 50 | 51 | function setChordCountsInLabels(){ 52 | songs.forEach(function(i){ 53 | if(chordCountsInLabels[i[0]] === undefined){ 54 | chordCountsInLabels[i[0]] = {}; 55 | } 56 | i[1].forEach(function(j){ 57 | if(chordCountsInLabels[i[0]][j] > 0){ 58 | chordCountsInLabels[i[0]][j] = 59 | chordCountsInLabels[i[0]][j] + 1; 60 | } else { 61 | chordCountsInLabels[i[0]][j] = 1; 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | function setProbabilityOfChordsInLabels(){ 68 | probabilityOfChordsInLabels = chordCountsInLabels; 69 | Object.keys(probabilityOfChordsInLabels).forEach(function(i){ 70 | Object.keys(probabilityOfChordsInLabels[i]).forEach(function(j){ 71 | probabilityOfChordsInLabels[i][j] = 72 | probabilityOfChordsInLabels[i][j] * 1.0 / songs.length; 73 | }); 74 | }); 75 | } 76 | 77 | train(imagine, 'easy'); 78 | train(somewhere_over_the_rainbow, 'easy'); 79 | train(tooManyCooks, 'easy'); 80 | train(iWillFollowYouIntoTheDark, 'medium'); 81 | train(babyOneMoreTime, 'medium'); 82 | train(creep, 'medium'); 83 | train(paperBag, 'hard'); 84 | train(toxic, 'hard'); 85 | train(bulletproof, 'hard'); 86 | 87 | setLabelProbabilities(); 88 | setChordCountsInLabels(); 89 | setProbabilityOfChordsInLabels(); 90 | 91 | 92 | 93 | function classify(chords){ 94 | var ttal = labelProbabilities; 95 | console.log(ttal); 96 | var classified = {}; 97 | Object.keys(ttal).forEach(function(obj){ 98 | var first = labelProbabilities[obj] + 1.01; 99 | chords.forEach(function(chord){ 100 | var probabilityOfChordInLabel = 101 | probabilityOfChordsInLabels[obj][chord]; 102 | if(probabilityOfChordInLabel === undefined){ 103 | first + 1.01; 104 | } else { 105 | first = first * (probabilityOfChordInLabel + 1.01); 106 | } 107 | }); 108 | classified[obj] = first; 109 | }); 110 | console.log(classified); 111 | }; 112 | 113 | classify(['d', 'g', 'e', 'dm']); 114 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 115 | -------------------------------------------------------------------------------- /code/ch06/2_after_renaming.js: -------------------------------------------------------------------------------- 1 | fs = require('fs'); 2 | // songs 3 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 4 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 5 | tooManyCooks = ['c', 'g', 'f']; 6 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 7 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 8 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 9 | army = ['ab', 'ebm7', 'dbadd9', 'fm7', 'bbm', 'abmaj7', 'ebm']; 10 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 11 | 'em7', 'a7', 'f7', 'b']; 12 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 13 | 'g7']; 14 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 15 | blankSong = []; 16 | 17 | var songs = []; 18 | var labels = []; 19 | var allChords = []; 20 | var labelCounts = []; 21 | var labelProbabilities = []; 22 | var chordCountsInLabels = {}; 23 | var probabilityOfChordsInLabels = {}; 24 | 25 | function train(chords, label){ 26 | songs.push([label, chords]); 27 | labels.push(label); 28 | for (var index = 0; index < chords.length; index++){ 29 | if(!allChords.includes(chords[index])){ 30 | allChords.push(chords[index]); 31 | } 32 | } 33 | if(!!(Object.keys(labelCounts).includes(label))){ 34 | labelCounts[label] = labelCounts[label] + 1; 35 | } else { 36 | labelCounts[label] = 1; 37 | } 38 | }; 39 | 40 | function getNumberOfSongs(){ 41 | return songs.length; 42 | }; 43 | 44 | function setLabelProbabilities(){ 45 | Object.keys(labelCounts).forEach(function(label){ 46 | var numberOfSongs = getNumberOfSongs(); 47 | labelProbabilities[label] = labelCounts[label] / numberOfSongs; 48 | }); 49 | }; 50 | 51 | function setChordCountsInLabels(){ 52 | songs.forEach(function(song){ 53 | if(chordCountsInLabels[song[0]] === undefined){ 54 | chordCountsInLabels[song[0]] = {}; 55 | } 56 | song[1].forEach(function(chord){ 57 | if(chordCountsInLabels[song[0]][chord] > 0){ 58 | chordCountsInLabels[song[0]][chord] = 59 | chordCountsInLabels[song[0]][chord] + 1; 60 | } else { 61 | chordCountsInLabels[song[0]][chord] = 1; 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | function setProbabilityOfChordsInLabels(){ 68 | probabilityOfChordsInLabels = chordCountsInLabels; 69 | Object.keys(probabilityOfChordsInLabels).forEach(function(difficulty){ 70 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(function(chord){ 71 | probabilityOfChordsInLabels[difficulty][chord] = 72 | probabilityOfChordsInLabels[difficulty][chord] * 1.0 / songs.length; 73 | }); 74 | }); 75 | } 76 | 77 | train(imagine, 'easy'); 78 | train(somewhereOverTheRainbow, 'easy'); 79 | train(tooManyCooks, 'easy'); 80 | train(iWillFollowYouIntoTheDark, 'medium'); 81 | train(babyOneMoreTime, 'medium'); 82 | train(creep, 'medium'); 83 | train(paperBag, 'hard'); 84 | train(toxic, 'hard'); 85 | train(bulletproof, 'hard'); 86 | 87 | setLabelProbabilities(); 88 | setChordCountsInLabels(); 89 | setProbabilityOfChordsInLabels(); 90 | 91 | 92 | 93 | function classify(chords){ 94 | var total = labelProbabilities; 95 | console.log(total); 96 | var classified = {}; 97 | Object.keys(total).forEach(function(difficulty){ 98 | var first = labelProbabilities[difficulty] + 1.01; 99 | chords.forEach(function(chord){ 100 | var probabilityOfChordInLabel = 101 | probabilityOfChordsInLabels[difficulty][chord]; 102 | if(probabilityOfChordInLabel === undefined){ 103 | first + 1.01; 104 | } else { 105 | first = first * (probabilityOfChordInLabel + 1.01); 106 | } 107 | }); 108 | classified[difficulty] = first; 109 | }); 110 | console.log(classified); 111 | }; 112 | 113 | classify(['d', 'g', 'e', 'dm']); 114 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 115 | -------------------------------------------------------------------------------- /code/ch06/3_after_useless_code.js: -------------------------------------------------------------------------------- 1 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 2 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 3 | tooManyCooks = ['c', 'g', 'f']; 4 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 5 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 6 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 7 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 8 | 'em7', 'a7', 'f7', 'b']; 9 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 10 | 'g7']; 11 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 12 | 13 | var songs = []; 14 | var labels = []; 15 | var allChords = []; 16 | var labelCounts = []; 17 | var labelProbabilities = []; 18 | var chordCountsInLabels = {}; 19 | var probabilityOfChordsInLabels = {}; 20 | 21 | function train(chords, label){ 22 | songs.push([label, chords]); 23 | labels.push(label); 24 | for (var index = 0; index < chords.length; index++){ 25 | if(!allChords.includes(chords[index])){ 26 | allChords.push(chords[index]); 27 | } 28 | } 29 | if(Object.keys(labelCounts).includes(label)){ 30 | labelCounts[label] = labelCounts[label] + 1; 31 | } else { 32 | labelCounts[label] = 1; 33 | } 34 | }; 35 | 36 | function getNumberOfSongs(){ 37 | return songs.length; 38 | }; 39 | 40 | function setLabelProbabilities(){ 41 | Object.keys(labelCounts).forEach(function(label){ 42 | var numberOfSongs = getNumberOfSongs(); 43 | labelProbabilities[label] = labelCounts[label] / numberOfSongs; 44 | }); 45 | }; 46 | 47 | function setChordCountsInLabels(){ 48 | songs.forEach(function(song){ 49 | if(chordCountsInLabels[song[0]] === undefined){ 50 | chordCountsInLabels[song[0]] = {}; 51 | } 52 | song[1].forEach(function(chord){ 53 | if(chordCountsInLabels[song[0]][chord] > 0){ 54 | chordCountsInLabels[song[0]][chord] = 55 | chordCountsInLabels[song[0]][chord] + 1; 56 | } else { 57 | chordCountsInLabels[song[0]][chord] = 1; 58 | } 59 | }); 60 | }); 61 | } 62 | 63 | function setProbabilityOfChordsInLabels(){ 64 | probabilityOfChordsInLabels = chordCountsInLabels; 65 | Object.keys(probabilityOfChordsInLabels).forEach(function(difficulty){ 66 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(function(chord){ 67 | probabilityOfChordsInLabels[difficulty][chord] = 68 | probabilityOfChordsInLabels[difficulty][chord] / songs.length; 69 | }); 70 | }); 71 | } 72 | 73 | train(imagine, 'easy'); 74 | train(somewhereOverTheRainbow, 'easy'); 75 | train(tooManyCooks, 'easy'); 76 | train(iWillFollowYouIntoTheDark, 'medium'); 77 | train(babyOneMoreTime, 'medium'); 78 | train(creep, 'medium'); 79 | train(paperBag, 'hard'); 80 | train(toxic, 'hard'); 81 | train(bulletproof, 'hard'); 82 | 83 | setLabelProbabilities(); 84 | setChordCountsInLabels(); 85 | setProbabilityOfChordsInLabels(); 86 | 87 | function classify(chords){ 88 | console.log(labelProbabilities); 89 | var classified = {}; 90 | Object.keys(labelProbabilities).forEach(function(difficulty){ 91 | var first = labelProbabilities[difficulty] + 1.01; 92 | chords.forEach(function(chord){ 93 | var probabilityOfChordInLabel = 94 | probabilityOfChordsInLabels[difficulty][chord]; 95 | if(probabilityOfChordInLabel){ 96 | first = first * (probabilityOfChordInLabel + 1.01); 97 | } 98 | }); 99 | classified[difficulty] = first; 100 | }); 101 | console.log(classified); 102 | }; 103 | 104 | classify(['d', 'g', 'e', 'dm']); 105 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 106 | -------------------------------------------------------------------------------- /code/ch06/4_after_variables.js: -------------------------------------------------------------------------------- 1 | var easy = 'easy'; 2 | var medium = 'medium'; 3 | var hard = 'hard'; 4 | 5 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 6 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 7 | tooManyCooks = ['c', 'g', 'f']; 8 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 9 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 10 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 11 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 12 | 'em7', 'a7', 'f7', 'b']; 13 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 14 | 'g7']; 15 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 16 | 17 | var songs = []; 18 | var labels = []; 19 | var allChords = []; 20 | var labelCounts = []; 21 | var labelProbabilities = []; 22 | var chordCountsInLabels = {}; 23 | var probabilityOfChordsInLabels = {}; 24 | 25 | function train(chords, label){ 26 | var index; 27 | songs.push([label, chords]); 28 | labels.push(label); 29 | for (index = 0; index < chords.length; index++){ 30 | if(!allChords.includes(chords[index])){ 31 | allChords.push(chords[index]); 32 | } 33 | } 34 | if(Object.keys(labelCounts).includes(label)){ 35 | labelCounts[label] = labelCounts[label] + 1; 36 | } else { 37 | labelCounts[label] = 1; 38 | } 39 | }; 40 | 41 | function setLabelProbabilities(){ 42 | Object.keys(labelCounts).forEach(function(label){ 43 | labelProbabilities[label] = labelCounts[label] / songs.length; 44 | }); 45 | }; 46 | 47 | function setChordCountsInLabels(){ 48 | songs.forEach(function(song){ 49 | if(chordCountsInLabels[song[0]] === undefined){ 50 | chordCountsInLabels[song[0]] = {}; 51 | } 52 | song[1].forEach(function(chord){ 53 | if(chordCountsInLabels[song[0]][chord] > 0){ 54 | chordCountsInLabels[song[0]][chord] += 1; 55 | } else { 56 | chordCountsInLabels[song[0]][chord] = 1; 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | function setProbabilityOfChordsInLabels(){ 63 | probabilityOfChordsInLabels = chordCountsInLabels; 64 | Object.keys(probabilityOfChordsInLabels).forEach(function(difficulty){ 65 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(function(chord){ 66 | probabilityOfChordsInLabels[difficulty][chord] /= songs.length; 67 | }); 68 | }); 69 | } 70 | 71 | train(imagine, easy); 72 | train(somewhereOverTheRainbow, easy); 73 | train(tooManyCooks, easy); 74 | train(iWillFollowYouIntoTheDark, medium); 75 | train(babyOneMoreTime, medium); 76 | train(creep, medium); 77 | train(paperBag, hard); 78 | train(toxic, hard); 79 | train(bulletproof, hard); 80 | 81 | setLabelProbabilities(); 82 | setChordCountsInLabels(); 83 | setProbabilityOfChordsInLabels(); 84 | 85 | function classify(chords){ 86 | var smoothing = 1.01; 87 | console.log(labelProbabilities); 88 | var classified = {}; 89 | Object.keys(labelProbabilities).forEach(function(difficulty){ 90 | var first = labelProbabilities[difficulty] + smoothing; 91 | chords.forEach(function(chord){ 92 | var probabilityOfChordInLabel = 93 | probabilityOfChordsInLabels[difficulty][chord] 94 | if(probabilityOfChordInLabel){ 95 | first = first * (probabilityOfChordInLabel + smoothing) 96 | } 97 | }) 98 | classified[difficulty] = first 99 | }); 100 | console.log(classified); 101 | }; 102 | 103 | classify(['d', 'g', 'e', 'dm']); 104 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 105 | -------------------------------------------------------------------------------- /code/ch06/5_after_strings.js: -------------------------------------------------------------------------------- 1 | function fileName(){ 2 | var theError = new Error("here I am"); 3 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 4 | }; 5 | console.log(`Welcome to ${fileName()}!`); 6 | var easy = 'easy'; 7 | var medium = 'medium'; 8 | var hard = 'hard'; 9 | 10 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 11 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 12 | tooManyCooks = ['c', 'g', 'f']; 13 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 14 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 15 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 16 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 17 | 'em7', 'a7', 'f7', 'b']; 18 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 19 | 'g7']; 20 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 21 | 22 | var songs = []; 23 | var labels = []; 24 | var allChords = []; 25 | var labelCounts = []; 26 | var labelProbabilities = []; 27 | var chordCountsInLabels = {}; 28 | var probabilityOfChordsInLabels = {}; 29 | 30 | function train(chords, label){ 31 | var index; 32 | songs.push([label, chords]); 33 | labels.push(label); 34 | for (index = 0; index < chords.length; index++){ 35 | if(!allChords.includes(chords[index])){ 36 | allChords.push(chords[index]); 37 | } 38 | } 39 | if(Object.keys(labelCounts).includes(label)){ 40 | labelCounts[label] = labelCounts[label] + 1; 41 | } else { 42 | labelCounts[label] = 1; 43 | } 44 | }; 45 | 46 | function setLabelProbabilities(){ 47 | Object.keys(labelCounts).forEach(function(label){ 48 | labelProbabilities[label] = labelCounts[label] / songs.length; 49 | }); 50 | }; 51 | 52 | function setChordCountsInLabels(){ 53 | songs.forEach(function(song){ 54 | if(chordCountsInLabels[song[0]] === undefined){ 55 | chordCountsInLabels[song[0]] = {}; 56 | } 57 | song[1].forEach(function(chord){ 58 | if(chordCountsInLabels[song[0]][chord] > 0){ 59 | chordCountsInLabels[song[0]][chord] += 1; 60 | } else { 61 | chordCountsInLabels[song[0]][chord] = 1; 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | function setProbabilityOfChordsInLabels(){ 68 | probabilityOfChordsInLabels = chordCountsInLabels; 69 | Object.keys(probabilityOfChordsInLabels).forEach(function(difficulty){ 70 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(function(chord){ 71 | probabilityOfChordsInLabels[difficulty][chord] /= songs.length; 72 | }); 73 | }); 74 | } 75 | 76 | train(imagine, easy); 77 | train(somewhereOverTheRainbow, easy); 78 | train(tooManyCooks, easy); 79 | train(iWillFollowYouIntoTheDark, medium); 80 | train(babyOneMoreTime, medium); 81 | train(creep, medium); 82 | train(paperBag, hard); 83 | train(toxic, hard); 84 | train(bulletproof, hard); 85 | 86 | setLabelProbabilities(); 87 | setChordCountsInLabels(); 88 | setProbabilityOfChordsInLabels(); 89 | 90 | function classify(chords){ 91 | var smoothing = 1.01; 92 | console.log(labelProbabilities); 93 | var classified = {}; 94 | Object.keys(labelProbabilities).forEach(function(difficulty){ 95 | var first = labelProbabilities[difficulty] + smoothing; 96 | chords.forEach(function(chord){ 97 | var probabilityOfChordInLabel = 98 | probabilityOfChordsInLabels[difficulty][chord] 99 | if(probabilityOfChordInLabel){ 100 | first = first * (probabilityOfChordInLabel + smoothing) 101 | } 102 | }) 103 | classified[difficulty] = first 104 | }); 105 | console.log(classified); 106 | }; 107 | 108 | classify(['d', 'g', 'e', 'dm']); 109 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 110 | -------------------------------------------------------------------------------- /code/ch06/6_after_working_with_arrays.js: -------------------------------------------------------------------------------- 1 | function fileName(){ 2 | var theError = new Error("here I am"); 3 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 4 | }; 5 | console.log(`Welcome to ${fileName()}!`); 6 | var easy = 'easy'; 7 | var medium = 'medium'; 8 | var hard = 'hard'; 9 | 10 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 11 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 12 | tooManyCooks = ['c', 'g', 'f']; 13 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 14 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 15 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 16 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 17 | 'em7', 'a7', 'f7', 'b']; 18 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 19 | 'g7']; 20 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 21 | 22 | var songs = []; 23 | var labels = []; 24 | var allChords = []; 25 | var labelCounts = []; 26 | var labelProbabilities = []; 27 | var chordCountsInLabels = {}; 28 | var probabilityOfChordsInLabels = {}; 29 | 30 | function train(chords, label){ 31 | songs.push([label, chords]); 32 | labels.push(label); 33 | chords.forEach(chord => { 34 | if(!allChords.includes(chord)){ 35 | allChords.push(chord); 36 | } 37 | }); 38 | if(Object.keys(labelCounts).includes(label)){ 39 | labelCounts[label] = labelCounts[label] + 1; 40 | } else { 41 | labelCounts[label] = 1; 42 | } 43 | }; 44 | 45 | function setLabelProbabilities(){ 46 | Object.keys(labelCounts).forEach(function(label){ 47 | labelProbabilities[label] = labelCounts[label] / songs.length; 48 | }); 49 | }; 50 | 51 | function setChordCountsInLabels(){ 52 | songs.forEach(function(song){ 53 | if(chordCountsInLabels[song[0]] === undefined){ 54 | chordCountsInLabels[song[0]] = {}; 55 | } 56 | song[1].forEach(function(chord){ 57 | if(chordCountsInLabels[song[0]][chord] > 0){ 58 | chordCountsInLabels[song[0]][chord] += 1; 59 | } else { 60 | chordCountsInLabels[song[0]][chord] = 1; 61 | } 62 | }); 63 | }); 64 | } 65 | 66 | function setProbabilityOfChordsInLabels(){ 67 | probabilityOfChordsInLabels = chordCountsInLabels; 68 | Object.keys(probabilityOfChordsInLabels).forEach(function(difficulty){ 69 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach(function(chord){ 70 | probabilityOfChordsInLabels[difficulty][chord] /= songs.length; 71 | }); 72 | }); 73 | } 74 | 75 | train(imagine, easy); 76 | train(somewhereOverTheRainbow, easy); 77 | train(tooManyCooks, easy); 78 | train(iWillFollowYouIntoTheDark, medium); 79 | train(babyOneMoreTime, medium); 80 | train(creep, medium); 81 | train(paperBag, hard); 82 | train(toxic, hard); 83 | train(bulletproof, hard); 84 | 85 | setLabelProbabilities(); 86 | setChordCountsInLabels(); 87 | setProbabilityOfChordsInLabels(); 88 | 89 | function classify(chords){ 90 | var smoothing = 1.01; 91 | console.log(labelProbabilities); 92 | var classified = {}; 93 | Object.keys(labelProbabilities).forEach(function(difficulty){ 94 | var first = labelProbabilities[difficulty] + smoothing; 95 | chords.forEach(function(chord){ 96 | var probabilityOfChordInLabel = 97 | probabilityOfChordsInLabels[difficulty][chord] 98 | if(probabilityOfChordInLabel){ 99 | first = first * (probabilityOfChordInLabel + smoothing) 100 | } 101 | }) 102 | classified[difficulty] = first 103 | }); 104 | console.log(classified); 105 | }; 106 | 107 | classify(['d', 'g', 'e', 'dm']); 108 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 109 | -------------------------------------------------------------------------------- /code/ch07/10_after_shorthand_syntax.js: -------------------------------------------------------------------------------- 1 | const classifier = { 2 | labelCounts: new Map(), 3 | labelProbabilities: new Map(), 4 | chordCountsInLabels: new Map(), 5 | smoothing: 1.01, 6 | songList: { 7 | allChords: new Set(), 8 | difficulties: ['easy', 'medium', 'hard'], 9 | songs: [], 10 | addSong(name, chords, difficulty){ 11 | this.songs.push({name, chords, 12 | difficulty: this.difficulties[difficulty]}); 13 | } 14 | }, 15 | chordCountForDifficulty(difficulty, testChord){ 16 | return this.songList.songs.reduce((counter, song) => { 17 | if(song.difficulty === difficulty){ 18 | counter += song.chords.filter(chord => chord === testChord).length; 19 | } 20 | return counter; 21 | }, 0); 22 | }, 23 | 24 | likelihoodFromChord(difficulty, chord){ 25 | return this.chordCountForDifficulty(difficulty, chord) / 26 | this.songList.songs.length; 27 | }, 28 | valueForChordDifficulty(difficulty, chord){ 29 | const value = this.likelihoodFromChord(difficulty, chord); 30 | return value ? value + this.smoothing : 1; 31 | }, 32 | trainAll(){ 33 | this.songList.songs.forEach((song) => { 34 | this.train(song.chords, song.difficulty); 35 | }); 36 | this.setLabelProbabilities(); 37 | }, 38 | 39 | train(chords, label){ 40 | chords.forEach(chord => this.songList.allChords.add(chord) ); 41 | if(Array.from(this.labelCounts.keys()).includes(label)){ 42 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 43 | } else { 44 | this.labelCounts.set(label, 1); 45 | } 46 | }, 47 | 48 | setLabelProbabilities(){ 49 | this.labelCounts.forEach((_count, label) => { 50 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 51 | this.songList.songs.length); 52 | }); 53 | }, 54 | 55 | classify(chords){ 56 | return new Map(Array.from( 57 | this.labelProbabilities.entries()).map((labelWithProbability) => { 58 | const difficulty = labelWithProbability[0]; 59 | return [difficulty, chords.reduce((total, chord) => { 60 | return total * this.valueForChordDifficulty(difficulty, chord); 61 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 62 | })); 63 | } 64 | }; 65 | 66 | const wish = require('wish'); 67 | describe('the file', () => { 68 | classifier.songList.addSong('imagine', 69 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 70 | classifier.songList.addSong('somewhereOverTheRainbow', 71 | ['c', 'em', 'f', 'g', 'am'], 0); 72 | classifier.songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 73 | classifier.songList.addSong('iWillFollowYouIntoTheDark', 74 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 75 | classifier.songList.addSong('babyOneMoreTime', 76 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 77 | classifier.songList.addSong('creep', 78 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 79 | classifier.songList.addSong('paperBag', 80 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 81 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 82 | 'b'], 2); 83 | classifier.songList.addSong('toxic', 84 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 85 | classifier.songList.addSong('bulletproof', 86 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 87 | classifier.trainAll(); 88 | it('classifies', () => { 89 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 90 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 91 | wish(classified.get('easy') === 1.3433333333333333); 92 | wish(classified.get('medium') === 1.5060259259259259); 93 | wish(classified.get('hard') === 1.6884223991769547); 94 | }); 95 | it('classifies again', () => { 96 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 97 | wish(classified.get('easy') === 2.023094827160494); 98 | wish(classified.get('medium') === 1.855758613168724); 99 | wish(classified.get('hard') === 1.855758613168724); 100 | }); 101 | it('label probabilities', () => { 102 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 103 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 104 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /code/ch07/11_after_getting_new_obj_constructor_func.js: -------------------------------------------------------------------------------- 1 | const Classifier = function(){ 2 | const SongList = function() { 3 | this.allChords = new Set(); 4 | this.difficulties = ['easy', 'medium', 'hard']; 5 | this.songs = []; 6 | this.addSong = function(name, chords, difficulty){ 7 | this.songs.push({name, 8 | chords, 9 | difficulty: this.difficulties[difficulty]}); 10 | }; 11 | }; 12 | this.songList = new SongList(); 13 | this.labelCounts = new Map(); 14 | this.labelProbabilities = new Map(); 15 | this.chordCountsInLabels = new Map(); 16 | this.smoothing = 1.01; 17 | this.chordCountForDifficulty = function(difficulty, testChord){ 18 | return this.songList.songs.reduce((counter, song) => { 19 | if(song.difficulty === difficulty){ 20 | counter += song.chords.filter(chord => chord === testChord).length; 21 | } 22 | return counter; 23 | }, 0); 24 | }; 25 | 26 | this.likelihoodFromChord = function(difficulty, chord){ 27 | return this.chordCountForDifficulty(difficulty, chord) / 28 | this.songList.songs.length; 29 | }; 30 | this.valueForChordDifficulty = function(difficulty, chord){ 31 | const value = this.likelihoodFromChord(difficulty, chord); 32 | return value ? value + this.smoothing : 1; 33 | }; 34 | this.trainAll = function(){ 35 | this.songList.songs.forEach((song) => { 36 | this.train(song.chords, song.difficulty); 37 | }); 38 | this.setLabelProbabilities(); 39 | }; 40 | 41 | this.train = function(chords, label){ 42 | chords.forEach(chord => this.songList.allChords.add(chord) ); 43 | if(Array.from(this.labelCounts.keys()).includes(label)){ 44 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 45 | } else { 46 | this.labelCounts.set(label, 1); 47 | } 48 | }; 49 | 50 | this.setLabelProbabilities = function(){ 51 | this.labelCounts.forEach((_count, label) => { 52 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 53 | this.songList.songs.length); 54 | }); 55 | }; 56 | 57 | this.classify = function(chords){ 58 | return new Map(Array.from( 59 | this.labelProbabilities.entries()).map((labelWithProbability) => { 60 | const difficulty = labelWithProbability[0]; 61 | return [difficulty, chords.reduce((total, chord) => { 62 | return total * this.valueForChordDifficulty(difficulty, chord); 63 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 64 | })); 65 | }; 66 | }; 67 | 68 | const wish = require('wish'); 69 | describe('the file', () => { 70 | const classifier = new Classifier(); 71 | classifier.songList.addSong('imagine', 72 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 73 | classifier.songList.addSong('somewhereOverTheRainbow', 74 | ['c', 'em', 'f', 'g', 'am'], 0); 75 | classifier.songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 76 | classifier.songList.addSong('iWillFollowYouIntoTheDark', 77 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 78 | classifier.songList.addSong('babyOneMoreTime', 79 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 80 | classifier.songList.addSong('creep', 81 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 82 | classifier.songList.addSong('paperBag', 83 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 84 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 85 | 'b'], 2); 86 | classifier.songList.addSong('toxic', 87 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 88 | classifier.songList.addSong('bulletproof', 89 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 90 | classifier.trainAll(); 91 | it('classifies', () => { 92 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 93 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 94 | wish(classified.get('easy') === 1.3433333333333333); 95 | wish(classified.get('medium') === 1.5060259259259259); 96 | wish(classified.get('hard') === 1.6884223991769547); 97 | }); 98 | it('classifies again', () => { 99 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 100 | wish(classified.get('easy') === 2.023094827160494); 101 | wish(classified.get('medium') === 1.855758613168724); 102 | wish(classified.get('hard') === 1.855758613168724); 103 | }); 104 | it('label probabilities', () => { 105 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 106 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 107 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /code/ch07/12_after_constructor_vs_factory.js: -------------------------------------------------------------------------------- 1 | const classifierTemplate = { 2 | labelCounts: new Map(), 3 | labelProbabilities: new Map(), 4 | chordCountsInLabels: new Map(), 5 | smoothing: 1.01, 6 | songList: { 7 | allChords: new Set(), 8 | difficulties: ['easy', 'medium', 'hard'], 9 | songs: [], 10 | addSong(name, chords, difficulty){ 11 | this.songs.push({name, chords, 12 | difficulty: this.difficulties[difficulty]}); 13 | } 14 | }, 15 | chordCountForDifficulty(difficulty, testChord){ 16 | return this.songList.songs.reduce((counter, song) => { 17 | if(song.difficulty === difficulty){ 18 | counter += song.chords.filter(chord => chord === testChord).length; 19 | } 20 | return counter; 21 | }, 0); 22 | }, 23 | 24 | likelihoodFromChord(difficulty, chord){ 25 | return this.chordCountForDifficulty(difficulty, chord) / 26 | this.songList.songs.length; 27 | }, 28 | valueForChordDifficulty(difficulty, chord){ 29 | const value = this.likelihoodFromChord(difficulty, chord); 30 | return value ? value + this.smoothing : 1; 31 | }, 32 | trainAll(){ 33 | this.songList.songs.forEach((song) => { 34 | this.train(song.chords, song.difficulty); 35 | }); 36 | this.setLabelProbabilities(); 37 | }, 38 | 39 | train(chords, label){ 40 | chords.forEach(chord => this.songList.allChords.add(chord) ); 41 | if(Array.from(this.labelCounts.keys()).includes(label)){ 42 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 43 | } else { 44 | this.labelCounts.set(label, 1); 45 | } 46 | }, 47 | 48 | setLabelProbabilities(){ 49 | this.labelCounts.forEach((_count, label) => { 50 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 51 | this.songList.songs.length); 52 | }); 53 | }, 54 | 55 | classify(chords){ 56 | return new Map(Array.from( 57 | this.labelProbabilities.entries()).map((labelWithProbability) => { 58 | const difficulty = labelWithProbability[0]; 59 | return [difficulty, chords.reduce((total, chord) => { 60 | return total * this.valueForChordDifficulty(difficulty, chord); 61 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 62 | })); 63 | } 64 | }; 65 | 66 | const wish = require('wish'); 67 | describe('the file', () => { 68 | const classifier = Object.create(classifierTemplate); 69 | classifier.songList.addSong('imagine', 70 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 71 | classifier.songList.addSong('somewhereOverTheRainbow', 72 | ['c', 'em', 'f', 'g', 'am'], 0); 73 | classifier.songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 74 | classifier.songList.addSong('iWillFollowYouIntoTheDark', 75 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 76 | classifier.songList.addSong('babyOneMoreTime', 77 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 78 | classifier.songList.addSong('creep', 79 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 80 | classifier.songList.addSong('paperBag', 81 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 82 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 83 | 'b'], 2); 84 | classifier.songList.addSong('toxic', 85 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 86 | classifier.songList.addSong('bulletproof', 87 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 88 | classifier.trainAll(); 89 | it('classifies', () => { 90 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 91 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 92 | wish(classified.get('easy') === 1.3433333333333333); 93 | wish(classified.get('medium') === 1.5060259259259259); 94 | wish(classified.get('hard') === 1.6884223991769547); 95 | }); 96 | it('classifies again', () => { 97 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 98 | wish(classified.get('easy') === 2.023094827160494); 99 | wish(classified.get('medium') === 1.855758613168724); 100 | wish(classified.get('hard') === 1.855758613168724); 101 | }); 102 | it('label probabilities', () => { 103 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 104 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 105 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /code/ch07/13_after_class_for_classifier.js: -------------------------------------------------------------------------------- 1 | class Classifier { 2 | constructor(){ 3 | this.songList = { 4 | allChords: new Set(), 5 | difficulties: ['easy', 'medium', 'hard'], 6 | songs: [], 7 | addSong(name, chords, difficulty){ 8 | this.songs.push({name, 9 | chords, 10 | difficulty: this.difficulties[difficulty]}); 11 | } 12 | }; 13 | this.labelCounts = new Map(); 14 | this.labelProbabilities = new Map(); 15 | this.smoothing = 1.01; 16 | }; 17 | chordCountForDifficulty(difficulty, testChord){ 18 | return this.songList.songs.reduce((counter, song) => { 19 | if(song.difficulty === difficulty){ 20 | counter += song.chords.filter( 21 | chord => chord === testChord 22 | ).length; 23 | } 24 | return counter; 25 | }, 0); 26 | }; 27 | 28 | likelihoodFromChord(difficulty, chord){ 29 | return this.chordCountForDifficulty(difficulty, chord) / 30 | this.songList.songs.length; 31 | }; 32 | valueForChordDifficulty(difficulty, chord){ 33 | const value = this.likelihoodFromChord(difficulty, chord); 34 | return value ? value + this.smoothing : 1; 35 | }; 36 | trainAll(){ 37 | this.songList.songs.forEach((song) => { 38 | this.train(song.chords, song.difficulty); 39 | }); 40 | this.setLabelProbabilities(); 41 | }; 42 | 43 | train(chords, label){ 44 | chords.forEach(chord => this.songList.allChords.add(chord) ); 45 | if(Array.from(this.labelCounts.keys()).includes(label)){ 46 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 47 | } else { 48 | this.labelCounts.set(label, 1); 49 | } 50 | }; 51 | 52 | setLabelProbabilities(){ 53 | this.labelCounts.forEach((_count, label) => { 54 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 55 | this.songList.songs.length); 56 | }); 57 | }; 58 | 59 | classify(chords){ 60 | return new Map(Array.from( 61 | this.labelProbabilities.entries()).map((labelWithProbability) => { 62 | const difficulty = labelWithProbability[0]; 63 | return [difficulty, chords.reduce((total, chord) => { 64 | return total * this.valueForChordDifficulty(difficulty, chord); 65 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 66 | })); 67 | } 68 | }; 69 | 70 | const wish = require('wish'); 71 | describe('the file', () => { 72 | const classifier = new Classifier; 73 | classifier.songList.addSong('imagine', 74 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 75 | classifier.songList.addSong('somewhereOverTheRainbow', 76 | ['c', 'em', 'f', 'g', 'am'], 0); 77 | classifier.songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 78 | classifier.songList.addSong('iWillFollowYouIntoTheDark', 79 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 80 | classifier.songList.addSong('babyOneMoreTime', 81 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 82 | classifier.songList.addSong('creep', 83 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 84 | classifier.songList.addSong('paperBag', 85 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 86 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 87 | 'b'], 2); 88 | classifier.songList.addSong('toxic', 89 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 90 | classifier.songList.addSong('bulletproof', 91 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 92 | classifier.trainAll(); 93 | it('classifies', () => { 94 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 95 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 96 | wish(classified.get('easy') === 1.3433333333333333); 97 | wish(classified.get('medium') === 1.5060259259259259); 98 | wish(classified.get('hard') === 1.6884223991769547); 99 | }); 100 | it('classifies again', () => { 101 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 102 | wish(classified.get('easy') === 2.023094827160494); 103 | wish(classified.get('medium') === 1.855758613168724); 104 | wish(classified.get('hard') === 1.855758613168724); 105 | }); 106 | it('label probabilities', () => { 107 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 108 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 109 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /code/ch07/14_after_choosing_api.js: -------------------------------------------------------------------------------- 1 | class Classifier { 2 | constructor(){ 3 | this.songList = { 4 | allChords: new Set(), 5 | difficulties: ['easy', 'medium', 'hard'], 6 | songs: [], 7 | addSong(name, chords, difficulty){ 8 | this.songs.push({name, 9 | chords, 10 | difficulty: this.difficulties[difficulty]}); 11 | } 12 | }; 13 | this.labelCounts = new Map(); 14 | this.labelProbabilities = new Map(); 15 | this.smoothing = 1.01; 16 | }; 17 | addSong(...songParams){ 18 | this.songList.addSong(...songParams); 19 | }; 20 | chordCountForDifficulty(difficulty, testChord){ 21 | return this.songList.songs.reduce((counter, song) => { 22 | if(song.difficulty === difficulty){ 23 | counter += song.chords.filter( 24 | chord => chord === testChord 25 | ).length; 26 | } 27 | return counter; 28 | }, 0); 29 | }; 30 | 31 | likelihoodFromChord(difficulty, chord){ 32 | return this.chordCountForDifficulty(difficulty, chord) / 33 | this.songList.songs.length; 34 | }; 35 | valueForChordDifficulty(difficulty, chord){ 36 | const value = this.likelihoodFromChord(difficulty, chord); 37 | return value ? value + this.smoothing : 1; 38 | }; 39 | trainAll(){ 40 | this.songList.songs.forEach((song) => { 41 | this.train(song.chords, song.difficulty); 42 | }); 43 | this.setLabelProbabilities(); 44 | }; 45 | 46 | train(chords, label){ 47 | chords.forEach(chord => this.songList.allChords.add(chord) ); 48 | if(Array.from(this.labelCounts.keys()).includes(label)){ 49 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 50 | } else { 51 | this.labelCounts.set(label, 1); 52 | } 53 | }; 54 | 55 | setLabelProbabilities(){ 56 | this.labelCounts.forEach((_count, label) => { 57 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 58 | this.songList.songs.length); 59 | }); 60 | }; 61 | 62 | classify(chords){ 63 | return new Map(Array.from( 64 | this.labelProbabilities.entries()).map((labelWithProbability) => { 65 | const difficulty = labelWithProbability[0]; 66 | return [difficulty, chords.reduce((total, chord) => { 67 | return total * this.valueForChordDifficulty(difficulty, chord); 68 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 69 | })); 70 | } 71 | }; 72 | 73 | const wish = require('wish'); 74 | describe('the file', () => { 75 | const classifier = new Classifier; 76 | classifier.addSong('imagine', 77 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 78 | classifier.addSong('somewhereOverTheRainbow', 79 | ['c', 'em', 'f', 'g', 'am'], 0); 80 | classifier.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 81 | classifier.addSong('iWillFollowYouIntoTheDark', 82 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 83 | classifier.addSong('babyOneMoreTime', 84 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 85 | classifier.addSong('creep', 86 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 87 | classifier.addSong('paperBag', 88 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 89 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 90 | 'b'], 2); 91 | classifier.addSong('toxic', 92 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 93 | classifier.addSong('bulletproof', 94 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 95 | classifier.trainAll(); 96 | it('classifies', () => { 97 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 98 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 99 | wish(classified.get('easy') === 1.3433333333333333); 100 | wish(classified.get('medium') === 1.5060259259259259); 101 | wish(classified.get('hard') === 1.6884223991769547); 102 | }); 103 | it('classifies again', () => { 104 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 105 | wish(classified.get('easy') === 2.023094827160494); 106 | wish(classified.get('medium') === 1.855758613168724); 107 | wish(classified.get('hard') === 1.855758613168724); 108 | }); 109 | it('label probabilities', () => { 110 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 111 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 112 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /code/ch07/15_after_privacy.js: -------------------------------------------------------------------------------- 1 | module.exports = class Classifier { 2 | constructor(){ 3 | this._songList = { 4 | allChords: new Set(), 5 | difficulties: ['easy', 'medium', 'hard'], 6 | songs: [], 7 | addSong(name, chords, difficulty){ 8 | this.songs.push({name, 9 | chords, 10 | difficulty: this.difficulties[difficulty]}); 11 | } 12 | }; 13 | this._labelCounts = new Map(); 14 | this._labelProbabilities = new Map(); 15 | this._smoothing = 1.01; 16 | }; 17 | addSong(...songParams){ 18 | this._songList.addSong(...songParams); 19 | }; 20 | _chordCountForDifficulty(difficulty, testChord){ 21 | return this._songList.songs.reduce((counter, song) => { 22 | if(song.difficulty === difficulty){ 23 | counter += song.chords.filter( 24 | chord => chord === testChord 25 | ).length; 26 | } 27 | return counter; 28 | }, 0); 29 | }; 30 | 31 | _likelihoodFromChord(difficulty, chord){ 32 | return this._chordCountForDifficulty(difficulty, chord) / 33 | this._songList.songs.length; 34 | }; 35 | _valueForChordDifficulty(difficulty, chord){ 36 | const value = this._likelihoodFromChord(difficulty, chord); 37 | return value ? value + this._smoothing : 1; 38 | }; 39 | trainAll(){ 40 | this._songList.songs.forEach((song) => { 41 | this._train(song.chords, song.difficulty); 42 | }); 43 | this._setLabelProbabilities(); 44 | }; 45 | 46 | _train(chords, label){ 47 | chords.forEach(chord => this._songList.allChords.add(chord) ); 48 | if(Array.from(this._labelCounts.keys()).includes(label)){ 49 | this._labelCounts.set(label, this._labelCounts.get(label) + 1); 50 | } else { 51 | this._labelCounts.set(label, 1); 52 | } 53 | }; 54 | 55 | _setLabelProbabilities(){ 56 | this._labelCounts.forEach((_count, label) => { 57 | this._labelProbabilities.set(label, this._labelCounts.get(label) / 58 | this._songList.songs.length); 59 | }); 60 | }; 61 | 62 | classify(chords){ 63 | return new Map(Array.from( 64 | this._labelProbabilities.entries()).map( 65 | (labelWithProbability) => { 66 | const difficulty = labelWithProbability[0]; 67 | return [difficulty, chords.reduce((total, chord) => { 68 | return total * this._valueForChordDifficulty(difficulty, 69 | chord); 70 | }, this._labelProbabilities.get(difficulty) + this._smoothing)]; 71 | })); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /code/ch07/15_after_privacy_test.js: -------------------------------------------------------------------------------- 1 | const Classifier = require('./15_after_privacy.js'); 2 | const wish = require('wish'); 3 | describe('the file', () => { 4 | const classifier = new Classifier; 5 | classifier.addSong('imagine', 6 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 7 | classifier.addSong('somewhereOverTheRainbow', 8 | ['c', 'em', 'f', 'g', 'am'], 0); 9 | classifier.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 10 | classifier.addSong('iWillFollowYouIntoTheDark', 11 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 12 | classifier.addSong('babyOneMoreTime', 13 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 14 | classifier.addSong('creep', 15 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 16 | classifier.addSong('paperBag', 17 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 18 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 19 | 'b'], 2); 20 | classifier.addSong('toxic', 21 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 22 | classifier.addSong('bulletproof', 23 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 24 | classifier.trainAll(); 25 | it('classifies', () => { 26 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 27 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 28 | wish(classified.get('easy') === 1.3433333333333333); 29 | wish(classified.get('medium') === 1.5060259259259259); 30 | wish(classified.get('hard') === 1.6884223991769547); 31 | }); 32 | it('classifies again', () => { 33 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 34 | wish(classified.get('easy') === 2.023094827160494); 35 | wish(classified.get('medium') === 1.855758613168724); 36 | wish(classified.get('hard') === 1.855758613168724); 37 | }); 38 | it('label probabilities', () => { 39 | wish(classifier._labelProbabilities.get('easy') === 0.3333333333333333); 40 | wish(classifier._labelProbabilities.get('medium') === 0.3333333333333333); 41 | wish(classifier._labelProbabilities.get('hard') === 0.3333333333333333); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /code/ch07/16_after_new_domain.js: -------------------------------------------------------------------------------- 1 | module.exports = class Classifier { 2 | constructor(){ 3 | this._textList = { 4 | allWords: new Set(), 5 | understood: ['yes', 'no'], 6 | texts: [], 7 | addText(name, words, comprehension){ 8 | this.texts.push({name, 9 | words, 10 | comprehension: this.understood[comprehension]}); 11 | } 12 | }; 13 | this._labelCounts = new Map(); 14 | this._labelProbabilities = new Map(); 15 | this._smoothing = 1.01; 16 | }; 17 | addText(...textParams){ 18 | this._textList.addText(...textParams); 19 | }; 20 | _wordCountForComprehension(comprehension, testWord){ 21 | return this._textList.texts.reduce((counter, text) => { 22 | if(text.comprehension === comprehension){ 23 | counter += text.words.filter( 24 | word => word === testWord 25 | ).length; 26 | } 27 | return counter; 28 | }, 0); 29 | }; 30 | 31 | _likelihoodFromWord(comprehension, word){ 32 | return this._wordCountForComprehension(comprehension, word) / 33 | this._textList.texts.length; 34 | }; 35 | _valueForWordComprehension(comprehension, word){ 36 | const value = this._likelihoodFromWord(comprehension, word); 37 | return value ? value + this._smoothing : 1; 38 | }; 39 | trainAll(){ 40 | this._textList.texts.forEach((text) => { 41 | this._train(text.words, text.comprehension); 42 | }); 43 | this._setLabelProbabilities(); 44 | }; 45 | 46 | _train(words, label){ 47 | words.forEach(word => this._textList.allWords.add(word) ); 48 | if(Array.from(this._labelCounts.keys()).includes(label)){ 49 | this._labelCounts.set(label, this._labelCounts.get(label) + 1); 50 | } else { 51 | this._labelCounts.set(label, 1); 52 | } 53 | }; 54 | 55 | _setLabelProbabilities(){ 56 | this._labelCounts.forEach((_count, label) => { 57 | this._labelProbabilities.set(label, this._labelCounts.get(label) / 58 | this._textList.texts.length); 59 | }); 60 | }; 61 | 62 | classify(words){ 63 | return new Map(Array.from( 64 | this._labelProbabilities.entries()).map( 65 | (labelWithProbability) => { 66 | const comprehension = labelWithProbability[0]; 67 | return [comprehension, words.reduce((total, word) => { 68 | return total * this._valueForWordComprehension(comprehension, 69 | word); 70 | }, this._labelProbabilities.get(comprehension) + this._smoothing)]; 71 | })); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /code/ch07/16_after_new_domain_test.js: -------------------------------------------------------------------------------- 1 | Classifier = require('./16_after_new_domain.js'); 2 | const wish = require('wish'); 3 | describe('the file', () => { 4 | const classifier = new Classifier; 5 | classifier.addText('english text', 6 | ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 7 | 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q'], 8 | 0); 9 | classifier.addText('japanese text', 10 | ['あ', 'い', 'う', 'え', 'お', 11 | 'か', 'き', 'く', 'け', 'こ'], 12 | 1); 13 | 14 | classifier.trainAll(); 15 | it('classifies', () =>{ 16 | const classified = classifier.classify(['お', 'は', 'よ', 'う', 'ご', 'ざ', 'い', 'ま', 'す']); 17 | wish(classified.get('yes') === 1.51); 18 | wish(classified.get('no') === 5.19885601); 19 | }); 20 | it('number of words', ()=>{ 21 | wish(classifier._textList.allWords.size === 27); 22 | }); 23 | 24 | it('label probabilities', ()=>{ 25 | wish(classifier._labelProbabilities.get('yes') === 0.5); 26 | wish(classifier._labelProbabilities.get('no') === 0.5); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /code/ch07/1_initial.js: -------------------------------------------------------------------------------- 1 | function fileName(){ 2 | var theError = new Error("here I am"); 3 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 4 | }; 5 | console.log(`Welcome to ${fileName()}!`); 6 | var easy = 'easy'; 7 | var medium = 'medium'; 8 | var hard = 'hard'; 9 | 10 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 11 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 12 | tooManyCooks = ['c', 'g', 'f']; 13 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 14 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 15 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 16 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 17 | 'em7', 'a7', 'f7', 'b']; 18 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 19 | 'g7']; 20 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 21 | 22 | var songs = []; 23 | var labels = []; 24 | var allChords = []; 25 | var labelCounts = []; 26 | var labelProbabilities = []; 27 | var chordCountsInLabels = {}; 28 | var probabilityOfChordsInLabels = {}; 29 | 30 | function train(chords, label){ 31 | songs.push([label, chords]); 32 | labels.push(label); 33 | chords.forEach(chord => { 34 | if(!allChords.includes(chord)){ 35 | allChords.push(chord); 36 | } 37 | }); 38 | if(Object.keys(labelCounts).includes(label)){ 39 | labelCounts[label] = labelCounts[label] + 1; 40 | } else { 41 | labelCounts[label] = 1; 42 | } 43 | }; 44 | 45 | function setLabelProbabilities(){ 46 | Object.keys(labelCounts).forEach(function(label){ 47 | labelProbabilities[label] = labelCounts[label] / songs.length; 48 | }); 49 | }; 50 | 51 | function setChordCountsInLabels(){ 52 | songs.forEach(function(song){ 53 | if(chordCountsInLabels[song[0]] === undefined){ 54 | chordCountsInLabels[song[0]] = {}; 55 | } 56 | song[1].forEach(function(chord){ 57 | if(chordCountsInLabels[song[0]][chord] > 0){ 58 | chordCountsInLabels[song[0]][chord] += 1; 59 | } else { 60 | chordCountsInLabels[song[0]][chord] = 1; 61 | } 62 | }); 63 | }); 64 | } 65 | 66 | function setProbabilityOfChordsInLabels(){ 67 | probabilityOfChordsInLabels = chordCountsInLabels; 68 | Object.keys(probabilityOfChordsInLabels).forEach( 69 | function(difficulty){ 70 | Object.keys(probabilityOfChordsInLabels[difficulty]).forEach( 71 | function(chord){ 72 | probabilityOfChordsInLabels[difficulty][chord] /= songs.length; 73 | }); 74 | }); 75 | } 76 | 77 | train(imagine, easy); 78 | train(somewhereOverTheRainbow, easy); 79 | train(tooManyCooks, easy); 80 | train(iWillFollowYouIntoTheDark, medium); 81 | train(babyOneMoreTime, medium); 82 | train(creep, medium); 83 | train(paperBag, hard); 84 | train(toxic, hard); 85 | train(bulletproof, hard); 86 | 87 | setLabelProbabilities(); 88 | setChordCountsInLabels(); 89 | setProbabilityOfChordsInLabels(); 90 | 91 | function classify(chords){ 92 | var smoothing = 1.01; 93 | console.log(labelProbabilities); 94 | var classified = {}; 95 | Object.keys(labelProbabilities).forEach(function(difficulty){ 96 | var first = labelProbabilities[difficulty] + smoothing; 97 | chords.forEach(function(chord){ 98 | var probabilityOfChordInLabel = 99 | probabilityOfChordsInLabels[difficulty][chord]; 100 | if(probabilityOfChordInLabel){ 101 | first = first * (probabilityOfChordInLabel + smoothing); 102 | } 103 | }); 104 | classified[difficulty] = first; 105 | }); 106 | console.log(classified); 107 | }; 108 | 109 | classify(['d', 'g', 'e', 'dm']); 110 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 111 | -------------------------------------------------------------------------------- /code/ch07/2_after_array_object_alternatives.js: -------------------------------------------------------------------------------- 1 | function fileName(){ 2 | var theError = new Error("here I am"); 3 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 4 | }; 5 | console.log(`Welcome to ${fileName()}!`); 6 | var easy = 'easy'; 7 | var medium = 'medium'; 8 | var hard = 'hard'; 9 | 10 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 11 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 12 | tooManyCooks = ['c', 'g', 'f']; 13 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 14 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 15 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 16 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 17 | 'em7', 'a7', 'f7', 'b']; 18 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 19 | 'g7']; 20 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 21 | 22 | var songs = []; 23 | var allChords = new Set(); 24 | var labelCounts = new Map(); 25 | var labelProbabilities = new Map(); 26 | var chordCountsInLabels = new Map(); 27 | var probabilityOfChordsInLabels = new Map(); 28 | 29 | function train(chords, label){ 30 | songs.push({label, chords}); 31 | chords.forEach(chord => allChords.add(chord)); 32 | if(Array.from(labelCounts.keys()).includes(label)){ 33 | labelCounts.set(label, labelCounts.get(label) + 1); 34 | } else { 35 | labelCounts.set(label, 1); 36 | } 37 | }; 38 | 39 | function setLabelProbabilities(){ 40 | labelCounts.forEach(function(_count, label){ 41 | labelProbabilities.set(label, labelCounts.get(label) / songs.length); 42 | }); 43 | }; 44 | 45 | function setChordCountsInLabels(){ 46 | songs.forEach(function(song){ 47 | if(chordCountsInLabels.get(song.label) === undefined){ 48 | chordCountsInLabels.set(song.label, {}); 49 | } 50 | song.chords.forEach(function(chord){ 51 | if(chordCountsInLabels.get(song.label)[chord] > 0){ 52 | chordCountsInLabels.get(song.label)[chord] += 1; 53 | } else { 54 | chordCountsInLabels.get(song.label)[chord] = 1; 55 | } 56 | }); 57 | }); 58 | } 59 | 60 | function setProbabilityOfChordsInLabels(){ 61 | probabilityOfChordsInLabels = chordCountsInLabels; 62 | probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 63 | Object.keys(probabilityOfChordsInLabels.get(difficulty)).forEach( 64 | function(chord){ 65 | probabilityOfChordsInLabels.get(difficulty)[chord] /= songs.length; 66 | }); 67 | }); 68 | } 69 | 70 | train(imagine, easy); 71 | train(somewhereOverTheRainbow, easy); 72 | train(tooManyCooks, easy); 73 | train(iWillFollowYouIntoTheDark, medium); 74 | train(babyOneMoreTime, medium); 75 | train(creep, medium); 76 | train(paperBag, hard); 77 | train(toxic, hard); 78 | train(bulletproof, hard); 79 | 80 | setLabelProbabilities(); 81 | setChordCountsInLabels(); 82 | setProbabilityOfChordsInLabels(); 83 | 84 | function classify(chords){ 85 | var smoothing = 1.01; 86 | console.log(labelProbabilities); 87 | var classified = new Map(); 88 | labelProbabilities.forEach(function(_probabilities, difficulty){ 89 | var first = labelProbabilities.get(difficulty) + smoothing; 90 | chords.forEach(function(chord){ 91 | var probabilityOfChordInLabel = 92 | probabilityOfChordsInLabels.get(difficulty)[chord]; 93 | if(probabilityOfChordInLabel){ 94 | first = first * (probabilityOfChordInLabel + smoothing); 95 | } 96 | }); 97 | classified.set(difficulty, first); 98 | }); 99 | console.log(classified); 100 | }; 101 | 102 | classify(['d', 'g', 'e', 'dm']); 103 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 104 | -------------------------------------------------------------------------------- /code/ch07/3_after_testing_setup.js: -------------------------------------------------------------------------------- 1 | function welcomeMessage(){ 2 | return `Welcome to ${fileName()}!`; 3 | }; 4 | function fileName(){ 5 | var theError = new Error("here I am"); 6 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 7 | }; 8 | var easy = 'easy'; 9 | var medium = 'medium'; 10 | var hard = 'hard'; 11 | 12 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 13 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 14 | tooManyCooks = ['c', 'g', 'f']; 15 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 16 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 17 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 18 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 19 | 'em7', 'a7', 'f7', 'b']; 20 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 21 | 'g7']; 22 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 23 | 24 | var songs = []; 25 | var allChords = new Set(); 26 | var labelCounts = new Map(); 27 | var labelProbabilities = new Map(); 28 | var chordCountsInLabels = new Map(); 29 | var probabilityOfChordsInLabels = new Map(); 30 | 31 | function train(chords, label){ 32 | songs.push({label, chords}); 33 | chords.forEach(chord => allChords.add(chord)); 34 | if(Array.from(labelCounts.keys()).includes(label)){ 35 | labelCounts.set(label, labelCounts.get(label) + 1); 36 | } else { 37 | labelCounts.set(label, 1); 38 | } 39 | }; 40 | 41 | function setLabelProbabilities(){ 42 | labelCounts.forEach(function(_count, label){ 43 | labelProbabilities.set(label, labelCounts.get(label) / songs.length); 44 | }); 45 | }; 46 | 47 | function setChordCountsInLabels(){ 48 | songs.forEach(function(song){ 49 | if(chordCountsInLabels.get(song.label) === undefined){ 50 | chordCountsInLabels.set(song.label, {}); 51 | } 52 | song.chords.forEach(function(chord){ 53 | if(chordCountsInLabels.get(song.label)[chord] > 0){ 54 | chordCountsInLabels.get(song.label)[chord] += 1; 55 | } else { 56 | chordCountsInLabels.get(song.label)[chord] = 1; 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | function setProbabilityOfChordsInLabels(){ 63 | probabilityOfChordsInLabels = chordCountsInLabels; 64 | probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 65 | Object.keys(probabilityOfChordsInLabels.get(difficulty)).forEach( 66 | function(chord){ 67 | probabilityOfChordsInLabels.get(difficulty)[chord] /= songs.length; 68 | }); 69 | }); 70 | } 71 | 72 | train(imagine, easy); 73 | train(somewhereOverTheRainbow, easy); 74 | train(tooManyCooks, easy); 75 | train(iWillFollowYouIntoTheDark, medium); 76 | train(babyOneMoreTime, medium); 77 | train(creep, medium); 78 | train(paperBag, hard); 79 | train(toxic, hard); 80 | train(bulletproof, hard); 81 | 82 | setLabelProbabilities(); 83 | setChordCountsInLabels(); 84 | setProbabilityOfChordsInLabels(); 85 | 86 | function classify(chords){ 87 | var smoothing = 1.01; 88 | var classified = new Map(); 89 | labelProbabilities.forEach(function(_probabilities, difficulty){ 90 | var first = labelProbabilities.get(difficulty) + smoothing; 91 | chords.forEach(function(chord){ 92 | var probabilityOfChordInLabel = 93 | probabilityOfChordsInLabels.get(difficulty)[chord]; 94 | if(probabilityOfChordInLabel){ 95 | first = first * (probabilityOfChordInLabel + smoothing); 96 | } 97 | }); 98 | classified.set(difficulty, first); 99 | }); 100 | return classified; 101 | }; 102 | 103 | var wish = require('wish'); 104 | describe('the file', function() { 105 | it('classifies', function(){ 106 | var classified = classify(['f#m7', 'a', 'dadd9', 107 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 108 | wish(classified.get('easy') === 1.3433333333333333); 109 | wish(classified.get('medium') === 1.5060259259259259); 110 | wish(classified.get('hard') === 1.6884223991769547); 111 | }); 112 | it('classifies again', function(){ 113 | var classified = classify(['d', 'g', 'e', 'dm']); 114 | wish(classified.get('easy') === 2.023094827160494); 115 | wish(classified.get('medium') === 1.855758613168724); 116 | wish(classified.get('hard') === 1.855758613168724); 117 | }); 118 | it('sets welcome message', function(){ 119 | wish(welcomeMessage() === 'Welcome to nb.js!') 120 | }); 121 | it('label probabilities', function(){ 122 | wish(labelProbabilities.get('easy') === 0.3333333333333333); 123 | wish(labelProbabilities.get('medium') === 0.3333333333333333); 124 | wish(labelProbabilities.get('hard') === 0.3333333333333333); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /code/ch07/4_after_extracting_functions.js: -------------------------------------------------------------------------------- 1 | function welcomeMessage(){ 2 | return `Welcome to ${fileName()}!`; 3 | }; 4 | function fileName(){ 5 | var theError = new Error("here I am"); 6 | return theError.stack.match(/\/(\w+\.js)\:/)[1]; 7 | }; 8 | function setDifficulties(){ 9 | easy = 'easy'; 10 | medium = 'medium'; 11 | hard = 'hard'; 12 | }; 13 | 14 | 15 | function setSongs(){ 16 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 17 | somewhereOverTheRainbow = ['c', 'em', 'f', 'g', 'am']; 18 | tooManyCooks = ['c', 'g', 'f']; 19 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 20 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 21 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 22 | paperBag = ['bm7', 'e', 'c', 'g', 23 | 'b7', 'f', 'em', 'a', 24 | 'cmaj7', 'em7', 'a7', 'f7', 25 | 'b']; 26 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 27 | 'gmaj7', 'g7']; 28 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 29 | }; 30 | 31 | function setup(){ 32 | songs = []; 33 | allChords = new Set(); 34 | labelCounts = new Map(); 35 | labelProbabilities = new Map(); 36 | chordCountsInLabels = new Map(); 37 | probabilityOfChordsInLabels = new Map(); 38 | }; 39 | 40 | 41 | function train(chords, label){ 42 | songs.push({label, chords}); 43 | chords.forEach(chord => allChords.add(chord)); 44 | if(Array.from(labelCounts.keys()).includes(label)){ 45 | labelCounts.set(label, labelCounts.get(label) + 1); 46 | } else { 47 | labelCounts.set(label, 1); 48 | } 49 | }; 50 | 51 | function setLabelProbabilities(){ 52 | labelCounts.forEach(function(_count, label){ 53 | labelProbabilities.set(label, labelCounts.get(label) / songs.length); 54 | }); 55 | }; 56 | 57 | function setChordCountsInLabels(){ 58 | songs.forEach(function(song){ 59 | if(chordCountsInLabels.get(song.label) === undefined){ 60 | chordCountsInLabels.set(song.label, {}); 61 | } 62 | song.chords.forEach(function(chord){ 63 | if(chordCountsInLabels.get(song.label)[chord] > 0){ 64 | chordCountsInLabels.get(song.label)[chord] += 1; 65 | } else { 66 | chordCountsInLabels.get(song.label)[chord] = 1; 67 | } 68 | }); 69 | }); 70 | } 71 | 72 | function setProbabilityOfChordsInLabels(){ 73 | probabilityOfChordsInLabels = chordCountsInLabels; 74 | probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 75 | Object.keys(probabilityOfChordsInLabels.get(difficulty)).forEach( 76 | function(chord){ 77 | probabilityOfChordsInLabels.get(difficulty)[chord] /= songs.length; 78 | }); 79 | }); 80 | } 81 | 82 | function trainAll(){ 83 | setDifficulties(); 84 | setup(); 85 | setSongs(); 86 | train(imagine, easy); 87 | train(somewhereOverTheRainbow, easy); 88 | train(tooManyCooks, easy); 89 | train(iWillFollowYouIntoTheDark, medium); 90 | train(babyOneMoreTime, medium); 91 | train(creep, medium); 92 | train(paperBag, hard); 93 | train(toxic, hard); 94 | train(bulletproof, hard); 95 | setLabelsAndProbabilities(); 96 | }; 97 | 98 | function setLabelsAndProbabilities(){ 99 | setLabelProbabilities(); 100 | setChordCountsInLabels(); 101 | setProbabilityOfChordsInLabels(); 102 | }; 103 | 104 | function classify(chords){ 105 | var smoothing = 1.01; 106 | var classified = new Map(); 107 | labelProbabilities.forEach(function(_probabilities, difficulty){ 108 | var first = labelProbabilities.get(difficulty) + smoothing; 109 | chords.forEach(function(chord){ 110 | var probabilityOfChordInLabel = 111 | probabilityOfChordsInLabels.get(difficulty)[chord]; 112 | if(probabilityOfChordInLabel){ 113 | first = first * (probabilityOfChordInLabel + smoothing); 114 | } 115 | }); 116 | classified.set(difficulty, first); 117 | }); 118 | return classified; 119 | }; 120 | 121 | var wish = require('wish'); 122 | describe('the file', function() { 123 | trainAll(); 124 | it('classifies', function(){ 125 | var classified = classify(['f#m7', 'a', 'dadd9', 126 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 127 | wish(classified.get('easy') === 1.3433333333333333); 128 | wish(classified.get('medium') === 1.5060259259259259); 129 | wish(classified.get('hard') === 1.6884223991769547); 130 | }); 131 | it('classifies again', function(){ 132 | var classified = classify(['d', 'g', 'e', 'dm']); 133 | wish(classified.get('easy') === 2.023094827160494); 134 | wish(classified.get('medium') === 1.855758613168724); 135 | wish(classified.get('hard') === 1.855758613168724); 136 | }); 137 | it('sets welcome message', function(){ 138 | wish(welcomeMessage() === 'Welcome to nb.js!') 139 | }); 140 | it('label probabilities', function(){ 141 | wish(labelProbabilities.get('easy') === 0.3333333333333333); 142 | wish(labelProbabilities.get('medium') === 0.3333333333333333); 143 | wish(labelProbabilities.get('hard') === 0.3333333333333333); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /code/ch07/5_after_handling_remaining_globals.js: -------------------------------------------------------------------------------- 1 | var songList = { 2 | difficulties: ['easy', 'medium', 'hard'], 3 | songs: [], 4 | addSong: function(name, chords, difficulty){ 5 | this.songs.push({name: name, 6 | chords: chords, 7 | difficulty: this.difficulties[difficulty]}) 8 | } 9 | }; 10 | 11 | var classifier = { 12 | songs: [], 13 | allChords: new Set(), 14 | labelCounts: new Map(), 15 | labelProbabilities: new Map(), 16 | chordCountsInLabels: new Map(), 17 | probabilityOfChordsInLabels: new Map() 18 | }; 19 | 20 | function setSongs(){ 21 | songList.addSong('imagine', 22 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 23 | songList.addSong('somewhereOverTheRainbow', 24 | ['c', 'em', 'f', 'g', 'am'], 0); 25 | songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 26 | songList.addSong('iWillFollowYouIntoTheDark', 27 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 28 | songList.addSong('babyOneMoreTime', 29 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 30 | songList.addSong('creep', 31 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 32 | songList.addSong('paperBag', 33 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 34 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 35 | 'b'], 2); 36 | songList.addSong('toxic', 37 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 38 | songList.addSong('bulletproof', 39 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 40 | }; 41 | 42 | function train(chords, label){ 43 | classifier.songs.push({label, chords}); 44 | chords.forEach(chord => classifier.allChords.add(chord)); 45 | if(Array.from(classifier.labelCounts.keys()).includes(label)){ 46 | classifier.labelCounts.set(label, classifier.labelCounts.get(label) + 1); 47 | } else { 48 | classifier.labelCounts.set(label, 1); 49 | } 50 | }; 51 | 52 | function setLabelProbabilities(){ 53 | classifier.labelCounts.forEach(function(_count, label){ 54 | classifier.labelProbabilities.set(label, 55 | classifier.labelCounts.get(label) / classifier.songs.length); 56 | }); 57 | }; 58 | 59 | function setChordCountsInLabels(){ 60 | classifier.songs.forEach(function(song){ 61 | if(classifier.chordCountsInLabels.get(song.label) === undefined){ 62 | classifier.chordCountsInLabels.set(song.label, {}); 63 | } 64 | song.chords.forEach(function(chord){ 65 | if(classifier.chordCountsInLabels.get(song.label)[chord] > 0){ 66 | classifier.chordCountsInLabels.get(song.label)[chord] += 1; 67 | } else { 68 | classifier.chordCountsInLabels.get(song.label)[chord] = 1; 69 | } 70 | }); 71 | }); 72 | } 73 | 74 | function setProbabilityOfChordsInLabels(){ 75 | classifier.probabilityOfChordsInLabels = classifier.chordCountsInLabels; 76 | classifier.probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 77 | Object.keys(classifier.probabilityOfChordsInLabels.get(difficulty)).forEach( 78 | function(chord){ 79 | classifier.probabilityOfChordsInLabels.get(difficulty)[chord] 80 | /= classifier.songs.length; 81 | }); 82 | }); 83 | } 84 | 85 | function trainAll(){ 86 | setSongs(); 87 | songList.songs.forEach(function(song){ 88 | train(song.chords, song.difficulty); 89 | }); 90 | setLabelsAndProbabilities(); 91 | }; 92 | 93 | function setLabelsAndProbabilities(){ 94 | setLabelProbabilities(); 95 | setChordCountsInLabels(); 96 | setProbabilityOfChordsInLabels(); 97 | }; 98 | 99 | function classify(chords){ 100 | var smoothing = 1.01; 101 | var classified = new Map(); 102 | classifier.labelProbabilities.forEach(function(_probabilities, difficulty){ 103 | var first = classifier.labelProbabilities.get(difficulty) + smoothing; 104 | chords.forEach(function(chord){ 105 | var probabilityOfChordInLabel = 106 | classifier.probabilityOfChordsInLabels.get(difficulty)[chord]; 107 | if(probabilityOfChordInLabel){ 108 | first = first * (probabilityOfChordInLabel + smoothing); 109 | } 110 | }); 111 | classified.set(difficulty, first); 112 | }); 113 | return classified; 114 | }; 115 | 116 | var wish = require('wish'); 117 | describe('the file', function() { 118 | trainAll(); 119 | it('classifies', function(){ 120 | var classified = classify(['f#m7', 'a', 'dadd9', 121 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 122 | wish(classified.get('easy') === 1.3433333333333333); 123 | wish(classified.get('medium') === 1.5060259259259259); 124 | wish(classified.get('hard') === 1.6884223991769547); 125 | }); 126 | it('classifies again', function(){ 127 | var classified = classify(['d', 'g', 'e', 'dm']); 128 | wish(classified.get('easy') === 2.023094827160494); 129 | wish(classified.get('medium') === 1.855758613168724); 130 | wish(classified.get('hard') === 1.855758613168724); 131 | }); 132 | it('label probabilities', function(){ 133 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 134 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 135 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /code/ch07/6_after_independent_data.js: -------------------------------------------------------------------------------- 1 | var songList = { 2 | difficulties: ['easy', 'medium', 'hard'], 3 | songs: [], 4 | addSong: function(name, chords, difficulty){ 5 | this.songs.push({name: name, 6 | chords: chords, 7 | difficulty: this.difficulties[difficulty]}) 8 | } 9 | }; 10 | 11 | var classifier = { 12 | songs: [], 13 | allChords: new Set(), 14 | labelCounts: new Map(), 15 | labelProbabilities: new Map(), 16 | chordCountsInLabels: new Map(), 17 | probabilityOfChordsInLabels: new Map() 18 | }; 19 | 20 | function train(chords, label){ 21 | classifier.songs.push({label, chords}); 22 | chords.forEach(chord => classifier.allChords.add(chord)); 23 | if(Array.from(classifier.labelCounts.keys()).includes(label)){ 24 | classifier.labelCounts.set(label, classifier.labelCounts.get(label) + 1); 25 | } else { 26 | classifier.labelCounts.set(label, 1); 27 | } 28 | }; 29 | 30 | function setLabelProbabilities(){ 31 | classifier.labelCounts.forEach(function(_count, label){ 32 | classifier.labelProbabilities.set(label, 33 | classifier.labelCounts.get(label) / classifier.songs.length); 34 | }); 35 | }; 36 | 37 | function setChordCountsInLabels(){ 38 | classifier.songs.forEach(function(song){ 39 | if(classifier.chordCountsInLabels.get(song.label) === undefined){ 40 | classifier.chordCountsInLabels.set(song.label, {}); 41 | } 42 | song.chords.forEach(function(chord){ 43 | if(classifier.chordCountsInLabels.get(song.label)[chord] > 0){ 44 | classifier.chordCountsInLabels.get(song.label)[chord] += 1; 45 | } else { 46 | classifier.chordCountsInLabels.get(song.label)[chord] = 1; 47 | } 48 | }); 49 | }); 50 | } 51 | 52 | function setProbabilityOfChordsInLabels(){ 53 | classifier.probabilityOfChordsInLabels = classifier.chordCountsInLabels; 54 | classifier.probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 55 | Object.keys(classifier.probabilityOfChordsInLabels.get(difficulty)).forEach( 56 | function(chord){ 57 | classifier.probabilityOfChordsInLabels.get(difficulty)[chord] 58 | /= classifier.songs.length; 59 | }); 60 | }); 61 | } 62 | 63 | function trainAll(){ 64 | songList.songs.forEach(function(song){ 65 | train(song.chords, song.difficulty); 66 | }); 67 | setLabelsAndProbabilities(); 68 | }; 69 | 70 | function setLabelsAndProbabilities(){ 71 | setLabelProbabilities(); 72 | setChordCountsInLabels(); 73 | setProbabilityOfChordsInLabels(); 74 | }; 75 | 76 | function classify(chords){ 77 | var smoothing = 1.01; 78 | var classified = new Map(); 79 | classifier.labelProbabilities.forEach(function(_probabilities, difficulty){ 80 | var first = classifier.labelProbabilities.get(difficulty) + smoothing; 81 | chords.forEach(function(chord){ 82 | var probabilityOfChordInLabel = 83 | classifier.probabilityOfChordsInLabels.get(difficulty)[chord]; 84 | if(probabilityOfChordInLabel){ 85 | first = first * (probabilityOfChordInLabel + smoothing); 86 | } 87 | }); 88 | classified.set(difficulty, first); 89 | }); 90 | return classified; 91 | }; 92 | 93 | var wish = require('wish'); 94 | describe('the file', function() { 95 | songList.addSong('imagine', 96 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 97 | songList.addSong('somewhereOverTheRainbow', 98 | ['c', 'em', 'f', 'g', 'am'], 0); 99 | songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 100 | songList.addSong('iWillFollowYouIntoTheDark', 101 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 102 | songList.addSong('babyOneMoreTime', 103 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 104 | songList.addSong('creep', 105 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 106 | songList.addSong('paperBag', 107 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 108 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 109 | 'b'], 2); 110 | songList.addSong('toxic', 111 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 112 | songList.addSong('bulletproof', 113 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 114 | trainAll(); 115 | it('classifies', function(){ 116 | var classified = classify(['f#m7', 'a', 'dadd9', 117 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 118 | wish(classified.get('easy') === 1.3433333333333333); 119 | wish(classified.get('medium') === 1.5060259259259259); 120 | wish(classified.get('hard') === 1.6884223991769547); 121 | }); 122 | it('classifies again', function(){ 123 | var classified = classify(['d', 'g', 'e', 'dm']); 124 | wish(classified.get('easy') === 2.023094827160494); 125 | wish(classified.get('medium') === 1.855758613168724); 126 | wish(classified.get('hard') === 1.855758613168724); 127 | }); 128 | it('label probabilities', function(){ 129 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 130 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 131 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /code/ch07/7_after_bringing_classify_in.js: -------------------------------------------------------------------------------- 1 | const songList = { 2 | difficulties: ['easy', 'medium', 'hard'], 3 | songs: [], 4 | addSong: function(name, chords, difficulty){ 5 | this.songs.push({name: name, 6 | chords: chords, 7 | difficulty: this.difficulties[difficulty]}) 8 | } 9 | }; 10 | 11 | const classifier = { 12 | songs: [], 13 | allChords: new Set(), 14 | labelCounts: new Map(), 15 | labelProbabilities: new Map(), 16 | chordCountsInLabels: new Map(), 17 | probabilityOfChordsInLabels: new Map(), 18 | smoothing: 1.01, 19 | valueForChordDifficulty(difficulty, chord){ 20 | const value = 21 | this.probabilityOfChordsInLabels.get(difficulty)[chord]; 22 | return value ? value + this.smoothing : 1; 23 | }, 24 | classify: function(chords){ 25 | return new Map(Array.from( 26 | this.labelProbabilities.entries()).map((labelWithProbability) => { 27 | const difficulty = labelWithProbability[0]; 28 | return [difficulty, chords.reduce((total, chord) => { 29 | return total * this.valueForChordDifficulty(difficulty, chord); 30 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 31 | })); 32 | } 33 | }; 34 | 35 | function train(chords, label){ 36 | classifier.songs.push({label, chords}); 37 | chords.forEach(chord => classifier.allChords.add(chord)); 38 | if(Array.from(classifier.labelCounts.keys()).includes(label)){ 39 | classifier.labelCounts.set(label, classifier.labelCounts.get(label) + 1); 40 | } else { 41 | classifier.labelCounts.set(label, 1); 42 | } 43 | }; 44 | 45 | function setLabelProbabilities(){ 46 | classifier.labelCounts.forEach(function(_count, label){ 47 | classifier.labelProbabilities.set(label, 48 | classifier.labelCounts.get(label) / classifier.songs.length); 49 | }); 50 | }; 51 | 52 | function setChordCountsInLabels(){ 53 | classifier.songs.forEach(function(song){ 54 | if(classifier.chordCountsInLabels.get(song.label) === undefined){ 55 | classifier.chordCountsInLabels.set(song.label, {}); 56 | } 57 | song.chords.forEach(function(chord){ 58 | if(classifier.chordCountsInLabels.get(song.label)[chord] > 0){ 59 | classifier.chordCountsInLabels.get(song.label)[chord] += 1; 60 | } else { 61 | classifier.chordCountsInLabels.get(song.label)[chord] = 1; 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | function setProbabilityOfChordsInLabels(){ 68 | classifier.probabilityOfChordsInLabels = classifier.chordCountsInLabels; 69 | classifier.probabilityOfChordsInLabels.forEach(function(_chords, difficulty){ 70 | Object.keys(classifier.probabilityOfChordsInLabels.get(difficulty)).forEach( 71 | function(chord){ 72 | classifier.probabilityOfChordsInLabels.get(difficulty)[chord] 73 | /= classifier.songs.length; 74 | }); 75 | }); 76 | } 77 | 78 | function trainAll(){ 79 | songList.songs.forEach(function(song){ 80 | train(song.chords, song.difficulty); 81 | }); 82 | setLabelsAndProbabilities(); 83 | }; 84 | 85 | function setLabelsAndProbabilities(){ 86 | setLabelProbabilities(); 87 | setChordCountsInLabels(); 88 | setProbabilityOfChordsInLabels(); 89 | }; 90 | 91 | const wish = require('wish'); 92 | describe('the file', function() { 93 | songList.addSong('imagine', 94 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 95 | songList.addSong('somewhereOverTheRainbow', 96 | ['c', 'em', 'f', 'g', 'am'], 0); 97 | songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 98 | songList.addSong('iWillFollowYouIntoTheDark', 99 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 100 | songList.addSong('babyOneMoreTime', 101 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 102 | songList.addSong('creep', 103 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 104 | songList.addSong('paperBag', 105 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 106 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 107 | 'b'], 2); 108 | songList.addSong('toxic', 109 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 110 | songList.addSong('bulletproof', 111 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 112 | trainAll(); 113 | it('classifies', function(){ 114 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 115 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 116 | wish(classified.get('easy') === 1.3433333333333333); 117 | wish(classified.get('medium') === 1.5060259259259259); 118 | wish(classified.get('hard') === 1.6884223991769547); 119 | }); 120 | it('classifies again', function(){ 121 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 122 | wish(classified.get('easy') === 2.023094827160494); 123 | wish(classified.get('medium') === 1.855758613168724); 124 | wish(classified.get('hard') === 1.855758613168724); 125 | }); 126 | it('label probabilities', function(){ 127 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 128 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 129 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /code/ch07/8_after_untangling_coupled_values.js: -------------------------------------------------------------------------------- 1 | const songList = { 2 | difficulties: ['easy', 'medium', 'hard'], 3 | songs: [], 4 | addSong: function(name, chords, difficulty){ 5 | this.songs.push({name: name, 6 | chords: chords, 7 | difficulty: this.difficulties[difficulty]}) 8 | } 9 | }; 10 | 11 | const classifier = { 12 | songs: [], 13 | allChords: new Set(), 14 | labelCounts: new Map(), 15 | labelProbabilities: new Map(), 16 | chordCountsInLabels: new Map(), 17 | smoothing: 1.01, 18 | likelihoodFromChord: function(difficulty, chord){ 19 | return this.chordCountsInLabels 20 | .get(difficulty)[chord] / this.songs.length; 21 | }, 22 | valueForChordDifficulty(difficulty, chord){ 23 | const value = this.likelihoodFromChord(difficulty, chord); 24 | return value ? value + this.smoothing : 1; 25 | }, 26 | classify: function(chords){ 27 | return new Map(Array.from( 28 | this.labelProbabilities.entries()).map((labelWithProbability) => { 29 | const difficulty = labelWithProbability[0]; 30 | return [difficulty, chords.reduce((total, chord) => { 31 | return total * this.valueForChordDifficulty(difficulty, chord); 32 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 33 | })); 34 | } 35 | }; 36 | 37 | function train(chords, label){ 38 | classifier.songs.push({label, chords}); 39 | chords.forEach(chord => classifier.allChords.add(chord)); 40 | if(Array.from(classifier.labelCounts.keys()).includes(label)){ 41 | classifier.labelCounts.set(label, classifier.labelCounts.get(label) + 1); 42 | } else { 43 | classifier.labelCounts.set(label, 1); 44 | } 45 | }; 46 | 47 | function setLabelProbabilities(){ 48 | classifier.labelCounts.forEach(function(_count, label){ 49 | classifier.labelProbabilities.set(label, 50 | classifier.labelCounts.get(label) / classifier.songs.length); 51 | }); 52 | }; 53 | 54 | function setChordCountsInLabels(){ 55 | classifier.songs.forEach(function(song){ 56 | if(classifier.chordCountsInLabels.get(song.label) === undefined){ 57 | classifier.chordCountsInLabels.set(song.label, {}); 58 | } 59 | song.chords.forEach(function(chord){ 60 | if(classifier.chordCountsInLabels.get(song.label)[chord] > 0){ 61 | classifier.chordCountsInLabels.get(song.label)[chord] += 1; 62 | } else { 63 | classifier.chordCountsInLabels.get(song.label)[chord] = 1; 64 | } 65 | }); 66 | }); 67 | } 68 | 69 | function trainAll(){ 70 | songList.songs.forEach(function(song){ 71 | train(song.chords, song.difficulty); 72 | }); 73 | setLabelsAndProbabilities(); 74 | }; 75 | 76 | function setLabelsAndProbabilities(){ 77 | setLabelProbabilities(); 78 | setChordCountsInLabels(); 79 | }; 80 | 81 | const wish = require('wish'); 82 | describe('the file', function() { 83 | songList.addSong('imagine', 84 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 85 | songList.addSong('somewhereOverTheRainbow', 86 | ['c', 'em', 'f', 'g', 'am'], 0); 87 | songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 88 | songList.addSong('iWillFollowYouIntoTheDark', 89 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 90 | songList.addSong('babyOneMoreTime', 91 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 92 | songList.addSong('creep', 93 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 94 | songList.addSong('paperBag', 95 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 96 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 97 | 'b'], 2); 98 | songList.addSong('toxic', 99 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 100 | songList.addSong('bulletproof', 101 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 102 | trainAll(); 103 | it('classifies', function(){ 104 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 105 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 106 | wish(classified.get('easy') === 1.3433333333333333); 107 | wish(classified.get('medium') === 1.5060259259259259); 108 | wish(classified.get('hard') === 1.6884223991769547); 109 | }); 110 | it('classifies again', function(){ 111 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 112 | wish(classified.get('easy') === 2.023094827160494); 113 | wish(classified.get('medium') === 1.855758613168724); 114 | wish(classified.get('hard') === 1.855758613168724); 115 | }); 116 | it('label probabilities', function(){ 117 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 118 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 119 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /code/ch07/9_after_bringing_other_functions_and_vars.js: -------------------------------------------------------------------------------- 1 | const classifier = { 2 | labelCounts: new Map(), 3 | labelProbabilities: new Map(), 4 | chordCountsInLabels: new Map(), 5 | smoothing: 1.01, 6 | songList: { 7 | allChords: new Set(), 8 | difficulties: ['easy', 'medium', 'hard'], 9 | songs: [], 10 | addSong: function(name, chords, difficulty){ 11 | this.songs.push({name: name, 12 | chords: chords, 13 | difficulty: this.difficulties[difficulty]}) 14 | } 15 | }, 16 | chordCountForDifficulty: function(difficulty, testChord){ 17 | return this.songList.songs.reduce(function(counter, song){ 18 | if(song.difficulty === difficulty){ 19 | counter += song.chords.filter(function(chord){ 20 | return chord === testChord; 21 | }).length; 22 | } 23 | return counter; 24 | }, 0); 25 | }, 26 | 27 | likelihoodFromChord: function(difficulty, chord){ 28 | return this.chordCountForDifficulty(difficulty, chord) / 29 | this.songList.songs.length; 30 | }, 31 | valueForChordDifficulty(difficulty, chord){ 32 | const value = this.likelihoodFromChord(difficulty, chord); 33 | return value ? value + this.smoothing : 1; 34 | }, 35 | trainAll: function(){ 36 | this.songList.songs.forEach(function(song){ 37 | this.train(song.chords, song.difficulty); 38 | }, this); 39 | this.setLabelProbabilities(); 40 | }, 41 | 42 | train: function(chords, label){ 43 | chords.forEach(chord => { this.songList.allChords.add(chord) } ); 44 | if(Array.from(this.labelCounts.keys()).includes(label)){ 45 | this.labelCounts.set(label, this.labelCounts.get(label) + 1); 46 | } else { 47 | this.labelCounts.set(label, 1); 48 | } 49 | }, 50 | 51 | setLabelProbabilities: function(){ 52 | this.labelCounts.forEach(function(_count, label){ 53 | this.labelProbabilities.set(label, this.labelCounts.get(label) / 54 | this.songList.songs.length); 55 | }, this); 56 | }, 57 | 58 | classify: function(chords){ 59 | return new Map(Array.from( 60 | this.labelProbabilities.entries()).map((labelWithProbability) => { 61 | const difficulty = labelWithProbability[0]; 62 | return [difficulty, chords.reduce((total, chord) => { 63 | return total * this.valueForChordDifficulty(difficulty, chord); 64 | }, this.labelProbabilities.get(difficulty) + this.smoothing)]; 65 | })); 66 | } 67 | }; 68 | 69 | const wish = require('wish'); 70 | describe('the file', function() { 71 | classifier.songList.addSong('imagine', 72 | ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7'], 0); 73 | classifier.songList.addSong('somewhereOverTheRainbow', 74 | ['c', 'em', 'f', 'g', 'am'], 0); 75 | classifier.songList.addSong('tooManyCooks', ['c', 'g', 'f'], 0); 76 | classifier.songList.addSong('iWillFollowYouIntoTheDark', 77 | ['f', 'dm', 'bb', 'c', 'a', 'bbm'], 1); 78 | classifier.songList.addSong('babyOneMoreTime', 79 | ['cm', 'g', 'bb', 'eb', 'fm', 'ab'], 1); 80 | classifier.songList.addSong('creep', 81 | ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6'], 1); 82 | classifier.songList.addSong('paperBag', 83 | ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 84 | 'a', 'cmaj7', 'em7', 'a7', 'f7', 85 | 'b'], 2); 86 | classifier.songList.addSong('toxic', 87 | ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7'], 2); 88 | classifier.songList.addSong('bulletproof', 89 | ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#'], 2); 90 | classifier.trainAll(); 91 | it('classifies', function(){ 92 | const classified = classifier.classify(['f#m7', 'a', 'dadd9', 93 | 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 94 | wish(classified.get('easy') === 1.3433333333333333); 95 | wish(classified.get('medium') === 1.5060259259259259); 96 | wish(classified.get('hard') === 1.6884223991769547); 97 | }); 98 | it('classifies again', function(){ 99 | const classified = classifier.classify(['d', 'g', 'e', 'dm']); 100 | wish(classified.get('easy') === 2.023094827160494); 101 | wish(classified.get('medium') === 1.855758613168724); 102 | wish(classified.get('hard') === 1.855758613168724); 103 | }); 104 | it('label probabilities', function(){ 105 | wish(classifier.labelProbabilities.get('easy') === 0.3333333333333333); 106 | wish(classifier.labelProbabilities.get('medium') === 0.3333333333333333); 107 | wish(classifier.labelProbabilities.get('hard') === 0.3333333333333333); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /code/ch07/secrets.js: -------------------------------------------------------------------------------- 1 | // Constructor Function 2 | const Secret = function(){ 3 | this.normalInfo = 'this is normal'; 4 | const secret = 'sekrit'; 5 | const secretFunction = function(){ 6 | return secret; 7 | } 8 | this.notSecret = function(){ 9 | return secret; 10 | }; 11 | totallyNotSecret = "I'm defined in the global scope"; 12 | }; 13 | const s = new Secret(); 14 | console.log(s.normalInfo); // 'this is normal' 15 | console.log(s.secret); // undefined 16 | console.log(s.secretFunction()); // error 17 | console.log(s.notSecret()); // 'sekrit' 18 | console.log(s.totallyNotSecret); // undefined 19 | console.log(totallyNotSecret); // I'm defined in the global scope 20 | 21 | // Factory Function 22 | var secretTemplate = (function(){ 23 | var obj = {}; 24 | obj.normalInfo = 'this is normal'; 25 | const secret = 'sekrit'; 26 | const secretFunction = function(){ 27 | return secret; 28 | }; 29 | obj.notSecret = function(){ 30 | return secret; 31 | }; 32 | totallyNotSecret = "I'm defined in the global scope"; 33 | return obj; 34 | })(); 35 | const s = Object.create(secretTemplate); 36 | console.log(s.normalInfo); // 'this is normal' 37 | console.log(s.secret); // undefined 38 | console.log(s.secretFunction()); // error 39 | console.log(s.notSecret()); // 'sekrit' 40 | console.log(s.totallyNotSecret); // undefined 41 | console.log(totallyNotSecret); // "I'm defined in the global scope" 42 | 43 | // Factory Function (concise) 44 | var secretTemplate = (function(){ 45 | const secret = 'sekrit'; 46 | const secretFunction = function(){ 47 | return secret; 48 | }; 49 | totallyNotSecret = "I'm defined in the global scope"; 50 | return {normalInfo: 'this is normal', 51 | notSecret(){ 52 | return secret; 53 | }} 54 | })(); 55 | const s = Object.create(secretTemplate); 56 | console.log(s.normalInfo); // 'this is normal' 57 | console.log(s.secret); // undefined 58 | console.log(s.secretFunction()); // error 59 | console.log(s.notSecret()); // 'sekrit' 60 | console.log(s.totallyNotSecret); // undefined 61 | console.log(totallyNotSecret); // "I'm defined in the global scope" 62 | 63 | // Module Pattern 64 | var secretTemplate = (function(){ 65 | const secret = 'sekrit'; 66 | const secretFunction = function(){ 67 | return secret; 68 | }; 69 | totallyNotSecret = "I'm defined in the global scope"; 70 | return {normalInfo: 'this is normal', 71 | notSecret(){ 72 | return secret; 73 | }}; 74 | })(); 75 | const s = Object.create(secretTemplate); 76 | console.log(s.normalInfo); // 'this is normal' 77 | console.log(s.secret); // undefined 78 | console.log(s.secretFunction()); // error 79 | console.log(s.notSecret()); // 'sekrit' 80 | console.log(s.totallyNotSecret); // undefined 81 | console.log(totallyNotSecret); // "I'm defined in the global scope" 82 | 83 | // Revealing Module Pattern 84 | var secretTemplate = (function(){ 85 | const secret = 'sekrit'; 86 | const secretFunction = function(){ 87 | return secret; 88 | }; 89 | totallyNotSecret = "I'm defined in the global scope"; 90 | const normalInfo = 'this is normal'; 91 | const notSecret = function(){ 92 | return secret; 93 | }; 94 | return {normalInfo, notSecret}; 95 | })(); 96 | const s = Object.create(secretTemplate); 97 | console.log(s.normalInfo); // 'this is normal' 98 | console.log(s.secret); // undefined 99 | console.log(s.secretFunction()); // error 100 | console.log(s.notSecret()); // 'sekrit' 101 | console.log(s.totallyNotSecret); // undefined 102 | console.log(totallyNotSecret); // "I'm defined in the global scope" 103 | -------------------------------------------------------------------------------- /code/ch08/1_lets_build_a_hierarchy.js: -------------------------------------------------------------------------------- 1 | // // Two Classes 2 | // class EnglishWord{ 3 | // constructor(word){ 4 | // this.word = word; 5 | // }; 6 | // count(){ 7 | // return this.word.length; 8 | // }; 9 | // }; 10 | // class JapaneseWord{ 11 | // constructor(word){ 12 | // this.word = word; 13 | // } 14 | // count(){ 15 | // return this.word.length; 16 | // } 17 | // }; 18 | // const japaneseWord = new JapaneseWord("犬"); 19 | // const englishWord = new EnglishWord("dog"); 20 | // console.log(japaneseWord.word); 21 | // console.log(japaneseWord.count()); 22 | // console.log(englishWord.word); 23 | // console.log(englishWord.count()); 24 | // 25 | // // With Parent Class 26 | // class Word{ 27 | // constructor(word){ 28 | // this.word = word; 29 | // } 30 | // count(){ 31 | // return this.word.length; 32 | // } 33 | // }; 34 | // 35 | // class EnglishWord extends Word{}; 36 | // class JapaneseWord extends Word{}; 37 | // 38 | // const japaneseWord = new JapaneseWord("犬"); 39 | // const englishWord = new EnglishWord("dog"); 40 | // console.log(japaneseWord.count()); 41 | // console.log(japaneseWord.word); 42 | // console.log(englishWord.count()); 43 | // console.log(englishWord.word); 44 | 45 | // Final 46 | 47 | class Word{ 48 | constructor(word, language, lookUpUrl){ 49 | this.word = word; 50 | this.language = language; 51 | this.lookUpUrl = lookUpUrl; 52 | }; 53 | count(){ 54 | return this.word.length; 55 | }; 56 | lookUp(){ 57 | return this.lookUpUrl + this.word; 58 | }; 59 | }; 60 | class EnglishWord extends Word{ 61 | constructor(word){ 62 | super(word, 'English', 'https://en.wiktionary.org/wiki/'); 63 | }; 64 | }; 65 | class JapaneseWord extends Word{ 66 | constructor(word){ 67 | super(word, 'Japanese', 'http://jisho.org/search/'); 68 | }; 69 | }; 70 | 71 | const japaneseWord = new JapaneseWord("犬"); 72 | const englishWord = new EnglishWord("dog"); 73 | 74 | const wish = require('wish'); 75 | const deepEqual = require('deep-equal') 76 | 77 | // interfaces tests 78 | wish(japaneseWord.word === "犬"); 79 | wish(japaneseWord.lookUp() === "http://jisho.org/search/犬"); 80 | wish(japaneseWord.count() === 1); 81 | 82 | wish(englishWord.word === "dog"); 83 | wish(englishWord.lookUp() === "https://en.wiktionary.org/wiki/dog"); 84 | wish(englishWord.count() === 3); 85 | 86 | // internals tests 87 | wish(typeof japaneseWord === 'object'); 88 | wish(typeof JapaneseWord === 'function'); 89 | wish(japaneseWord instanceof JapaneseWord); 90 | wish(japaneseWord instanceof Word); 91 | wish(!(JapaneseWord instanceof Word)); 92 | 93 | wish(japaneseWord.constructor === JapaneseWord); 94 | wish(Object.getPrototypeOf(JapaneseWord) === Word); 95 | 96 | // sketchy bits 97 | wish(deepEqual(Object.getPrototypeOf(japaneseWord), {})); 98 | console.log(Object.getPrototypeOf(japaneseWord)); 99 | // reports JapaneseWord {} 100 | -------------------------------------------------------------------------------- /code/ch08/2_lets_wreck_our_hierarchy.js: -------------------------------------------------------------------------------- 1 | 2 | function Word(word, language, lookUpUrl){ 3 | this.word = word; 4 | this.language = language; 5 | this.lookUpUrl = lookUpUrl; 6 | this.count = function(){ 7 | return this.word.length; 8 | }; 9 | this.lookUp = function(){ 10 | return this.lookUpUrl + this.word; 11 | }; 12 | }; 13 | 14 | function EnglishWord(word){ 15 | Word.call(this, word, "English", 'https://en.wiktionary.org/wiki/'); 16 | }; 17 | 18 | function JapaneseWord(word){ 19 | Word.call(this, word, "Japanese", 'http://jisho.org/search/'); 20 | }; 21 | 22 | /* Variant 23 | function Word(word, language, lookUpUrl){ 24 | this.word = word; 25 | this.language = language; 26 | this.lookUpUrl = lookUpUrl; 27 | this.count = function(){ 28 | return this.word.length; 29 | }; 30 | this.lookUp = function(){ 31 | return this.lookUpUrl + this.word; 32 | }; 33 | }; 34 | 35 | function EnglishWord(word){ 36 | Word.call(this, word, "English", 'https://en.wiktionary.org/wiki/'); 37 | }; 38 | 39 | function JapaneseWord(word){ 40 | Word.call(this, word, "Japanese", 'http://jisho.org/search/'); 41 | }; 42 | */ 43 | 44 | 45 | JapaneseWord.prototype = Object.create(Word.prototype); 46 | JapaneseWord.prototype.constructor = JapaneseWord; 47 | EnglishWord.prototype = Object.create(Word.prototype); 48 | EnglishWord.prototype.constructor = EnglishWord; 49 | 50 | 51 | Word.prototype.reportLanguage = function(){ 52 | return `The language is: ${this.language}`; 53 | }; 54 | const japaneseWord = new JapaneseWord("犬"); 55 | console.log(japaneseWord.reportLanguage()); 56 | 57 | const englishWord = new EnglishWord("dog"); 58 | 59 | const wish = require('wish'); 60 | const deepEqual = require('deep-equal'); 61 | 62 | // interfaces tests 63 | wish(japaneseWord.word === "犬"); 64 | wish(japaneseWord.lookUp() === "http://jisho.org/search/犬"); 65 | wish(japaneseWord.count() === 1); 66 | 67 | wish(englishWord.word === "dog"); 68 | wish(englishWord.lookUp() === "https://en.wiktionary.org/wiki/dog"); 69 | wish(englishWord.count() === 3); 70 | 71 | // internals tests 72 | wish(typeof japaneseWord === 'object'); 73 | wish(typeof JapaneseWord === 'function'); 74 | wish(japaneseWord instanceof JapaneseWord); 75 | wish(japaneseWord instanceof Word); 76 | wish(!(JapaneseWord instanceof Word)); 77 | 78 | wish(japaneseWord.constructor === JapaneseWord); 79 | // wish(Object.getPrototypeOf(JapaneseWord) === Word); 80 | 81 | // sketchy bits 82 | // wish(deepEqual(Object.getPrototypeOf(japaneseWord), {})); 83 | console.log(Object.getPrototypeOf(japaneseWord)); 84 | // JapaneseWord { constructor: [Function: JapaneseWord] } 85 | -------------------------------------------------------------------------------- /code/ch08/3_object_literals.js: -------------------------------------------------------------------------------- 1 | const word = { 2 | count(){ 3 | return this.word.length; 4 | }, 5 | lookUp(){ 6 | return this.lookUpUrl + this.word; 7 | } 8 | }; 9 | /* Variant 10 | const englishWord = Object.create(word); 11 | englishWord.word = 'dog'; 12 | englishWord.language = 'English'; 13 | englishWord.lookUpUrl = 'https://en.wiktionary.org/wiki/'; 14 | 15 | const japaneseWord = Object.create(word); 16 | japaneseWord.word = '犬'; 17 | japaneseWord.language = 'Japanese'; 18 | japaneseWord.lookUpUrl = 'http://jisho.org/search/'; 19 | */ 20 | 21 | const englishWord = Object.assign(Object.create(word), 22 | {word: 'dog', 23 | language: 'English', 24 | lookUpUrl: 'https://en.wiktionary.org/wiki/'}); 25 | 26 | const japaneseWord = Object.assign(Object.create(word), 27 | {word: '犬', 28 | language: 'japanese', 29 | lookUpUrl: 'http://jisho.org/search/'}); 30 | 31 | 32 | 33 | const wish = require('wish'); 34 | const deepEqual = require('deep-equal'); 35 | 36 | // interfaces tests 37 | wish(japaneseWord.word === "犬"); 38 | wish(japaneseWord.lookUp() === "http://jisho.org/search/犬"); 39 | wish(japaneseWord.count() === 1); 40 | 41 | wish(englishWord.word === "dog"); 42 | wish(englishWord.lookUp() === "https://en.wiktionary.org/wiki/dog"); 43 | wish(englishWord.count() === 3); 44 | 45 | // internals tests 46 | wish(typeof japaneseWord === 'object'); 47 | console.log(Object.getPrototypeOf(japaneseWord)); 48 | -------------------------------------------------------------------------------- /code/ch08/4_factory_functions.js: -------------------------------------------------------------------------------- 1 | const word = { 2 | count(){ 3 | return this.word.length; 4 | }, 5 | lookUp(){ 6 | return this.lookUpUrl + this.word; 7 | } 8 | } 9 | const englishWordFactory = (theWord) => { 10 | return Object.assign(Object.create(word), 11 | {word: theWord, 12 | language: 'English', 13 | lookUpUrl: 'https://en.wiktionary.org/wiki/'}) 14 | }; 15 | 16 | const japaneseWordFactory = (theWord) => { 17 | return Object.assign(Object.create(word), 18 | {word: theWord, 19 | language: 'Japanese', 20 | lookUpUrl: 'http://jisho.org/search/'}) 21 | }; 22 | 23 | 24 | const wish = require('wish'); 25 | const deepEqual = require('deep-equal'); 26 | 27 | 28 | const englishWord = englishWordFactory('dog'); 29 | const japaneseWord = japaneseWordFactory('犬'); 30 | 31 | japaneseWord.prototype = word; 32 | englishWord.prototype = word; 33 | 34 | word.reportLanguage = function(){ 35 | return `The language is: ${this.language}`; 36 | }; 37 | 38 | console.log(japaneseWord.reportLanguage()); 39 | console.log(englishWord.reportLanguage()); 40 | 41 | 42 | 43 | // interfaces tests 44 | wish(japaneseWord.word === "犬"); 45 | wish(japaneseWord.lookUp() === "http://jisho.org/search/犬"); 46 | wish(japaneseWord.count() === 1); 47 | 48 | wish(englishWord.word === "dog"); 49 | wish(englishWord.lookUp() === "https://en.wiktionary.org/wiki/dog"); 50 | wish(englishWord.count() === 3); 51 | -------------------------------------------------------------------------------- /code/ch08/4_factory_functions_with_prototype.js: -------------------------------------------------------------------------------- 1 | const wordFactory = function(){ 2 | return {count(){ 3 | return this.word.length; 4 | }, 5 | lookUp(){ 6 | return this.lookUpUrl + this.word; 7 | } 8 | }; 9 | }; 10 | 11 | const englishWordFactory = (theWord) => { 12 | let copy = Object.assign(wordFactory(), 13 | {word: theWord, 14 | language: 'English', 15 | lookUpUrl: 'https://en.wiktionary.org/wiki/'}) 16 | return Object.setPrototypeOf(copy, wordFactory); 17 | }; 18 | 19 | const japaneseWordFactory = (theWord) =>{ 20 | let copy = Object.assign(wordFactory(), 21 | {word: theWord, 22 | language: 'Japanese', 23 | lookUpUrl: 'http://jisho.org/search/'}) 24 | return Object.setPrototypeOf(copy, wordFactory); 25 | }; 26 | const englishWord = englishWordFactory('dog'); 27 | const japaneseWord = japaneseWordFactory('犬'); 28 | 29 | wordFactory.reportLanguage = function(){ 30 | return `The language is: ${this.language}`; 31 | }; 32 | 33 | console.log(japaneseWord.reportLanguage()); 34 | console.log(englishWord.reportLanguage()); 35 | 36 | const wish = require('wish'); 37 | const deepEqual = require('deep-equal'); 38 | 39 | 40 | // interfaces tests 41 | wish(japaneseWord.word === "犬"); 42 | wish(japaneseWord.lookUp() === "http://jisho.org/search/犬"); 43 | wish(japaneseWord.count() === 1); 44 | 45 | wish(englishWord.word === "dog"); 46 | wish(englishWord.lookUp() === "https://en.wiktionary.org/wiki/dog"); 47 | wish(englishWord.count() === 3); 48 | -------------------------------------------------------------------------------- /code/ch08/5_multiple_inheritance.js: -------------------------------------------------------------------------------- 1 | // class Barky{ 2 | // bark(){ console.log('woof woof')}; 3 | // }; 4 | // class Bitey{ 5 | // bark(){ console.log('grrr')}; 6 | // bite(){ console.log('real bite')}; 7 | // }; 8 | // class Animal{ 9 | // beFluffy(){ console.log('fluffy')}; 10 | // bite(){ console.log('normal bite')}; 11 | // }; 12 | // 13 | // // this is not possible: 14 | // class Dog extends (Animal, Barky, Bitey) { }; 15 | // dog = new Dog; 16 | // dog.bite(); 17 | // dog.beFluffy(); // this won't work 18 | 19 | // Multiple Inheritance 20 | 21 | const barky = { 22 | bark(){ console.log('woof woof')} 23 | }; 24 | const bitey = { 25 | bark(){ console.log('grrr')}, 26 | bite(){ console.log('real bite')} 27 | }; 28 | const animal = { 29 | beFluffy(){ console.log('fluffy')}, 30 | bite(){ console.log('normal bite')} 31 | }; 32 | const myPet = Object.assign(Object.create(animal), barky, bitey); 33 | myPet.beFluffy(); 34 | myPet.bite(); 35 | -------------------------------------------------------------------------------- /code/ch08/6_hyperextension.js: -------------------------------------------------------------------------------- 1 | class Report{ 2 | constructor(params){ 3 | this.params = params; 4 | }; 5 | printReport(params){ 6 | return params; 7 | }; 8 | } 9 | class GenericReport extends Report{ 10 | constructor(params){ 11 | super(params); 12 | this.params = params; 13 | }; 14 | printReport(params){ 15 | return super.printReport(Object.assign(this.params, params)); 16 | }; 17 | }; 18 | class ClientReport extends GenericReport{ 19 | constructor(params){ 20 | super(params); 21 | this.params = params; 22 | }; 23 | printReport(params){ 24 | return super.printReport(Object.assign(this.params, params)); 25 | }; 26 | }; 27 | class SpecificClientReport extends ClientReport{ 28 | constructor(params){ 29 | super(params); 30 | this.params = params; 31 | }; 32 | printReport(params){ 33 | return super.printReport(Object.assign(this.params, params)); 34 | }; 35 | }; 36 | report = new SpecificClientReport({whatever: 'we want', to: 'add'}); 37 | 38 | const wish = require('wish'); 39 | const deepEqual = require('deep-equal'); 40 | wish(deepEqual(report.printReport({extra: 'params'}), 41 | {whatever:"we want", to:"add", extra:"params"})); 42 | -------------------------------------------------------------------------------- /code/ch08/6_hyperextension_fixed.js: -------------------------------------------------------------------------------- 1 | class Report{ 2 | constructor(params){ 3 | this.params = params; 4 | }; 5 | printReport(params){ 6 | return Object.assign(this.params, params); 7 | }; 8 | }; 9 | 10 | 11 | const wish = require('wish'); 12 | const deepEqual = require('deep-equal'); 13 | const report = new Report({whatever: 'we want', to: 'add'}); 14 | wish(deepEqual(report.printReport({extra: 'params'}), 15 | {whatever:"we want", to:"add", extra:"params"})) 16 | -------------------------------------------------------------------------------- /code/ch08/7_goat_cabbage_wolf.js: -------------------------------------------------------------------------------- 1 | class Agent{ 2 | constructor(name, type){ 3 | this.name = 'name'; 4 | if(Math.random() > .5){ 5 | this.type = 'user'; 6 | }else{ 7 | this.type = 'project'; 8 | } 9 | }; 10 | static makeProjectOrUser(agent){ 11 | if(agent.type === 'user'){ 12 | return Object.assign(Object.create(new User), agent); 13 | }else{ 14 | return Object.assign(Object.create(new Project), agent); 15 | } 16 | }; 17 | }; 18 | 19 | class User extends Agent{ 20 | sayName(){ 21 | return `my name is ${this.name}`; 22 | } 23 | }; 24 | 25 | class Project extends Agent{ 26 | sayTheName(){ 27 | return `the project name is ${this.name}`; 28 | } 29 | }; 30 | 31 | const agent = new Agent('name'); 32 | const projectOrUser = Agent.makeProjectOrUser(agent); 33 | if(projectOrUser.type === 'user'){ 34 | console.log(projectOrUser.sayName()); 35 | }else{ 36 | console.log(projectOrUser.sayTheName()); 37 | } 38 | const wish = require('wish'); 39 | if(projectOrUser.type === 'user'){ 40 | wish(projectOrUser.sayName() === "my name is name"); 41 | }else{ 42 | wish(projectOrUser.sayTheName() === "the project name is name"); 43 | } 44 | -------------------------------------------------------------------------------- /code/ch08/7_goat_cabbage_wolf_fixed.js: -------------------------------------------------------------------------------- 1 | function coinToss(){ 2 | return Math.random() > .5; 3 | }; 4 | 5 | class User{ 6 | constructor(name){ 7 | this.name = name; 8 | }; 9 | sayName(){ 10 | return `my name is ${this.name}`; 11 | }; 12 | }; 13 | 14 | class Project{ 15 | constructor(name){ 16 | this.name = name; 17 | }; 18 | sayTheName(){ 19 | return `the project name is ${this.name}`; 20 | }; 21 | }; 22 | 23 | let agent; 24 | if(coinToss()){ 25 | agent = new User('name'); 26 | }else{ 27 | agent = new Project('name'); 28 | } 29 | 30 | const wish = require('wish'); 31 | if(agent instanceof User){ 32 | wish(agent.sayName() === "my name is name"); 33 | }else{ 34 | wish(agent.sayTheName() === "the project name is name"); 35 | } 36 | 37 | wish(new User('name', 'user').sayName() === "my name is name"); 38 | wish(new Project('name', 'project').sayTheName() 39 | === "the project name is name"); 40 | -------------------------------------------------------------------------------- /code/ch09/1_template.js: -------------------------------------------------------------------------------- 1 | class Person{ 2 | constructor(binaryKnower){ 3 | this.binaryKnower = binaryKnower; 4 | }; 5 | whatIs(number){ return number }; 6 | whatIsInBinary(number){ return Number('0b' + number) }; 7 | }; 8 | 9 | const personOne = new Person(true); 10 | const personTwo = new Person(false); 11 | 12 | [personOne, personTwo].forEach(person => { 13 | if(person.binaryKnower){ 14 | console.log(person.whatIsInBinary(10)); 15 | } else{ 16 | console.log(person.whatIs(10)); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /code/ch09/1_template_final.js: -------------------------------------------------------------------------------- 1 | class Person{ 2 | log(number){ 3 | console.log(this.whatIs(number)); 4 | }; 5 | }; 6 | 7 | class BinaryKnower extends Person{ 8 | whatIs(number){ return Number('0b' + number) }; 9 | }; 10 | 11 | class BinaryOblivious extends Person{ 12 | whatIs(number){ return number }; 13 | }; 14 | const personOne = new BinaryKnower(); 15 | const personTwo = new BinaryOblivious(); 16 | [personOne, personTwo].forEach(person => person.log(10)); 17 | -------------------------------------------------------------------------------- /code/ch09/1_template_functional.js: -------------------------------------------------------------------------------- 1 | function log(person, number){ 2 | console.log(person.whatIs(number)); 3 | }; 4 | class BinaryKnower{ whatIs(number){ return Number('0b' + number) } }; 5 | class BinaryOblivious{ whatIs(number){ return number } }; 6 | const personOne = new BinaryKnower(); 7 | const personTwo = new BinaryOblivious(); 8 | [personOne, personTwo].forEach(person => { log(person, 10) }); 9 | 10 | 11 | // No objects 12 | // function log(fun, number){ 13 | // console.log(fun(number)); 14 | // }; 15 | // function whatIsInBinary(number){return Number('0b' + number)}; 16 | // function whatIs(number){return number}; 17 | // 18 | // [whatIsInBinary, whatIs].forEach(fun => { log(fun, 10) }); 19 | -------------------------------------------------------------------------------- /code/ch09/2_strategy.js: -------------------------------------------------------------------------------- 1 | class Person{ 2 | constructor(whatIs){ this.whatIs = whatIs }; 3 | log(number){ console.log(this.whatIs(number)) }; 4 | }; 5 | 6 | const binary = { 7 | aware(number){ return Number('0b' + number) }, 8 | oblivious(number){ return number } 9 | }; 10 | 11 | const personOne = new Person(binary.aware); 12 | const personTwo = new Person(binary.oblivious); 13 | 14 | [personOne, personTwo].forEach(person => { person.log(10) }); 15 | -------------------------------------------------------------------------------- /code/ch09/3_state.js: -------------------------------------------------------------------------------- 1 | class Person{ 2 | constructor(readKnowledge, andKnowledge, xorKnowledge){ 3 | this.read = readKnowledge; 4 | this.and = andKnowledge; 5 | this.xor = xorKnowledge; 6 | }; 7 | }; 8 | 9 | const binary = { 10 | readAware(number){ 11 | return Number('0b' + number); 12 | }, 13 | readOblivious(number){ 14 | return number; 15 | }, 16 | andAware(numberOne, numberTwo){ 17 | return numberOne & numberTwo; 18 | }, 19 | andOblivious(numberOne, numberTwo){ 20 | return "unknown"; 21 | }, 22 | xorAware(numberOne, numberTwo){ 23 | return numberOne ^ numberTwo; 24 | }, 25 | xorOblivious(numberOne, numberTwo){ 26 | return "unknown"; 27 | } 28 | }; 29 | 30 | const personOne = new Person(binary.readAware, 31 | binary.andAware, 32 | binary.xorAware); 33 | const personTwo = new Person(binary.readOblivious, 34 | binary.andOblivious, 35 | binary.xorOblivious); 36 | 37 | [personOne, personTwo].forEach(person => { 38 | console.log(person.read(10)); 39 | console.log(person.and(2, 3)); 40 | console.log(person.xor(2, 3)); 41 | }); 42 | -------------------------------------------------------------------------------- /code/ch09/3_state_final.js: -------------------------------------------------------------------------------- 1 | class Person{ 2 | constructor(binaryKnowledge){ 3 | this.binaryKnowledge = Object.create( 4 | Object.assign( 5 | {person: this}, 6 | binaryKnowledge)); 7 | }; 8 | change(binaryKnowledge){ 9 | this.binaryKnowledge = Object.create( 10 | Object.assign( 11 | {person: this}, 12 | binaryKnowledge)); 13 | }; 14 | }; 15 | 16 | const binaryAwareness = { 17 | read(number){ 18 | return Number('0b' + number); 19 | }, 20 | and(numberOne, numberTwo){ 21 | return numberOne & numberTwo; 22 | }, 23 | xor(numberOne, numberTwo){ 24 | return numberOne ^ numberTwo; 25 | }, 26 | forget(){ 27 | this.person.change(binaryObliviousness); 28 | } 29 | } 30 | const binaryObliviousness = { 31 | read(number){ 32 | return number; 33 | }, 34 | and(numberOne, numberTwo){ 35 | return "unknown"; 36 | }, 37 | xor(number){ 38 | return "unknown"; 39 | }, 40 | learn(){ 41 | this.person.change(binaryAwareness); 42 | } 43 | }; 44 | 45 | const personOne = new Person(binaryAwareness); 46 | const personTwo = new Person(binaryObliviousness); 47 | 48 | [personOne, personTwo].forEach(person => { 49 | console.log(person.binaryKnowledge.read(10)); 50 | console.log(person.binaryKnowledge.and(2, 3)); 51 | console.log(person.binaryKnowledge.xor(2, 3)); 52 | }); 53 | 54 | personOne.binaryKnowledge.forget(); 55 | personTwo.binaryKnowledge.learn(); 56 | 57 | [personOne, personTwo].forEach(person => { 58 | console.log(person.binaryKnowledge.read(10)); 59 | console.log(person.binaryKnowledge.and(2, 3)); 60 | console.log(person.binaryKnowledge.xor(2, 3)); 61 | }); 62 | personTwo.binaryKnowledge.forget(); 63 | personTwo.binaryKnowledge.read = () => 'will not assign both'; 64 | [personOne, personTwo].forEach(person => { 65 | console.log(person.binaryKnowledge.read(3)); 66 | }); 67 | -------------------------------------------------------------------------------- /code/ch09/4_null_object.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name){ 3 | this.name = name; 4 | } 5 | }; 6 | class AnonymousPerson extends Person { 7 | constructor(){ 8 | super(); 9 | this.name = null; 10 | } 11 | }; 12 | 13 | function capitalize(string) { 14 | if(string === null){ 15 | return null; 16 | }else{ 17 | return string[0].toUpperCase() + string.substring(1); 18 | } 19 | }; 20 | 21 | function tigerify(string) { 22 | if(string === null){ 23 | return null; 24 | }else{ 25 | return `${string}, the tiger`; 26 | } 27 | }; 28 | 29 | function display(string){ 30 | if(string === null){ 31 | return ''; 32 | }else{ 33 | return string; 34 | } 35 | }; 36 | 37 | personOne = new Person("tony"); 38 | personTwo = new AnonymousPerson("tony"); 39 | console.log(display(tigerify(capitalize(personOne.name)))); 40 | console.log(display(tigerify(capitalize(personTwo.name)))); 41 | -------------------------------------------------------------------------------- /code/ch09/4_null_object_final.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name){ 3 | this.name = new NameString(name); 4 | } 5 | }; 6 | 7 | class AnonymousPerson extends Person { 8 | constructor(){ 9 | super(); 10 | this.name = new NullString; 11 | } 12 | }; 13 | 14 | class NullString{ 15 | capitalize(){ 16 | return this; // same as new NullString in this case 17 | }; 18 | tigerify() { 19 | return this; // same as new NullString in this case 20 | }; 21 | display() { 22 | return ''; 23 | }; 24 | }; 25 | 26 | class NameString extends String{ 27 | capitalize() { 28 | return new NameString(this[0].toUpperCase() + this.substring(1)); 29 | }; 30 | tigerify() { 31 | return new NameString(`${this}, the tiger`); 32 | }; 33 | display(){ 34 | return this.toString(); 35 | }; 36 | } 37 | 38 | personOne = new Person("tony"); 39 | personTwo = new AnonymousPerson("tony"); 40 | console.log(personOne.name.capitalize().tigerify().display()); 41 | console.log(personTwo.name.capitalize().tigerify().display()); 42 | -------------------------------------------------------------------------------- /code/ch09/5_adapter.js: -------------------------------------------------------------------------------- 1 | class Target{ 2 | hello(){ 3 | console.log('hello'); 4 | }; 5 | goodbye(){ 6 | console.log('goodbye'); 7 | }; 8 | 9 | }; 10 | class Adaptee{ 11 | hi(){ 12 | console.log('hi'); 13 | }; 14 | bye(){ 15 | console.log('bye'); 16 | }; 17 | }; 18 | 19 | const formal = new Target; 20 | formal.hello(); 21 | formal.goodbye(); 22 | 23 | const casual = new Adaptee; 24 | casual.hi(); 25 | casual.bye(); 26 | 27 | class Adapter{ 28 | constructor(adaptee){ 29 | this.hello = adaptee.hi; 30 | this.goodbye = adaptee.bye; 31 | }; 32 | }; 33 | const adaptedCasual = new Adapter(new Adaptee); 34 | adaptedCasual.hello(); 35 | adaptedCasual.goodbye(); 36 | -------------------------------------------------------------------------------- /code/ch09/5_wrapper_dog.js: -------------------------------------------------------------------------------- 1 | class Dog{ 2 | constructor(){ 3 | this.cost = 50; 4 | } 5 | displayPrice(){ 6 | return `The dog costs $${this.cost}.`; 7 | } 8 | }; 9 | 10 | function Cute(dog){ 11 | const cuteDog = Object.create(dog); 12 | cuteDog.cost = dog.cost + 20; 13 | return cuteDog; 14 | }; 15 | 16 | function Trained(dog){ 17 | const trainedDog = Object.create(dog); 18 | trainedDog.cost = dog.cost + 60; 19 | return trainedDog; 20 | }; 21 | 22 | const test = require('tape'); 23 | 24 | test("trained/cute dog price", (assert) => { 25 | assert.equal(Trained(Cute(new Dog)).displayPrice(), 26 | 'The dog costs $130.'); 27 | assert.end(); 28 | }); 29 | 30 | test("cute dog price", (assert) => { 31 | assert.equal((Cute(new Dog)).displayPrice(), 'The dog costs $70.'); 32 | assert.end(); 33 | }); 34 | 35 | test("base dog price", (assert) => { 36 | assert.equal((new Dog).displayPrice(), 'The dog costs $50.'); 37 | assert.end(); 38 | }); 39 | -------------------------------------------------------------------------------- /code/ch09/5_wrapper_null_object.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name){ 3 | this.name = new NameString(name); 4 | } 5 | }; 6 | 7 | class AnonymousPerson extends Person { 8 | constructor(){ 9 | super(); 10 | this.name = null; 11 | } 12 | }; 13 | 14 | class NameString extends String{ 15 | capitalize() { 16 | return new NameString(this[0].toUpperCase() + this.substring(1)); 17 | }; 18 | tigerify() { 19 | return new NameString(`${this}, the tiger`); 20 | }; 21 | display(){ 22 | return this.toString(); 23 | }; 24 | }; 25 | 26 | class NullString{ 27 | capitalize(){ 28 | return this; 29 | }; 30 | tigerify() { 31 | return this; 32 | }; 33 | display() { 34 | return ''; 35 | }; 36 | }; 37 | 38 | function WithoutNull(person){ 39 | personWithoutNull = Object.create(person); 40 | if(personWithoutNull.name === null){ 41 | personWithoutNull.name = new NullString; 42 | }; 43 | return personWithoutNull; 44 | }; 45 | 46 | const test = require('tape'); 47 | 48 | test("Displaying a person", (assert) => { 49 | const personOne = new Person("tony"); 50 | assert.equal(personOne.name.capitalize().tigerify().display(), 51 | 'Tony, the tiger'); 52 | assert.end(); 53 | }); 54 | test("Displaying an anonymous person", (assert) => { 55 | const personTwo = new AnonymousPerson("tony"); 56 | assert.equal(WithoutNull(personTwo) 57 | .name.capitalize().tigerify().display(), 58 | ''); 59 | assert.end(); 60 | }); 61 | -------------------------------------------------------------------------------- /code/ch09/6_facade.js: -------------------------------------------------------------------------------- 1 | const page = { 2 | say(string){ 3 | console.log(string); 4 | }, 5 | yell(string){ 6 | alert(string); 7 | }, 8 | addNewLine(){ 9 | document.body.appendChild(document.createElement("br")); 10 | }, 11 | 12 | addButton(text){ 13 | const button = document.createElement("button"); 14 | button.appendChild(document.createTextNode(text)); 15 | document.body.appendChild(button); 16 | }, 17 | addText(text){ 18 | const span = document.createElement("span"); 19 | span.appendChild(document.createTextNode(text)); 20 | document.body.appendChild(span); 21 | }, 22 | changeBackground(color){ 23 | document.body.style.background = color; 24 | }, 25 | now(asNumber = false){ 26 | if(asNumber === false){ 27 | return new Date().toLocaleTimeString(); 28 | }else{ 29 | return new Date().getTime(); 30 | } 31 | }, 32 | timeOnPage(){ 33 | return ((this.now(true) - this._start) / 1000) + " seconds"; 34 | }, 35 | loadTime(){ 36 | return ((this._start - this._loaded) / 1000) + " seconds"; 37 | }, 38 | eventsSoFar(){ 39 | console.info(this._events); 40 | }, 41 | _events: [], 42 | _start: 'nothing yet', 43 | _loaded: 'nothing yet' 44 | }; 45 | 46 | window.onload = function(){ 47 | page._start = page.now(true); 48 | page._loaded = performance.timing.navigationStart; 49 | document.onclick = function(event) { 50 | page._events.push(event.target + " clicked at " + page.now()); 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /code/ch10/1_why_async.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const response = http.get('http://refactoringjs.com') 3 | 4 | 5 | let theResult = []; 6 | http.get('http://refactoringjs.com', (result) => { 7 | result.on('data', (chunk) => { 8 | theResult.push(chunk.toString()); 9 | }); 10 | }); 11 | setTimeout(function(){console.log(theResult)}, 500); 12 | -------------------------------------------------------------------------------- /code/ch10/2_fixing_pyramid_of_doom.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const getBody = { 3 | bodyArray: [], 4 | saveBody: function(chunk){ 5 | this.bodyArray.push(chunk); 6 | }, 7 | getResult: function(result){ 8 | result.on('data', this.saveBody.bind(this)); 9 | result.on('end', this.printBody.bind(this)); 10 | }, 11 | printBody: function(){ 12 | console.log(this.bodyArray.join('')) 13 | this.allDone(); 14 | }, 15 | allDone: function(){} 16 | }; 17 | 18 | const test = require('tape'); 19 | 20 | const testDouble = require('testdouble'); 21 | 22 | function setup(){ 23 | return Object.create(getBody); 24 | }; 25 | 26 | test('our async routine', function (assert) { 27 | const newBody = setup(); 28 | newBody.allDone = testDouble.function(); 29 | testDouble.when(newBody.allDone()).thenDo(function(){ 30 | assert.notEqual(newBody.bodyArray.length, 0) 31 | assert.end() 32 | }); 33 | http.get('http://refactoringjs.com', 34 | newBody.getResult.bind(newBody)); 35 | }); 36 | -------------------------------------------------------------------------------- /code/ch10/3_callbacks_and_testing.js: -------------------------------------------------------------------------------- 1 | // function addOne(addend){ 2 | // console.log(addend + 1); 3 | // }; 4 | // addOne(2); 5 | // 6 | // function two(callback){ 7 | // callback(2); 8 | // }; 9 | // two((addend) => console.log(addend + 1)); 10 | // 11 | // two(addOne); 12 | // 13 | // function three(callback){ 14 | // setTimeout(function(){ 15 | // callback(3); 16 | // }, 17 | // 500); 18 | // }; 19 | // three(addOne); 20 | 21 | // function three(){ 22 | // setTimeout(function(){ 23 | // return 3 24 | // }, 25 | // 500); 26 | // } 27 | // function addOne(addend){ 28 | // console.log(addend + 1); 29 | // }; 30 | // addOne(three()); 31 | 32 | function addOne(addend, callback){ 33 | callback(addend + 1); 34 | }; 35 | function three(callback){ 36 | setTimeout(function(){ 37 | callback(3, console.log); 38 | }, 39 | 500); 40 | }; 41 | function addOneSync(addend){ 42 | return addend + 1; 43 | }; 44 | 45 | const test = require('tape'); 46 | 47 | test('our addOne function', (assert) => { 48 | addOne(3, (result) => { 49 | assert.equal(result, 4); 50 | assert.end(); 51 | }); 52 | }); 53 | 54 | test('our three function', (assert) => { 55 | three((result, callback) => { 56 | assert.equal(result, 3); 57 | assert.equal(callback, console.log); 58 | }); 59 | assert.end(); 60 | }); 61 | 62 | 63 | test('our addOneSync function', (assert) => { 64 | assert.equal(addOneSync(3), 4); 65 | assert.end(); 66 | }); 67 | 68 | const testDouble = require('testdouble'); 69 | test('our end-to-end test', (assert) => { 70 | testDouble.replace(console, 'log') 71 | three((result, callback) => { 72 | addOne(result, callback) 73 | testDouble.verify(console.log(4)); 74 | testDouble.reset(); 75 | assert.end(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /code/ch10/4_promises.js: -------------------------------------------------------------------------------- 1 | function addOne(addend){ 2 | return Promise.resolve(addend + 1); 3 | }; 4 | 5 | function four(){ 6 | return new Promise((resolve, _reject) => { 7 | setTimeout(() => resolve(4), 500); 8 | }); 9 | }; 10 | 11 | const test = require('tape'); 12 | const testdouble = require('testdouble'); 13 | 14 | test('our addOne function', (assert) => { 15 | addOne(3).then((result) => { 16 | assert.equal(result, 4); 17 | assert.end(); 18 | }); 19 | }); 20 | 21 | test('our four function', (assert) => { 22 | four().then((result) => { 23 | assert.equal(result, 4); 24 | assert.end(); 25 | }); 26 | }); 27 | 28 | test('our end-to-end test', (assert) => { 29 | testdouble.replace(console, 'log') 30 | four() 31 | .then(addOne) 32 | .then(console.log) 33 | .then(() => { 34 | testdouble.verify(console.log(5)); 35 | assert.pass(); 36 | testdouble.reset(); 37 | assert.end(); 38 | }).catch((e) => { 39 | testdouble.reset(); 40 | console.log(e); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /code/ch11/10_functional_version_of_nbc.js: -------------------------------------------------------------------------------- 1 | //naive_bayes_functional.js 2 | R = require('ramda'); 3 | 4 | const smoothing = 1.01; 5 | 6 | function wordCountForLabel(testWord, relevantTexts){ 7 | const equalsTestword = R.equals(testWord); 8 | return R.filter(equalsTestword, _allWords(relevantTexts)).length; 9 | }; 10 | 11 | function likelihoodOfWord(word, relevantTexts, numberOfTexts){ 12 | return wordCountForLabel(word, 13 | relevantTexts) / numberOfTexts + smoothing; 14 | }; 15 | 16 | function likelihoodByLabel(label, newWords, trainedSet){ 17 | const relevantTexts = textsForLabel(trainedSet.texts, label) 18 | const initialValue = trainedSet.probabilities[label] + smoothing; 19 | const likelihood = R.product( 20 | newWords.map(newWord => 21 | likelihoodOfWord(newWord, 22 | relevantTexts, 23 | trainedSet.texts.length))) * initialValue; 24 | return {[label]: likelihood} 25 | } 26 | 27 | function textsForLabel(texts, label){ 28 | return R.filter(text => text.label === label)(texts); 29 | } 30 | 31 | function _allWords(theTexts){ 32 | return R.flatten(R.pluck('words', theTexts)); 33 | }; 34 | 35 | function addText(words, label, existingText = []){ 36 | return R.concat(existingText, [{words: words, label: label}]); 37 | }; 38 | 39 | function train(allTexts) { 40 | const overTextLength = R.divide(R.__, allTexts.length); 41 | return {texts: allTexts, 42 | probabilities: R.map(overTextLength, 43 | R.countBy(R.identity, 44 | R.pluck('label', allTexts)))}; 45 | }; 46 | 47 | function classify(newWords, trainedSet){ 48 | const labelNames = R.keys(trainedSet.probabilities); 49 | return R.reduce((acc, label) => 50 | R.merge(acc, likelihoodByLabel(label, newWords, trainedSet)) 51 | , {}, labelNames); 52 | }; 53 | 54 | module.exports = {_allWords: _allWords, 55 | addText: addText, 56 | train: train, 57 | classify: classify} 58 | -------------------------------------------------------------------------------- /code/ch11/10_functional_version_of_nbc_test.js: -------------------------------------------------------------------------------- 1 | //naive_bayes_functional_test.js 2 | const NB = require('./10_functional_version_of_nbc.js'); 3 | 4 | const wish = require('wish'); 5 | describe('the file', () => { 6 | const english = NB.addText(['a', 'b', 'c', 'd', 'e', 'f', 'g', 7 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q'], 8 | 'yes') 9 | const moreEnglish = NB.addText(['a', 'e', 'i', 'o', 'u'], 10 | 'yes', english) 11 | const allTexts = NB.addText(['あ', 'い', 'う', 'え', 'お', 12 | 'か', 'き', 'く', 'け', 'こ'], 13 | 'no', moreEnglish) 14 | 15 | var trainedSet = NB.train(allTexts); 16 | 17 | it('works', () => { 18 | wish(true); 19 | }) 20 | it('classifies', () =>{ 21 | const classified = NB.classify(['お', 'は', 'よ', 'う', 'ご', 'ざ', 'い', 'ま', 'す'], trainedSet); 22 | wish(classified['yes'] === 1.833745640534112); 23 | wish(classified['no'] === 3.456713680099012); 24 | }); 25 | it('number of words', ()=>{ 26 | wish(NB._allWords(trainedSet.texts).length === 32); 27 | }); 28 | 29 | it('label probabilities', ()=>{ 30 | wish(trainedSet.probabilities['yes'] === 0.6666666666666666); 31 | wish(trainedSet.probabilities['no'] === 0.3333333333333333); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /code/ch11/1_restrictions_and_benefits.js: -------------------------------------------------------------------------------- 1 | function factorial(number){ 2 | if(number < 2){ 3 | return 1; 4 | } else { 5 | return(number * factorial(number - 1)); 6 | } 7 | }; 8 | factorial(3); //returns 6 9 | 10 | function memoizedFactorial(number, lookupTable = {}){ 11 | if(number in lookupTable){ 12 | console.log("cached"); 13 | return lookupTable[number]; 14 | } 15 | else{ 16 | console.log("calculating"); 17 | var reduceValue; 18 | if(number < 2){ 19 | reduceValue = 1; 20 | } else { 21 | reduceValue = number * (memoizedFactorial(number - 1, lookupTable))['result']; 22 | }; 23 | lookupTable[number] = reduceValue; 24 | return {result: reduceValue, lookupTable: lookupTable}; 25 | } 26 | }; 27 | const lookup = memoizedFactorial(10)['lookupTable']; 28 | console.log(memoizedFactorial(10, lookup)); 29 | -------------------------------------------------------------------------------- /code/ch11/2_the_basics.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const testdouble = require('testdouble'); 3 | var x; 4 | 5 | const impure = { 6 | setGlobalFromAddition(addend1, addend2){ 7 | x = add(addend1, addend2); 8 | }, 9 | readAddition(addend1, addend2, done){ 10 | console.log(add(addend1, addend2)); 11 | done(); 12 | } 13 | }; 14 | 15 | function add(addend1, addend2){ 16 | return addend1 + addend2; 17 | }; 18 | 19 | test('addition', (assert) => { 20 | assert.equal(add(2, 3), 5); 21 | assert.end(); 22 | }); 23 | 24 | test('setting global', (assert) => { 25 | impure.setGlobalFromAddition(2, 3); 26 | assert.equal(x, 5); 27 | assert.end(); 28 | }); 29 | 30 | test('setting global again', (assert) => { 31 | impure.setGlobalFromAddition(2, 8); 32 | assert.equal(x, 10); 33 | assert.end(); 34 | }); 35 | 36 | test('calling console', (assert) => { 37 | testdouble.replace(console, 'log'); 38 | impure.readAddition(2, 3, () => { 39 | testdouble.verify(console.log(5)); 40 | assert.pass(); 41 | testdouble.reset(); 42 | assert.end(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /code/ch11/3_currying.js: -------------------------------------------------------------------------------- 1 | R = require('ramda'); 2 | 3 | function add(numberOne, numberTwo){ 4 | return numberOne + numberTwo; 5 | }; 6 | 7 | const curriedAdd = R.curry(add); 8 | 9 | console.log(curriedAdd(1)); 10 | console.log(curriedAdd(1)(2)); 11 | console.log(curriedAdd(1, 2)); 12 | 13 | const increment = curriedAdd(1); 14 | console.log(increment(3)); 15 | 16 | 17 | const square = (thing) => thing * thing; 18 | 19 | const mapSquares = R.map(square); 20 | console.log(mapSquares([2, 4, 5])); 21 | 22 | console.log(R.map(square, [2, 4, 5])); 23 | -------------------------------------------------------------------------------- /code/ch11/4_function_composition.js: -------------------------------------------------------------------------------- 1 | _ = require('lodash'); 2 | const square = (thing) => thing * thing; 3 | 4 | // const mapSquares = _.map(square); 5 | const mapSquares = (data) => _.map(data, square); 6 | // console.log(mapSquares([2, 4, 5])); 7 | 8 | 9 | // [3, 4, 2].forEach((element) => console.log(element)); 10 | // [3, 4, 2].forEach(console.log); 11 | 12 | const logFirst = (first) => console.log(first); 13 | [3, 4, 2].forEach(logFirst); 14 | 15 | R = require('ramda'); 16 | console.log(R.map(square, R.map(square, [2, 4, 5]))); 17 | 18 | const fourthPower = R.compose(square, square); 19 | console.log(R.map(fourthPower, [2, 4, 5])); 20 | 21 | const mapFourthPower = R.map(fourthPower); 22 | console.log(mapFourthPower([2, 4, 5])); 23 | 24 | const printFourthPower = R.compose(console.log, square, square); 25 | R.map(printFourthPower, [2, 4, 5]); 26 | 27 | R.map(printFourthPower) ([2, 4, 5]); 28 | 29 | 30 | var factorial = R.memoize(n => R.product(R.range(1, n + 1))); 31 | 32 | var printFact = R.compose(console.log, factorial); 33 | printFact(3); 34 | 35 | 36 | var printFactPipe = R.pipe(factorial, console.log); 37 | printFactPipe(3); 38 | -------------------------------------------------------------------------------- /code/ch11/5_types.js: -------------------------------------------------------------------------------- 1 | console.log(Math.random("beaver")); 2 | 3 | 4 | // types 5 | // add :: Number -> Number -> Number 6 | // add :: (Number, Number) -> Number 7 | // addOne :: Number -> Number 8 | // map :: (a -> b) -> [a] -> [b] 9 | // Maybe#concat :: Semigroup a => Maybe a ~> Maybe a -> Maybe a 10 | -------------------------------------------------------------------------------- /code/ch11/6_introducing_sanctuary.js: -------------------------------------------------------------------------------- 1 | const {create, env} = require('sanctuary'); 2 | const S = create({checkTypes: true, env: env}); 3 | R = require('ramda'); 4 | // R.add("hello", 3); 5 | // NaN 6 | // S.add("hello", 3); 7 | 8 | // TypeError: Invalid value 9 | // add :: FiniteNumber -> FiniteNumber -> FiniteNumber 10 | // ^^^^^^^^^^^^ 11 | // 1 12 | // 1) "hello" :: String 13 | // The value at position 1 is not a member of ‘FiniteNumber’. 14 | 15 | const getAThree = S.find(R.equals(3)); 16 | getAThree([3, 4]); 17 | getAThree([8, 4]); 18 | -------------------------------------------------------------------------------- /code/ch11/7_null_object_again.js: -------------------------------------------------------------------------------- 1 | const {create, env} = require('sanctuary'); 2 | const S = create({checkTypes: false, env: env}); 3 | 4 | class Person { 5 | constructor(name){ 6 | this.name = S.Just(name); 7 | } 8 | }; 9 | class AnonymousPerson extends Person { 10 | constructor(){ 11 | super(); 12 | this.name = S.Nothing(); 13 | } 14 | }; 15 | const capitalize = (string) => string[0].toUpperCase() 16 | + string.substring(1); 17 | const tigerify = (string) => `${string}, the tiger`; 18 | const display = (string) => string.toString(); 19 | 20 | const test = require('tape'); 21 | test("Displaying a person", (assert) => { 22 | const personOne = new Person("tony"); 23 | assert.equal(S.fromMaybe('', 24 | personOne.name.map(capitalize).map(tigerify)), 25 | 'Tony, the tiger'); 26 | assert.end(); 27 | }); 28 | test("Displaying an anonymous person", (assert) => { 29 | const personTwo = new AnonymousPerson(null); 30 | assert.equal(S.fromMaybe('', 31 | personTwo.name.map(capitalize).map(tigerify)), 32 | ''); 33 | assert.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /code/ch11/8_functional_refactoring_with_maybe.js: -------------------------------------------------------------------------------- 1 | const {create, env} = require('sanctuary'); 2 | const S = create({checkTypes: false, env: env}); 3 | 4 | class Person { 5 | constructor(name){ 6 | this.name = S.toMaybe(name); 7 | }; 8 | }; 9 | const capitalize = (string) => string[0].toUpperCase() 10 | + string.substring(1); 11 | const tigerify = (string) => `${string}, the tiger`; 12 | const display = (string) => string.toString(); 13 | const capitalTiger = S.compose(capitalize, tigerify); 14 | 15 | const test = require('tape'); 16 | test("Displaying a person", (assert) => { 17 | const personOne = new Person("tony"); 18 | assert.equal(personOne.name.chain(capitalTiger), 'Tony, the tiger'); 19 | assert.end(); 20 | }); 21 | test("Displaying an anonymous person", (assert) => { 22 | const personTwo = new Person(null); 23 | assert.equal(S.maybe('', capitalTiger, personTwo.name), ''); 24 | assert.end(); 25 | }); 26 | -------------------------------------------------------------------------------- /code/ch11/9_functional_refactoring_with_either.js: -------------------------------------------------------------------------------- 1 | const {create, env} = require('sanctuary'); 2 | const S = create({checkTypes: false, env: env}); 3 | const R = require('ramda'); 4 | 5 | class Person { 6 | constructor(name){ 7 | this.name = S.maybeToEither('', S.toMaybe(name)); 8 | } 9 | }; 10 | 11 | const capitalize = (string) => string[0].toUpperCase() 12 | + string.substring(1); 13 | const tigerify = (string) => `${string}, the tiger`; 14 | 15 | const capitalTiger = S.compose(capitalize, tigerify); 16 | 17 | const test = require('tape'); 18 | test("Displaying a person", (assert) => { 19 | const personOne = new Person("tony"); 20 | assert.equal(personOne.name.map(capitalTiger).value, 21 | 'Tony, the tiger'); 22 | assert.end(); 23 | }); 24 | test("Displaying an anonymous person", (assert) => { 25 | const personTwo = new Person(null); 26 | assert.equal(personTwo.name.map(capitalTiger).value, ''); 27 | assert.end(); 28 | }); 29 | -------------------------------------------------------------------------------- /console_with_context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/console_with_context.png -------------------------------------------------------------------------------- /copyright.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 |

{{ title }}

14 | 15 | 16 |

by ??? ?. ???

17 | 18 | 19 | 20 | 21 |

Printed in the United States of America.

22 | 23 | 24 |

Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

25 | 26 |

O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most 27 | titles (http://safaribooksonline.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com.

28 | 29 | 30 | 41 | 42 | 43 | 46 | 47 | 48 |
49 |

Revision History for the First Edition

50 |
    51 |
  • yyyy-mm-dd: First Release
  • 52 |
53 |
54 | 55 | 56 |

See http://oreilly.com/catalog/errata.csp?isbn=XXXXXXXXXXXXX for release details.

57 | 58 | 59 | 64 | 65 | 69 | 70 |
71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /cover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/cover.png -------------------------------------------------------------------------------- /dedication.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |
11 |

Dedication Title

12 | 13 |

Put your dedication here

14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /diagramming_language.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagramming_language.afdesign -------------------------------------------------------------------------------- /diagrams/diary_creating_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/diary_creating_function.png -------------------------------------------------------------------------------- /diagrams/function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function.png -------------------------------------------------------------------------------- /diagrams/function_anonymous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_anonymous.png -------------------------------------------------------------------------------- /diagrams/function_attached_to_anObject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_attached_to_anObject.png -------------------------------------------------------------------------------- /diagrams/function_diary_privateUnlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_diary_privateUnlock.png -------------------------------------------------------------------------------- /diagrams/function_diary_privateUnlock_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_diary_privateUnlock_2.png -------------------------------------------------------------------------------- /diagrams/function_diary_privateUnlock_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_diary_privateUnlock_3.png -------------------------------------------------------------------------------- /diagrams/function_diary_read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_diary_read.png -------------------------------------------------------------------------------- /diagrams/function_diary_tryLock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_diary_tryLock.png -------------------------------------------------------------------------------- /diagrams/function_returning_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_returning_number.png -------------------------------------------------------------------------------- /diagrams/function_setIt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_setIt.png -------------------------------------------------------------------------------- /diagrams/function_with_bulk_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_bulk_1.png -------------------------------------------------------------------------------- /diagrams/function_with_bulk_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_bulk_2.png -------------------------------------------------------------------------------- /diagrams/function_with_complex_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_complex_output.png -------------------------------------------------------------------------------- /diagrams/function_with_global_implicit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_global_implicit.png -------------------------------------------------------------------------------- /diagrams/function_with_no_return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_no_return.png -------------------------------------------------------------------------------- /diagrams/function_with_on_explicit_input_one_implicit_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_on_explicit_input_one_implicit_input.png -------------------------------------------------------------------------------- /diagrams/function_with_side_effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_side_effect.png -------------------------------------------------------------------------------- /diagrams/function_with_two_explicit_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_two_explicit_inputs.png -------------------------------------------------------------------------------- /diagrams/function_with_two_non_local_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_two_non_local_inputs.png -------------------------------------------------------------------------------- /diagrams/function_with_undefined_implicit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diagrams/function_with_undefined_implicit.png -------------------------------------------------------------------------------- /diary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary.png -------------------------------------------------------------------------------- /diary10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary10.png -------------------------------------------------------------------------------- /diary3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary3.png -------------------------------------------------------------------------------- /diary4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary4.png -------------------------------------------------------------------------------- /diary5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary5.png -------------------------------------------------------------------------------- /diary6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary6.png -------------------------------------------------------------------------------- /diary7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary7.png -------------------------------------------------------------------------------- /diary8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary8.png -------------------------------------------------------------------------------- /diary9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary9.png -------------------------------------------------------------------------------- /diary_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/diary_2.png -------------------------------------------------------------------------------- /dynamic_this.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/dynamic_this.png -------------------------------------------------------------------------------- /fantasy_land_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/fantasy_land_diagram.png -------------------------------------------------------------------------------- /function-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/function-circle.png -------------------------------------------------------------------------------- /function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/function.png -------------------------------------------------------------------------------- /functions_with_bulk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/functions_with_bulk.png -------------------------------------------------------------------------------- /haskell_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/haskell_diagram.png -------------------------------------------------------------------------------- /images/__red_green_refactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/__red_green_refactor.png -------------------------------------------------------------------------------- /images/_red_green_refactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/_red_green_refactor.png -------------------------------------------------------------------------------- /images/_testing_in_action_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/_testing_in_action_.png -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/cover.png -------------------------------------------------------------------------------- /images/red_green_refactor.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/red_green_refactor.afdesign -------------------------------------------------------------------------------- /images/red_green_refactor_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/red_green_refactor_.png -------------------------------------------------------------------------------- /images/tarsier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/tarsier.png -------------------------------------------------------------------------------- /images/testing_in_action.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/testing_in_action.afdesign -------------------------------------------------------------------------------- /images/testing_in_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/testing_in_action.png -------------------------------------------------------------------------------- /images/testing_in_action_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/images/testing_in_action_.png -------------------------------------------------------------------------------- /ix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Index

13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /mocha_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/mocha_results.png -------------------------------------------------------------------------------- /mocha_test_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/mocha_test_output.png -------------------------------------------------------------------------------- /nbc.js: -------------------------------------------------------------------------------- 1 | fs = require('fs'); 2 | //songs 3 | imagine = ['c', 'cmaj7', 'f', 'am', 'dm', 'g', 'e7']; 4 | somewhere_over_the_rainbow = ['c', 'em', 'f', 'g', 'am']; 5 | tooManyCooks = ['c', 'g', 'f']; 6 | iWillFollowYouIntoTheDark = ['f', 'dm', 'bb', 'c', 'a', 'bbm']; 7 | babyOneMoreTime = ['cm', 'g', 'bb', 'eb', 'fm', 'ab']; 8 | creep = ['g', 'gsus4', 'b', 'bsus4', 'c', 'cmsus4', 'cm6']; 9 | army = ['ab', 'ebm7', 'dbadd9', 'fm7', 'bbm', 'abmaj7', 'ebm']; 10 | paperBag = ['bm7', 'e', 'c', 'g', 'b7', 'f', 'em', 'a', 'cmaj7', 'em7', 'a7', 'f7', 'b']; 11 | toxic = ['cm', 'eb', 'g', 'cdim', 'eb7', 'd7', 'db7', 'ab', 'gmaj7', 'g7']; 12 | bulletproof = ['d#m', 'g#', 'b', 'f#', 'g#m', 'c#']; 13 | song_11 = []; 14 | var songs = []; 15 | var labels = []; 16 | var allChords = []; 17 | var labelCounts = []; 18 | var labelProbabilities = []; 19 | var chordCountsInLabels = {}; 20 | var probabilityOfChordsInLabels = {}; 21 | function train(chords, label){ 22 | songs.push([label, chords]); 23 | labels.push(label); 24 | for (var i = 0; i < chords.length; i++){ 25 | if(!allChords.includes(chords[i])){ 26 | allChords.push(chords[i]); 27 | } } 28 | if(!!(Object.keys(labelCounts).includes(label))){ 29 | labelCounts[label] = labelCounts[label] + 1; 30 | } else { 31 | labelCounts[label] = 1; 32 | } }; 33 | function getNumberOfSongs(){ 34 | return songs.length; 35 | }; 36 | function setLabelProbabilities(){ 37 | Object.keys(labelCounts).forEach(function(label){ 38 | var numberOfSongs = getNumberOfSongs(); 39 | labelProbabilities[label] = labelCounts[label] / numberOfSongs; 40 | }) 41 | }; 42 | function setChordCountsInLabels(){ 43 | songs.forEach(function(i){ 44 | if(chordCountsInLabels[i[0]] === undefined){ 45 | chordCountsInLabels[i[0]] = {} 46 | } 47 | i[1].forEach(function(j){ 48 | if(chordCountsInLabels[i[0]][j] > 0){ 49 | chordCountsInLabels[i[0]][j] = chordCountsInLabels[i[0]][j] + 1; 50 | } else { 51 | chordCountsInLabels[i[0]][j] = 1; 52 | } }); 53 | }); } 54 | function setProbabilityOfChordsInLabels(){ 55 | probabilityOfChordsInLabels = chordCountsInLabels; 56 | Object.keys(probabilityOfChordsInLabels).forEach(function(i){ 57 | Object.keys(probabilityOfChordsInLabels[i]).forEach(function(j){ 58 | probabilityOfChordsInLabels[i][j] = probabilityOfChordsInLabels[i][j] * 1.0 / songs.length; 59 | }) }) 60 | } 61 | train(imagine, 'easy'); 62 | train(somewhere_over_the_rainbow, 'easy'); 63 | train(tooManyCooks, 'easy'); 64 | train(iWillFollowYouIntoTheDark, 'medium'); 65 | train(babyOneMoreTime, 'medium'); 66 | train(creep, 'medium'); 67 | train(paperBag, 'hard'); 68 | train(toxic, 'hard'); 69 | train(bulletproof, 'hard'); 70 | setLabelProbabilities(); 71 | setChordCountsInLabels(); 72 | setProbabilityOfChordsInLabels(); 73 | function classify(chords){ 74 | var ttal = labelProbabilities; 75 | console.log(ttal); 76 | var classified = {} 77 | Object.keys(ttal).forEach(function(obj){ 78 | var first = labelProbabilities[obj] + 1.01; 79 | chords.forEach(function(chord){ 80 | var probabilityOfChordInLabel = probabilityOfChordsInLabels[obj][chord] 81 | if(probabilityOfChordInLabel === undefined){ 82 | first + 1.01 83 | } else { 84 | first = first * (probabilityOfChordInLabel + 1.01) 85 | } 86 | }) 87 | classified[obj] = first 88 | }); 89 | console.log(classified); 90 | }; 91 | classify(['d', 'g', 'e', 'dm']); 92 | classify(['f#m7', 'a', 'dadd9', 'dmaj7', 'bm', 'bm7', 'd', 'f#m']); 93 | -------------------------------------------------------------------------------- /not_global_context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/not_global_context.png -------------------------------------------------------------------------------- /null.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/null.png -------------------------------------------------------------------------------- /private_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/private_function.png -------------------------------------------------------------------------------- /red_green_refactor_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/red_green_refactor_.png -------------------------------------------------------------------------------- /side_effect_to_other_this.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/side_effect_to_other_this.png -------------------------------------------------------------------------------- /testing_in_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/testing_in_action.png -------------------------------------------------------------------------------- /testing_in_action_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/testing_in_action_.png -------------------------------------------------------------------------------- /theme/epub/epub.css: -------------------------------------------------------------------------------- 1 | /* Add your custom CSS styles for the EPUB here */ -------------------------------------------------------------------------------- /theme/html/html.css: -------------------------------------------------------------------------------- 1 | /* Add CSS styles for the HTML format here */ 2 | 3 | /* Add your custom CSS styles for the HTML here */ -------------------------------------------------------------------------------- /theme/mobi/mobi.css: -------------------------------------------------------------------------------- 1 | /* Add your custom CSS styles for the MOBI here */ -------------------------------------------------------------------------------- /theme/pdf/pdf.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /*--------Put Your Custom CSS Rules Below--------*/ 4 | /*--- This oneoff overrides the code in https://github.com/oreillymedia/animal_theme/blob/master/pdf/pdf.css---*/ 5 | 6 | /*--- Font fallback for Japanese characters not represented in standard ---*/ 7 | /*--- monospace font. ---*/ 8 | pre, code { font-family: UbuntuMono, monospace, ArialUnicodeMS } 9 | 10 | /*----Uncomment to turn on automatic code wrapping 11 | 12 | pre { 13 | white-space: pre-wrap; 14 | word-wrap: break-word; 15 | } 16 | ----*/ 17 | 18 | /*----Uncomment to change the TOC start page (set 19 | the number to one page _after_ the one you want; 20 | so 6 to start on v, 8 to start on vii, etc.) 21 | 22 | @page toc:first { 23 | counter-reset: page 6; 24 | } 25 | ----*/ 26 | 27 | /*----Uncomment to fix a bad break in the title 28 | (increase padding value to push down, decrease 29 | value to pull up) 30 | 31 | section[data-type="titlepage"] h1 { 32 | padding-left: 1.5in; 33 | } 34 | ----*/ 35 | 36 | /*----Uncomment to fix a bad break in the subtitle 37 | (increase padding value to push down, decrease 38 | value to pull up) 39 | 40 | section[data-type="titlepage"] h2 { 41 | padding-left: 1in; 42 | } 43 | ----*/ 44 | 45 | /*----Uncomment to fix a bad break in the author names 46 | (increase padding value to push down, decrease 47 | value to pull up) 48 | 49 | section[data-type="titlepage"] p.author { 50 | padding-left: 3in; 51 | } 52 | ----*/ 53 | -------------------------------------------------------------------------------- /timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/timeline.png -------------------------------------------------------------------------------- /titlepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Refactoring JavaScript 6 | 7 | 8 | 9 | 10 |
11 |

Book Title Here

12 |

??? Edition

13 |

Subtitle Goes Here

14 |

Some Author

15 |
16 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /todo: -------------------------------------------------------------------------------- 1 | ask for tech reviewer, and email nan the name 2 | nan will give all details 3 | 4 | sometime next week, nan will let me know 5 | 6 | 350 is ok. 400 is kind of ok. 7 | 8 | Finish everything in the end of october. 9 | Ally's edits 10 | don't worry about index tags 11 | 12 | how to handle fowler refactorings 13 | 14 | appendix/glossary - table of fowler refactorings 15 | 16 | nan - looking into fowler catalog 17 | 18 | sales? 19 | 20 | images 21 | 22 | tell nan when manual updates for early release are needed 23 | 24 | -------------------------------------------------------------------------------- /using_strict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/using_strict.png -------------------------------------------------------------------------------- /with_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/with_inputs.png -------------------------------------------------------------------------------- /with_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/with_outputs.png -------------------------------------------------------------------------------- /with_side_effects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvanBurchard/refactoringjavascript/7cf4a4809c5034a88587a7d47e76b992fb92e8fa/with_side_effects.png --------------------------------------------------------------------------------