├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── apple-stocks ├── stocktools.js └── test.js ├── array-merge ├── arrayMerge.js └── test.js ├── bracket-validator ├── bracketvalidator.js └── test.js ├── cake-thief ├── dynamicUKP.js └── test.js ├── dup-files ├── finddupfiles.js └── test.js ├── highest-product ├── arraytools.js └── test.js ├── integer-products ├── arraytools.js └── test.js ├── kth-to-the-last-node ├── linkedList.js └── test.js ├── linked-list ├── linkedList.js └── test.js ├── make-change ├── money.js └── test.js ├── package.json ├── rand5 ├── random.js └── test.js ├── range-merge ├── rangemerge.js └── test.js ├── riffle-shuffle ├── riffleshuffle.js └── test.js ├── second-largest ├── bst.js └── test.js ├── shuffle ├── shuffle.js └── test.js ├── string-permutations └── test.js ├── test.json └── word-reverse ├── test.js └── wordreverse.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # IDE 31 | .idea/ 32 | 33 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "devel": true, 4 | "strict": true, 5 | "esnext": true 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | interview-cake-answers-javascript 2 | ================================= 3 | 4 | My answers in JavaScript to InterviewCake (https://www.interviewcake.com) Weekly Problems 5 | 6 | Each solution can be found in its own directory and generally includes a package containing the guts of the solution 7 | plus a test script that runs through the test cases presented by the Interview Cake assignment. 8 | 9 | Solutions: 10 | 11 | * 01 'Apple Stocks' - Found low and high inflection points in the data then returned largest gap 12 | * 02 'Product of every integer in an array except integer at that index' 13 | * 03 'Product of the three largest integers in an array' - Implemented the 'bonus' generic solution of product of 'x' largest integers 14 | * 04 'Merging Meeting Times' - Implemented by sorting tuples then walking down them O(nlog(n)) 15 | * 05 'Making Change' - Implemented using O(n * m) time, O(n) space, bottom-up non-recursive approach 16 | * 06 'Rectangular Love' - Finds the intersection of two rectangles 17 | * 10 'Second Largest' - Implemented a BST library and added to it the ability to return the next to the 2nd largest. Implemented in a generic way to return the nth to the last element in the tree. 18 | * 16 'Cake Thief' - Solved using a dynamic programming solution to the unbounded knapsack problem 19 | * 22 'Delete Node' - Solved by using a single linked list 20 | * 25 'kth To The Last Node in a Singly-Linked List' - Solved by using a second offset sliding pointer 21 | * 27 'Reverse Words' - Solved using a recursive approach. Converted to an array to satisfy mutability requirement. 22 | * 29 'Bracket Validator' - Solved the main problem, but haven't yet figured out the bonus. 23 | * 35 'In-Place Shuffle' - Solved by using the Fisher-Yates in-place shuffle algorithm 24 | * 37 'Rand5() using Rand7()' - Solved using Rand7() but in a way that avoids infinite loop possibility 25 | * 36 'Single Riffle Check' - Solved by writing a riffle shuffle function and corresponding checks 26 | * 41 'Find Duplicate Files' - Solved by building a multi-map hash map based upon CRC-32 signatures for each file 27 | * 42 'Merge Sorted Arrays' - Solved using in O(n+m) time and O(2(n+m)) space using indexes for each array w/BONUS solved 28 | * 'Recursive String Permutations' - Solved with a single function that returns as an array of permutations, uses recursion, and containes no loops 29 | -------------------------------------------------------------------------------- /apple-stocks/stocktools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-22 3 | */ 4 | 5 | /** 6 | * Stock Tools 7 | */ 8 | 9 | "use strict"; 10 | 11 | module.exports.getBestSpread = function getBestSpread(data) { 12 | 13 | var minPriceTuple = data[0], 14 | maxPriceTuple = data[0], 15 | maxProfit = 0, 16 | currentPrice, 17 | time; 18 | 19 | for (time = 0; time < data.length; time += 1) { 20 | 21 | currentPrice = data[time][1]; 22 | 23 | // minPrice = Math.min(minPrice, currentPrice); 24 | if (currentPrice < minPriceTuple[1]) { 25 | minPriceTuple = data[time]; 26 | } 27 | 28 | // maxProfit = Math.max(maxProfit, currentPrice - minPrice); 29 | if ((currentPrice - minPriceTuple[1]) > maxProfit) { 30 | maxPriceTuple = data[time]; 31 | maxProfit = currentPrice - minPriceTuple[1]; 32 | } 33 | 34 | } 35 | 36 | return { 37 | minPrice : minPriceTuple, 38 | maxPrice : maxPriceTuple, 39 | maxProfit: maxProfit 40 | }; 41 | 42 | }; -------------------------------------------------------------------------------- /apple-stocks/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-22 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Writing interview questions hasn't made me rich. Maybe trading Apple stocks will. 9 | * 10 | * I have an array stockPricesYesterday where: 11 | * 12 | * The indices are the time, as a number of minutes past trade opening time, which was 9:30am local time. 13 | * 14 | * The values are the price of Apple stock at that time, in dollars. 15 | * 16 | * For example, the stock cost $500 at 10:30am, so stockPricesYesterday[60] = 500. 17 | * 18 | * Write an efficient algorithm for computing the best profit I could have made from 1 purchase and 1 sale of 1 19 | * Apple stock yesterday. For this problem, we won't allow "shorting"—you must buy before you sell. 20 | */ 21 | 22 | "use strict"; 23 | 24 | var stocktools = require('./stocktools.js'); 25 | 26 | var prices = [{ 27 | date: '2015-01-21', 28 | data: [[0, 10748], [ 5, 10751], [ 10, 10750], [ 15, 10694], [ 20, 10695], [ 25, 10738], [ 30, 10726], 29 | [ 35, 10751], [ 40, 10749], [ 45, 10694], [ 50, 10728], [ 55, 10722], [ 60, 10714], [ 65, 10696], 30 | [ 70, 10692], [ 75, 10708], [ 80, 10693], [ 85, 10669], [ 90, 10670], [ 95, 10704], [100, 10703], 31 | [105, 10711], [110, 10725], [115, 10716], [120, 10711], [125, 10708], [130, 10691], [135, 10682], 32 | [140, 10697], [145, 10706], [150, 10701], [155, 10726], [160, 10722], [165, 10727], [170, 10725], 33 | [175, 10725], [180, 10719], [185, 10725], [190, 10736], [195, 10738], [200, 10735], [205, 10753], 34 | [210, 10754], [215, 10727], [220, 10728], [225, 10742], [230, 10754], [235, 10767], [240, 10764], 35 | [245, 10764], [250, 10759], [255, 10763], [260, 10765], [265, 10768], [270, 10785], [275, 10787], 36 | [280, 10785], [285, 10788], [290, 10797], [295, 10784], [300, 10805], [305, 10814], [310, 10836], 37 | [315, 10835], [320, 10832], [325, 10849], [330, 10858], [335, 10862], [340, 10861], [345, 10866], 38 | [350, 10854], [355, 10853], [360, 10844], [365, 10847], [370, 10864], [375, 10861], [380, 10860], 39 | [385, 10871], [390, 10845]] 40 | }, { 41 | date: '2015-01-21', 42 | data: [[0, 10879], [30, 10902], [ 60, 10999], [ 90, 11084], [120, 11074], [150, 11056], [180, 11042], 43 | [210, 10996], [240, 10982], [270, 10941], [300, 10982], [330, 11010], [360, 10980], [390, 10955]] 44 | }, { 45 | date: '2015-01-22', 46 | data: [[0, 11032], [30, 11016], [ 60, 11045], [ 90, 11124], [120, 11147], [150, 11156], [180, 11161], 47 | [210, 11130], [240, 11118], [270, 11148], [300, 11161], [330, 11190], [360, 11213], [390, 11239]] 48 | }, { 49 | date: '2015-01-23', 50 | data: [[0, 11213], [ 5, 11120], [ 10, 11183], [ 15, 11166], [ 20, 11200], [ 25, 11230], [ 30, 11265], 51 | [ 35, 11236], [ 40, 11224], [ 45, 11209], [ 50, 11228], [ 55, 11227], [ 60, 11226], [ 65, 11220], 52 | [ 70, 11208], [ 75, 11187], [ 80, 11194], [ 85, 11212], [ 90, 11210], [ 95, 11220], [100, 11207], 53 | [105, 11210], [110, 11224], [115, 11228], [120, 11242], [125, 11248], [130, 11242], [135, 11266], 54 | [140, 11272], [145, 11273], [150, 11280], [155, 11288], [160, 11270], [165, 11280], [170, 11268], 55 | [175, 11264], [180, 11257], [185, 11266], [190, 11282], [195, 11280], [200, 11283], [205, 11294], 56 | [210, 11296], [215, 11289], [220, 11293], [225, 11314], [230, 11305], [235, 11327], [240, 11327], 57 | [245, 11336], [250, 11333], [255, 11355], [260, 11372], [265, 11374], [270, 11362], [275, 11371], 58 | [280, 11347], [285, 11324], [290, 11320], [295, 11335], [300, 11337], [305, 11344], [310, 11340], 59 | [315, 11338], [320, 11304], [325, 11316], [330, 11311], [335, 11300], [340, 11289], [345, 11299], 60 | [350, 11297], [355, 11300], [360, 11300], [365, 11298], [370, 11295], [375, 11296], [380, 11298], 61 | [385, 11293], [390, 11299]] 62 | }, { 63 | date: 'always up', 64 | data: [[0, 1], [1, 2], [2, 3], [3, 4]] 65 | }, { 66 | date: 'always down', 67 | data: [[0, 4], [1, 3], [2, 2], [3, 1]] 68 | }, { 69 | date: 'big swing following small swing', 70 | data: [[0, 100], [1, 200], [2, 300], [4, 200], [5, 100], [6, 50], [7, 200], [8, 500]] 71 | }, { 72 | date: 'up high but ending lower then prior low', 73 | data: [[0, 100], [1, 200], [2, 300], [4, 200], [5, 100], [6, 50]] 74 | }]; 75 | 76 | 77 | // Run the scenarios 78 | 79 | var i; 80 | for (i = 0; i < prices.length; i += 1) { 81 | console.log(prices[i].date, stocktools.getBestSpread(prices[i].data)); 82 | } -------------------------------------------------------------------------------- /array-merge/arrayMerge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-17 3 | */ 4 | 5 | /* 6 | * Simple Sorted Array Merge Utility Function 7 | **/ 8 | 9 | "use strict"; 10 | 11 | module.exports.merge = function merge(array1, array2) { 12 | 13 | var result = [], 14 | i = 0, 15 | j = 0; 16 | 17 | while (i < array1.length || j < array2.length) { 18 | 19 | // The logic below handles four cases but condenses them into the two ultimate outcomes: 20 | // - pushing array1's next element if either 21 | // - if array2 is empty, or 22 | // - array1's next element is less than array2's next element 23 | // - otherwise, pushing array2's next element since 24 | // - either array1 is now empty, or 25 | // - array1's next element is equal two or great than array2's next element 26 | 27 | if ((i < array1.length && array1[i] < array2[j]) || j === array2.length) { 28 | result.push(array1[i]); 29 | i += 1; 30 | } else { 31 | result.push(array2[j]); 32 | j += 1; 33 | } 34 | } 35 | 36 | return result; 37 | }; 38 | 39 | /* 40 | * Merge an Array of Sorted Arrays Utility Function 41 | */ 42 | 43 | module.exports.mergeMulti = function mergeMulti(arrayOfArrays) { 44 | 45 | var result = [], // fully merged results 46 | indices = [], // an array of indices for each array passed 47 | i, // loop index 48 | arrayContainingNextElement, // pointer to the embedded array containing the smallest element 49 | nextElement, // the smallest element found on the current pass through the embedded arrays 50 | numOfArrays, // the number of embedded arrays 51 | moreElementsToMerge; // a flag indicating that there are still more elements to be merged 52 | 53 | // Start with the first element of each of the embedded arrays 54 | 55 | for (i = 0, numOfArrays = arrayOfArrays.length; i < numOfArrays; i += 1) { 56 | indices[i] = 0; 57 | } 58 | 59 | // Keep merging elements until all elements in all embedded arrays have been processed 60 | 61 | do { 62 | 63 | arrayContainingNextElement = null; 64 | nextElement = null; 65 | moreElementsToMerge = false; 66 | 67 | // Spin through all the embedded arrays looking for the smallest element. Keep track of which array contains 68 | // this smallest element. Then, once found, push the smallest element into the results array and move the 69 | // index pointer for the array that contained the element to the next element in that respective array. Keep 70 | // going so long as at least one array contains more elements that need to be processed. 71 | 72 | for (i = 0, numOfArrays = arrayOfArrays.length; i < numOfArrays; i += 1) { 73 | if (indices[i] < arrayOfArrays[i].length && !moreElementsToMerge) { 74 | moreElementsToMerge = true; 75 | } 76 | if (indices[i] < arrayOfArrays[i].length && ((arrayContainingNextElement === null) || arrayOfArrays[i][indices[i]] < nextElement)) { 77 | arrayContainingNextElement = i; 78 | nextElement = arrayOfArrays[i][indices[i]]; 79 | } 80 | } 81 | if (arrayContainingNextElement !== null) { 82 | result.push(nextElement); 83 | indices[arrayContainingNextElement] += 1; 84 | } 85 | 86 | } while (moreElementsToMerge); 87 | 88 | return result; 89 | }; 90 | 91 | /* 92 | * Helper function to compare the equality of two arrays used by 'flatten' method 93 | * 94 | * NOTE: !! This is awkward code from the fellow who provide the flatten gist. I've replaced it with cleaner code 95 | * based upon http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript 96 | * 97 | * (c.f. https://gist.github.com/th507/5158907) 98 | * 99 | 100 | function arrayEqual(a, b) { 101 | var i = Math.max(a.length, b.length, 1); 102 | while (i-- >= 0 && a[i] === b[i]); 103 | return (i === -2); 104 | } 105 | */ 106 | 107 | function arrayEqual(a, b) { 108 | 109 | var i, l; 110 | 111 | // if either of the arrays are falsy value, return 112 | if (!a || !b) { 113 | return false; 114 | } 115 | 116 | // compare lengths - can save a lot of time 117 | if (a.length !== b.length) { 118 | return false; 119 | } 120 | 121 | for (i = 0, l = a.length; i < l; i += 1) { 122 | // Check if we have nested arrays 123 | if (a[i] instanceof Array && b[i] instanceof Array) { 124 | // recurse into the nested arrays 125 | if (!arrayEqual(a[i], b[i])) { 126 | return false; 127 | } 128 | } else if (a[i] !== b[i]) { 129 | // Warning - two different object instances will never be equal: {x:20} != {x:20} 130 | return false; 131 | } 132 | } 133 | 134 | return true; 135 | } 136 | 137 | /* 138 | * Flatten nested arrays into a single array 139 | * 140 | * (c.f. https://gist.github.com/th507/5158907) 141 | */ 142 | 143 | module.exports.flatten = function flatten(arr) { 144 | var r = []; 145 | while (!arrayEqual(r, arr)) { 146 | r = arr; 147 | arr = [].concat.apply([], arr); 148 | } 149 | return arr; 150 | }; -------------------------------------------------------------------------------- /array-merge/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-17 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * In order to win the prize for most cookies sold, my friend Alice and I are going to merge our Girl Scout Cookies 9 | * orders and enter as one unit. Each order is represented by an "order id" (an integer). We have our lists of orders 10 | * sorted numerically already, in arrays. Write a function to merge our arrays of orders into one sorted array. 11 | * 12 | * For example: 13 | * 14 | * my_array = [3,4,6,10,11,15] 15 | * alices_array = [1,5,8,12,14,19] 16 | * 17 | * print merge_arrays(my_array, alices_array) 18 | * # prints [1,3,4,5,6,8,10,11,12,14,15,19] 19 | */ 20 | 21 | "use strict"; 22 | 23 | var arrayMerge = require('./arraymerge.js'); 24 | 25 | // Test happy path set 26 | 27 | var myArray = [3, 4, 6, 10, 11, 15]; 28 | var alicesArray = [1, 5, 8, 12, 14, 19]; 29 | console.log("Happy Path Array Merge"); 30 | console.log(arrayMerge.merge(myArray, alicesArray)); 31 | 32 | // Test edge cases 33 | 34 | console.log("Alternate Test Cases"); 35 | console.log(arrayMerge.merge([], [])); // both empty 36 | console.log(arrayMerge.merge([], [1, 2, 3])); // a empty 37 | console.log(arrayMerge.merge([1, 2, 3], [])); // b empty 38 | console.log(arrayMerge.merge([1, 2], [3, 4, 5])); // exhausting a before b 39 | console.log(arrayMerge.merge([3, 4, 5], [1, 2])); // exhausting b before a 40 | console.log(arrayMerge.merge([1, 2, 5, 6], [3, 4])); // b in the middle of a 41 | console.log(arrayMerge.merge([3, 4], [1, 2, 5, 6])); // a in the middle of b 42 | console.log(arrayMerge.merge([9, 2, 1], [6, 3, 4, 7, 5])); // not ordered lists 43 | 44 | // BONUS: Merging several sorted arrays passed as an array of arrays 45 | 46 | console.log("BONUS: Multiple Array Merge Happy Path"); 47 | console.log(arrayMerge.mergeMulti([[3, 4, 6, 10, 11, 15], [1, 5, 8, 12, 14, 19], [2, 7, 9, 13]])); 48 | console.log("BONUS: Multiple Array Merge Alternate Test Cases"); 49 | console.log(arrayMerge.mergeMulti([[], [], []])); 50 | console.log(arrayMerge.mergeMulti([[1, 2, 3], [], []])); 51 | console.log(arrayMerge.mergeMulti([[], [1, 2, 3], []])); 52 | console.log(arrayMerge.mergeMulti([[], [], [1, 2, 3]])); 53 | console.log(arrayMerge.mergeMulti([[1, 2, 3], [4, 5, 6, 7], []])); 54 | console.log(arrayMerge.mergeMulti([[4, 5, 6, 7], [1, 2, 3], []])); 55 | console.log(arrayMerge.mergeMulti([[], [4, 5, 6, 7], [1, 2, 3]])); 56 | console.log(arrayMerge.mergeMulti([[1, 2, 7, 8], [3, 6], [4, 5]])); 57 | 58 | // Playing around with an array flattening method 59 | 60 | console.log("Array Flattening Experiment"); 61 | console.log(arrayMerge.flatten([[1, 2], [3, 4]])); 62 | 63 | console.log(arrayMerge.flatten([[[10, 11], 1, 2], [3, 4, [5, 6, 7, [12]]]])); 64 | 65 | -------------------------------------------------------------------------------- /bracket-validator/bracketvalidator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-31 3 | */ 4 | 5 | "use strict"; 6 | 7 | /* 8 | * Validate that brackets, braces, and parentheses all match 9 | */ 10 | 11 | module.exports.validate = function validate(string, pairs) { 12 | 13 | var symbolStack = [], // Stack of opening symbols of representing symbol pairs that are unmatched 14 | poppedSymbol, // Symbol popped of the stack 15 | poppedSymbolIndex, // Index within the 'pairs' string of valid symbol pairs of the popped symbol 16 | matchingSymbolIndex, // Index within the 'pairs' string of the current symbol within the string being evaluated 17 | index; // Index for position within the 'string' buffer 18 | 19 | // If no symbol set is defined or if it contains only one symbol as opposed to pair, throw error 20 | 21 | if (pairs === undefined || pairs.length < 2) { 22 | return new Error("At least one pair of symbols needs to be defined in the symbol set"); 23 | } 24 | 25 | // If the symbol set contains an odd number of symbols then it doesn't contain a set of pairs, throw error 26 | 27 | if (pairs.length % 2) { 28 | return new Error("The set of valid symbols '" + pairs + "' is missing a symbol--it must contain pairs of symbols"); 29 | } 30 | 31 | // Walk through all the characters in the string that needs to be validated. When an opening symbol of a given 32 | // symbol pair is encountered, push it onto the stack of unmatched pairs. When a closing symbol of a given pair is 33 | // found, pop its mate off the stack if possible and validate that it is as expected. 34 | 35 | for (index = 0; index < string.length; index += 1) { 36 | 37 | // See if the current character within the string to be validated is a symbol from our symbol pairs, if it is 38 | // not, then move on to the next character; otherwise, handle it accordingly 39 | 40 | matchingSymbolIndex = pairs.indexOf(string[index]); 41 | if (matchingSymbolIndex > -1) { 42 | 43 | // If the character matches one a symbol in the symbol set at an even location (0, 2, 4, etc.) then the 44 | // symbol by definition is an opening symbol so push it on the stack. 45 | 46 | if ((matchingSymbolIndex % 2) === 0) { 47 | symbolStack.push(string[index]); 48 | } else { 49 | 50 | // Otherwise, it is a closing symbol and needs to be paired with its mate. However, if the opening 51 | // symbol stack is empty, then we have a symbol mismatch, throw an error. 52 | 53 | if (symbolStack.length === 0) { 54 | return new Error("Encountered a closing '" + string[index] + "' at offset " + index + " but missing a matching '" + pairs[pairs.indexOf(string[index]) - 1] + "'"); 55 | } 56 | 57 | // Pull an opening symbol from the stack and validate that it is the matching mate to the closing 58 | // symbol currently being handled. If it isn't, then we have another mismatch error and need to throw it 59 | 60 | poppedSymbol = symbolStack.pop(); 61 | poppedSymbolIndex = pairs.indexOf(poppedSymbol); 62 | if (pairs[poppedSymbolIndex + 1] !== string[index]) { 63 | return new Error("Encountered a closing '" + string[index] + "' at offset " + index + " but expected '" + pairs[poppedSymbolIndex + 1] + "'"); 64 | } 65 | 66 | } 67 | 68 | } 69 | } 70 | 71 | // When we're done inspecting all the characters in the input stream, if the opening symbol stack still contains 72 | // symbols then we have another mismatch condition, throw an error. 73 | 74 | if (symbolStack.length > 0) { 75 | return new Error("Reached the end of string but still expecting " + symbolStack.length + " more appropriate closing symbols"); 76 | } 77 | 78 | // All is good -- String fully processed and everything matched. 79 | 80 | return true; 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /bracket-validator/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-11 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * You're working with an intern that keeps coming to you with JavaScript code that won't run because the braces, 9 | * brackets, and parentheses are off. To save you both some time, you decide to write a braces/brackets/parentheses 10 | * validator. 11 | * 12 | * Let's say: 13 | * '(', '{', '[' are called "openers." 14 | * ')', '}', ']' are called "closers." 15 | * 16 | * Write an efficient function that tells us whether or not an input string's openers and closers are properly nested. 17 | * 18 | * Examples: 19 | * "{ [ ] ( ) }" should return true 20 | * "{ [ ( ] ) }" should return false 21 | * "{ [ }" should return false 22 | */ 23 | 24 | "use strict"; 25 | 26 | var bracketValidator = require('./bracketvalidator.js'); 27 | 28 | var symbolPairs = '{}[]()', 29 | string; 30 | 31 | console.log("Problem Examples"); 32 | console.log("----------------"); 33 | 34 | string = '{ [ ] ( ) }'; 35 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 36 | 37 | string = '{ [ ( ] ) }'; 38 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 39 | 40 | string = '{ [ }'; 41 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 42 | 43 | console.log(); 44 | console.log("Bonus Conditions"); 45 | console.log("---------------------"); 46 | 47 | string = '|like this|'; 48 | symbolPairs = '{}[]()||'; 49 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 50 | 51 | 52 | 53 | 54 | console.log(); 55 | console.log("Additional Test Cases"); 56 | console.log("---------------------"); 57 | 58 | string = ''; 59 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 60 | 61 | string = '[ ] ]'; 62 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 63 | 64 | string = '[ [ ]'; 65 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 66 | 67 | string = ''; 68 | symbolPairs = undefined; 69 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 70 | symbolPairs = ''; 71 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 72 | symbolPairs = '['; 73 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 74 | symbolPairs = '[]('; 75 | console.log("'%s' %s", string, bracketValidator.validate(string, symbolPairs)); 76 | 77 | -------------------------------------------------------------------------------- /cake-thief/dynamicUKP.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-12 3 | */ 4 | 5 | /* 6 | * Dynamic Unbounded Knapsack Problem (dynamicUKP) 7 | * 8 | * Calculate a solution to the unbounded knapsack problem (UKP) in pseudo-polynomial time using dynamic programming 9 | * 10 | * Given an array of double tuples (2-tuples) of weights and values, i.e "(weight, value)" and a maximum capacity, 11 | * return the maximum total value that can be obtained. The solution assumes all weights and values are positive. Any 12 | * tuples where this is not the case are ignored. Knapsack capacity and weights are treated as integers. It is also 13 | * assumed that capacity is always positive--negative capacity knapsacks return a value of zero. This solution uses a 14 | * tail-recursive approach. 15 | * 16 | * Time Complexity: O(nC) where n = number of tuples, C = knapsack capacity 17 | * Space Complexity: O(C) where C = knapsack capacity 18 | * 19 | * Solution is based upon the solution provided at csegeek.com but adapted for the eccentricities of JavaScript with 20 | * support added for an array of tuples. The csegeek solution also returns a list of the number of items of each that 21 | * are used to fill the knapsack, but that has been omitted from this solution. 22 | * 23 | * c.f. http://en.wikipedia.org/wiki/Knapsack_problem 24 | * c.f. http://www.csegeek.com/csegeek/view/tutorials/algorithms/dynamic_prog/dp_part7.php 25 | */ 26 | 27 | "use strict"; 28 | 29 | module.exports.dynamicUKP = function dynamicUKP(tuples, capacity) { 30 | 31 | // bail out if capacity is not zero or greater 32 | if (capacity < 0) { 33 | return 0; 34 | } 35 | 36 | // knapsack is a temporary array where the value of each element is the maximum value that can be contained within 37 | // a knapsack whose weight is the value of the array index 38 | 39 | var WEIGHT = 0, // Index of the tuple element that defines the weight (constant) 40 | VALUE = 1, // Index of the tuple element that defines the value (constant) 41 | knapsack = [0], // Initialize our knapsack array with a zero value first element 42 | max, 43 | c, l, // Pre-calc loop termination (c - capacity, l - tuples.length) 44 | i, j, // Loop indices 45 | x; 46 | 47 | for (j = 1, c = Math.floor(capacity); j <= c; j += 1) { 48 | knapsack[j] = 0; // expand our array 49 | max = knapsack[j - 1]; 50 | for (i = 0, l = tuples.length; i < l; i += 1) { 51 | if (Math.floor(tuples[i][WEIGHT]) > 0 && tuples[i][VALUE] > 0) { 52 | x = j - Math.floor(tuples[i][WEIGHT]); 53 | if (x >= 0 && (knapsack[x] + tuples[i][VALUE]) > max) { 54 | max = knapsack[x] + tuples[i][VALUE]; 55 | } 56 | } 57 | knapsack[j] = max; 58 | } 59 | } 60 | 61 | return knapsack[c]; 62 | }; 63 | -------------------------------------------------------------------------------- /cake-thief/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-11 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * "You are a renowned thief who has recently switched from stealing precious metals to stealing cakes because of the 9 | * insane profit margins. You end up hitting the jackpot, breaking into the world's largest privately owned stock of 10 | * cakes—the vault of the Queen of England. 11 | * 12 | * While Queen Elizabeth has a limited number of types of cake, she has an unlimited supply of each type. 13 | * 14 | * Each type of cake has a weight and a value, stored in tuples with two positions: 15 | * - An integer representing the weight of the cake in kilograms 16 | * - An integer representing the monetary value of the cake in British pounds 17 | * 18 | * For example: 19 | * - weighs 7 kilograms and has a value of 160 pounds 20 | * (7, 160) 21 | * - weighs 3 kilograms and has a value of 90 pounds 22 | * (3, 90) 23 | * 24 | * You brought a duffel bag that can hold limited weight, and you want to make off with the most valuable haul possible. 25 | * 26 | * Write a function max_duffel_bag_value() that takes an array of cake tuples and a weight capacity, and returns the 27 | * maximum monetary value the duffel bag can hold. 28 | * 29 | * For example: 30 | * 31 | * cake_tuples = [(7, 160), (3, 90), (2, 15)] 32 | * capacity = 20 33 | * 34 | * max_duffel_bag_value(cake_tuples, capacity) 35 | * 36 | * # returns 555 (6 of the middle type of cake and 1 of the last type of cake) 37 | * 38 | * Weights and values may be any non-negative integer. Yes, it's weird to think about cakes that weigh nothing or 39 | * duffel bags that can't hold anything. But we're not just super mastermind criminals—we're also meticulous about 40 | * keeping our algorithms flexible and comprehensive." 41 | */ 42 | 43 | "use strict"; 44 | 45 | var cakeThief = require('./dynamicukp.js'); 46 | 47 | var cakeTuples = [[7, 160], [3, 90], [2, 15]]; 48 | var capacity = 20; 49 | 50 | // Test 1: Happy Path 51 | var result = cakeThief.dynamicUKP(cakeTuples, capacity); 52 | console.log("Happy Path"); 53 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 54 | console.log(" capacity : ", capacity); 55 | console.log(" result : ", result, (result === 555) ? " (pass)" : " (fail)"); 56 | 57 | // Test 2: Zero capacity 58 | capacity = 0; 59 | result = cakeThief.dynamicUKP(cakeTuples, capacity); 60 | console.log("Zero Capacity"); 61 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 62 | console.log(" capacity : ", capacity); 63 | console.log(" result : ", result, (result === 0) ? " (pass)" : " (fail)"); 64 | 65 | // Test 3: Negative capacity 66 | capacity = -10; 67 | result = cakeThief.dynamicUKP(cakeTuples, capacity); 68 | console.log("Negative Capacity"); 69 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 70 | console.log(" capacity : ", capacity); 71 | console.log(" result : ", result, (result === 0) ? " (pass)" : " (fail)"); 72 | 73 | // Test 4: Zero weight and Zero Value 74 | capacity = 20; 75 | cakeTuples.push([0, 0]); 76 | result = cakeThief.dynamicUKP(cakeTuples, capacity); 77 | console.log("A Zero Weight and Value Item"); 78 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 79 | console.log(" capacity : ", capacity); 80 | console.log(" result : ", result, (result === 555) ? " (pass)" : " (fail)"); 81 | 82 | // Test 5: Non-zero weight and Zero Value 83 | capacity = 20; 84 | cakeTuples.pop(); 85 | cakeTuples.push([10, 0]); 86 | result = cakeThief.dynamicUKP(cakeTuples, capacity); 87 | console.log("A Zero Value Item"); 88 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 89 | console.log(" capacity : ", capacity); 90 | console.log(" result : ", result, (result === 555) ? " (pass)" : " (fail)"); 91 | 92 | // Test 6: Zero weight and Non-zero Value 93 | capacity = 20; 94 | cakeTuples.pop(); 95 | cakeTuples.push([0, 10]); 96 | result = cakeThief.dynamicUKP(cakeTuples, capacity); 97 | console.log("A Zero Weight Item"); 98 | console.log(" cakeTuples: ", JSON.stringify(cakeTuples)); 99 | console.log(" capacity : ", capacity); 100 | console.log(" result : ", result, (result === 555) ? " (pass)" : " (fail)"); -------------------------------------------------------------------------------- /dup-files/finddupfiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-17 3 | */ 4 | 5 | /** 6 | * Finds duplicate files within a files structure based upon CRC-32 signatures as opposed to other file 7 | * attributes and is thus able to find duplicate files that may have different file names, creation dates, etc. 8 | * 9 | * This modules works by crawling the directory structure starting from a specified path. As it walks the 10 | * structure, it calculates a CRC-32 signature for each file. Then, the module builds a multi-map using the 11 | * CRC-32 signature as the key. A multi-map is a hash map that allows storing multiple values for a given 12 | * hash key. The module then walks through the hash map and builds an array of entries that contain two or more 13 | * items for a given hash key and thus represent duplicate files. It then returns this array to the caller. 14 | * 15 | * Currently, this module is written using synchronous calls to the file system. 16 | * 17 | * */ 18 | 19 | "use strict"; 20 | 21 | var fs = require('fs'), 22 | path = require('path'), 23 | crc = require('crc'), 24 | MultiMap = require('dsjslib').MultiMap; 25 | 26 | /* getFilesSync 27 | * 28 | * Walks a specified directory adding to the 'files' array all the files found in the directory and adding to 29 | * the 'dirs' array all the sub-directories encountered. For each file, the file's attributes are also saved 30 | * along with a calculated CRC-32 value. Files that cannot be read due to access rights or other 31 | * constraints are logged in an errors array. 32 | * 33 | * Files system entries other than files or directories are ignored (i.e. devices, symbolic links, etc., are 34 | * ignored) 35 | * 36 | * This approach is currently implemented with synchronous calls to file system services. 37 | * 38 | * 'errors' - an array containing any error messages returned by the file system. 'getFilesSync' appends to 39 | * this array any errors it encounters while calling file system services then continues processing. 40 | * 'dirs' - an array containing a list of directory paths. 'getFileSync' appends to this array any 41 | * sub-directories encountered while reading the files contained in 'dir'. 42 | * 'files' - an array containing a set of objects. Each object contains the name, directory, file path, 43 | * file attributes, and calculated CRC-32 code for each file contained in 'dir'. 44 | * 'dir' - the path to the directory to be processed. 45 | */ 46 | 47 | function getFilesSync(errors, dirs, files, dir) { 48 | //Okay, so Douglas Crockford hates synchronous calls preferring to brand them 'stupid' within JSLint 49 | //checks. I don't like them either. But, I haven't given myself the time to figure out how to implement 50 | //this function using asynchronous versions of 'readdir', 'lstat', and 'readFile' yet. So, for now, we 51 | //will have JSLint tolerate my 'stupidity' in the eyes of Demigod Crockford. 52 | /*jslint stupid: true */ 53 | try { 54 | fs.readdirSync(dir).forEach(function (item) { 55 | var filepath = path.join(dir, item), 56 | stats = fs.lstatSync(filepath), 57 | crccode; 58 | if (stats.isDirectory()) { 59 | dirs.push(filepath); 60 | } else if (stats.isFile()) { 61 | crccode = crc.crc32(fs.readFileSync(filepath, 'utf8')).toString(16); 62 | files.put(crccode, { 63 | name: item, 64 | dir: dir, 65 | path: filepath, 66 | stats: stats, 67 | crc: crccode 68 | }); 69 | } 70 | }); 71 | } catch(err) { 72 | errors.push(err); 73 | } 74 | /*jslint stupid: false */ 75 | } 76 | 77 | /* walkDirsSync 78 | * 79 | * Walk through the directory structure starting from 'dir' and return an array containing all 80 | * encountered files including the file name, directory, full path, file attributes, and CRC-32 81 | * for the file. 82 | * 83 | * This approach is currently implemented with synchronous calls to file system services. 84 | * 85 | * 'errors' - an array containing any error messages returned by the file system. 'getFilesSync' appends to 86 | * this array any errors it encounters while calling file system services then continues processing. 87 | * 'files' - an array containing a set of objects. Each object contains the name, directory, file path, 88 | * file attributes, and calculated CRC-32 code for each file contained in 'dir'. 89 | * 'dir' - the path to the directory to be processed. 90 | */ 91 | 92 | function walkDirsSync(errors, files, dir) { 93 | var dirs = []; 94 | dirs.push(dir); 95 | while (dirs.length > 0) { 96 | getFilesSync(errors, dirs, files, dirs.pop()); 97 | } 98 | } 99 | 100 | /* findDuplicateFiles 101 | * 102 | * Takes in an array of file objects (containing file name, path, file attributes, and calculated CRC-32) and 103 | * returns another array containing tuples of the duplicate files regardless of their file name (based upon 104 | * the CRC-32) 105 | */ 106 | 107 | module.exports.findDuplicateFiles = function findDuplicateFiles(dir) { 108 | 109 | var files = new MultiMap(), // Multi-map hash map 110 | elements = [], // Array of hash map value elements for a given key 111 | errors = [], // Array of error messages 112 | duplicates = [], // Array of duplicate file sets 113 | keys, // Array of key-value elements from hash map 114 | pairs, // Array of individual duplicate files for a given key 115 | elementKey, // Hash map element key 116 | i, j; // Array indices 117 | 118 | // Get all the files we need to check for duplicates 119 | 120 | walkDirsSync(errors, files, dir); 121 | 122 | // Check for duplicates using a multi-map hash map 123 | 124 | keys = files.entries(); 125 | for (i = 0; i < keys.length; i += 1) { 126 | elementKey = keys[i].key; 127 | elements = files.get(elementKey); 128 | if (elements.length > 1) { 129 | pairs = []; 130 | for (j = 0; j < elements.length; j += 1) { 131 | pairs.push(elements[j].path); 132 | } 133 | duplicates.push(pairs); 134 | } 135 | } 136 | 137 | return duplicates; 138 | }; -------------------------------------------------------------------------------- /dup-files/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-07 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * You left your computer unlocked and your friend decided to troll you by copying a lot of your files to random spots 9 | * all over your file system. Even worse, she saved the duplicate files with random, embarrassing names 10 | * ("this_is_like_a_digital_wedgie.txt" was clever, I'll give her that). 11 | * 12 | * Write a function that returns a list of all the duplicate files. We'll check them by hand before actually 13 | * deleting them, since programmatically deleting files is really scary. To help us confirm that two files are 14 | * actually duplicates, make the returned list a list of tuples, where: 15 | * 16 | * the first item is the duplicate file 17 | * the second item is the original file 18 | * 19 | * For example: 20 | * 21 | * [('/tmp/parker_is_dumb.mpg', '/home/parker/secret_puppy_dance.mpg'), ('/home/trololol.mov', '/etc/apache2/httpd.conf')] 22 | */ 23 | 24 | "use strict"; 25 | 26 | console.log(require('./finddupfiles.js').findDuplicateFiles("/users/ptdecker/Code")); 27 | 28 | -------------------------------------------------------------------------------- /highest-product/arraytools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-26 3 | */ 4 | 5 | /** 6 | * Array Tools 7 | */ 8 | 9 | "use strict"; 10 | 11 | /* minIndex 12 | * 13 | * Finds the index of the minimum item in an array 14 | * 15 | */ 16 | 17 | function minIndex(array) { 18 | var i, min; 19 | for (i = 0; i < array.length; i += 1) { 20 | if (min === undefined || Math.abs(array[i]) < Math.abs(array[min])) { 21 | min = i; 22 | } 23 | } 24 | return min; 25 | } 26 | 27 | /* highestProduct 28 | * 29 | * Calculates the product of the 'size' largest integers contained in an 'array' 30 | * 31 | * Parker's suggested solution is fixed at finding the product of the largest three integers. The approach below 32 | * is a generic approach which returns the product of the largest 'x' integers. 33 | * 34 | * Time analysis is: 35 | * 36 | * O(size) + O(n - size) * O(size) where size is a constant 'C' 37 | * 38 | * or 39 | * 40 | * O(size + size * (n - size)) 41 | * 42 | * or 43 | * 44 | * O(size + size * n - size ^ 2) 45 | * 46 | * which, I believe, is: 47 | * 48 | * O(size * n) and since size is constant we hae 49 | * 50 | * O(n) 51 | * 52 | */ 53 | 54 | module.exports.highestProduct = function intProductExcludingIndex(array, size) { 55 | 56 | var i, 57 | highProducts, 58 | min, 59 | totalProduct; 60 | 61 | // Return a highest product value of zero if no values are included nor if size is zero 62 | 63 | if (size === 0 || array.length === 0) { 64 | return 0; 65 | } 66 | 67 | // Assume that the first 'size' items passed are the largest integers. Do this by slicing off the first 68 | // 'size' elements and taking the absolute value of each of them. 69 | 70 | highProducts = array.slice(0, size).map(function(num) { return Math.abs(num); }); 71 | 72 | // Walk through the remaining elements passed to us in the array (if any). 73 | 74 | for (i = size; i < array.length; i += 1) { 75 | 76 | // if the value of the current item being looked at in the array is greater than the smallest 77 | // element in the largest products accumulated so far, then replace out that smallest element 78 | // with the current one. Note that the minIndex function is O(size) where 'size' is the number 79 | // of largest indexes are desired. 80 | 81 | min = minIndex(highProducts); 82 | if (Math.abs(array[i]) > highProducts[min]) { 83 | highProducts[min] = Math.abs(array[i]); 84 | } 85 | } 86 | 87 | // Sum up the highest values found 88 | 89 | totalProduct = highProducts[0]; 90 | for (i = 1; i < highProducts.length; i += 1) { 91 | totalProduct *= highProducts[i]; 92 | } 93 | 94 | return totalProduct; 95 | }; -------------------------------------------------------------------------------- /highest-product/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-26 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Given an array_of_ints, find the highest_product you can get from three of the integers. 9 | * 10 | * The input array_of_ints will always have at least three integers. 11 | */ 12 | 13 | "use strict"; 14 | 15 | var arraytools = require('./arraytools.js'); 16 | 17 | // Test case from problem statement 18 | 19 | console.log(arraytools.highestProduct([-10, -10, 1, 3, 2], 3)); 20 | 21 | // Other edge test cases 22 | 23 | console.log(arraytools.highestProduct([1, 7, 3, 4], 0)); 24 | console.log(arraytools.highestProduct([1, 7, 3, 4], 1)); 25 | console.log(arraytools.highestProduct([1, 7, 3, 4], 2)); 26 | console.log(arraytools.highestProduct([1, 7, 3, 4], 3)); 27 | console.log(arraytools.highestProduct([1, 7, 3, 4], 4)); 28 | console.log(arraytools.highestProduct([1, 7, 3, 4], 5)); 29 | console.log(arraytools.highestProduct([], 3)); 30 | console.log(arraytools.highestProduct([1, 7], 3)); 31 | 32 | // Edge cases 33 | 34 | -------------------------------------------------------------------------------- /integer-products/arraytools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-23 3 | */ 4 | 5 | /** 6 | * Array Tools 7 | */ 8 | 9 | "use strict"; 10 | 11 | /* repeatedPush 12 | * 13 | * Repeatedly pushes 'value' onto an array 'count' number of times. Can be used to initialize an array of a 14 | * desired size with a fixed value. 15 | * 16 | * The solution below required O(n) time and space. An alternate O(n^2) brute force option is provided too. The 17 | * O(n) approach is a nice trick that falls out of an observation that the end value at a given index can 18 | * be determined by multiplying the product of all the values to to the left with the product of all the values 19 | * to the right. So, it makes one pass over the values populating the result array with the products of the 20 | * values from the left. Then makes a second pass the other direction multiplying the value at an index with 21 | * the accumulated product of all the values from the right. A pretty sweet approach. Parker characterizes this 22 | * as a "greedy" algorithm; however, I don't think it complies with the definition of such because there is 23 | * no selection of a candidate from a candidate set (characteristic one) nor feasibility function 24 | * characteristic two) (c.f. https://en.wikipedia.org/wiki/Greedy_algorithm) 25 | */ 26 | 27 | module.exports.intProductsExcludingIndex = function intProductExcludingIndex(array) { 28 | 29 | var result = [], 30 | i, 31 | factor; 32 | 33 | /* The brute force O(n^2) approach 34 | var j; 35 | for (i = 0; i < array.length; i += 1) { 36 | for (j = 0; j < array.length; j += 1) { 37 | factor = (i === j) ? 1 : array[i]; 38 | result[j] = (result[j] === undefined) ? factor : factor * result[j]; 39 | } 40 | } 41 | */ 42 | 43 | // The nice, clean, two-pass O(n) approach 44 | 45 | for(factor = 1, i = 0; i < array.length; i += 1) { 46 | result[i] = factor; 47 | factor *= array[i]; 48 | } 49 | 50 | for (factor = 1, i = (array.length - 1); i >= 0; i -= 1) { 51 | result[i] *= factor; 52 | factor *= array[i]; 53 | } 54 | 55 | return result; 56 | }; -------------------------------------------------------------------------------- /integer-products/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-24 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * 9 | * You have an array of integers, and for each index you want to find the product of every integer except the 10 | * integer at that index. Write a function get_products_of_all_ints_except_at_index() that takes an array of 11 | * integers and returns an array of the products. 12 | * 13 | * For example, given: 14 | * 15 | * [1, 7, 3, 4] 16 | * 17 | * your function would return: 18 | * 19 | * [84, 12, 28, 21] 20 | * 21 | * by calculating: 22 | * 23 | * [7*3*4, 1*3*4, 1*7*4, 1*7*3] 24 | * 25 | * Do not use division in your solution. 26 | */ 27 | 28 | "use strict"; 29 | 30 | var arraytools = require('./arraytools.js'); 31 | 32 | // Test case from problem statement 33 | 34 | console.log(arraytools.intProductsExcludingIndex([1, 7, 3, 4])); 35 | 36 | // Edge cases 37 | 38 | console.log(arraytools.intProductsExcludingIndex([1, 0, 3, 4])); // Impact of zero 39 | console.log(arraytools.intProductsExcludingIndex([10])); // One element 40 | console.log(arraytools.intProductsExcludingIndex([])); // Empty array 41 | -------------------------------------------------------------------------------- /kth-to-the-last-node/linkedList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-19 3 | * Updated on 2014-12-13 by ptdecker to include kthToLastNode() function 4 | */ 5 | 6 | /* 7 | * Basic In Memory Single Linked Linked List 8 | */ 9 | 10 | "use strict"; 11 | 12 | // Node constructor 13 | 14 | function Node(content) { 15 | this.data = content; 16 | this.nextNode = null; 17 | } 18 | 19 | // Link another node after this node 20 | 21 | Node.prototype.next = function next(node) { 22 | this.nextNode = node; 23 | }; 24 | 25 | // Delete node preserving the chain it may belong to by shifting nodes up 26 | 27 | Node.prototype.removeNode = function removeNode() { 28 | 29 | var tempNode = this.nextNode; 30 | 31 | if (tempNode) { 32 | this.data = this.nextNode.data; 33 | this.nextNode = this.nextNode.nextNode; 34 | delete tempNode.data; 35 | delete tempNode.nextNode; 36 | tempNode = null; 37 | } 38 | 39 | }; 40 | 41 | // Walk downward from this node and return a string of the node contents 42 | 43 | Node.prototype.walkDown = function walkDown() { 44 | var currentNode = this, 45 | nodeList = ""; 46 | while (currentNode) { 47 | nodeList += JSON.stringify(currentNode.data); 48 | currentNode = currentNode.nextNode; 49 | if (currentNode) { 50 | nodeList += ", "; 51 | } 52 | } 53 | return nodeList; 54 | }; 55 | 56 | /* 57 | * Return the "kth to the last node" 58 | * 59 | * Will return 'null' if there are less than kth items in the list 60 | */ 61 | 62 | Node.prototype.kthToLastNode = function kthToLastNode(k) { 63 | 64 | if (k < 1) { 65 | return new Error("'kthToLastNode' was passed a value less than '1'"); 66 | } 67 | 68 | var currentNode = this, 69 | kthNode = this, 70 | nodeCount = 0; 71 | 72 | /* while (currentNode) { 73 | if (nodeCount < k) { 74 | nodeCount += 1; 75 | if (nodeCount === k) { 76 | kthNode = this; 77 | } 78 | } else { 79 | kthNode = kthNode.nextNode; 80 | } 81 | currentNode = currentNode.nextNode; 82 | } 83 | */ 84 | while (currentNode && nodeCount < k) { 85 | currentNode = currentNode.nextNode; 86 | nodeCount += 1; 87 | } 88 | 89 | while (currentNode) { 90 | currentNode = currentNode.nextNode; 91 | kthNode = kthNode.nextNode; 92 | } 93 | 94 | return (nodeCount < k ? null : kthNode.data); 95 | }; 96 | 97 | module.exports = Node; -------------------------------------------------------------------------------- /kth-to-the-last-node/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-13 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * You have a linked list ↴ and want to find the kth to last node. Write a function kth_to_last_node() that takes an 9 | * integer k and the head_node of a singly linked list, and returns the kth to last node in the list. 10 | * 11 | * For example: 12 | * 13 | * a = Node("Angel Food") 14 | * b = Node("Bundt") 15 | * c = Node("Cheese") 16 | * d = Node("Devil's Food") 17 | * e = Node("Eccles") 18 | * 19 | * a.next = b 20 | * b.next = c 21 | * c.next = d 22 | * d.next = e 23 | * 24 | * kth_to_last_node(2, a) 25 | * # returns the node with value "Devil's Food" (the 2nd to last node) 26 | */ 27 | 28 | "use strict"; 29 | 30 | var Node = require('./linkedlist.js'); 31 | 32 | var a = new Node("Angel Food"); 33 | var b = new Node("Bundt"); 34 | var c = new Node("Cheese"); 35 | var d = new Node("Devil's Food"); 36 | var e = new Node("Eccles"); 37 | 38 | a.next(b); 39 | b.next(c); 40 | c.next(d); 41 | d.next(e); 42 | 43 | console.log("Items: " + a.walkDown()); 44 | 45 | console.log("2nd to the last item is: " + a.kthToLastNode(2)); -------------------------------------------------------------------------------- /linked-list/linkedList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-19 3 | */ 4 | 5 | /* 6 | * Basic In Memory Single Linked Linked List 7 | */ 8 | 9 | "use strict"; 10 | 11 | // Node constructor 12 | 13 | function Node(content) { 14 | this.data = content; 15 | this.nextNode = null; 16 | } 17 | 18 | // Link another node after this node 19 | 20 | Node.prototype.next = function next(node) { 21 | this.nextNode = node; 22 | }; 23 | 24 | // Delete node preserving the chain it may belong to by shifting nodes up 25 | 26 | Node.prototype.removeNode = function removeNode() { 27 | 28 | var tempNode = this.nextNode; 29 | 30 | if (tempNode) { 31 | this.data = this.nextNode.data; 32 | this.nextNode = this.nextNode.nextNode; 33 | delete tempNode.data; 34 | delete tempNode.nextNode; 35 | tempNode = null; 36 | } 37 | 38 | }; 39 | 40 | // Walk downward from this node and return a string of the node contents 41 | 42 | Node.prototype.walkDown = function walkDown() { 43 | var currentNode = this, 44 | nodeList = ""; 45 | while (currentNode) { 46 | nodeList += JSON.stringify(currentNode.data); 47 | currentNode = currentNode.nextNode; 48 | if (currentNode) { 49 | nodeList += ", "; 50 | } 51 | } 52 | return nodeList; 53 | }; 54 | 55 | module.exports = Node; -------------------------------------------------------------------------------- /linked-list/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-11-19 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * "Delete a node from a singly linked list, given only a variable pointing to that node. 9 | * The input could, for example, be the variable b below: 10 | * 11 | * a = Node('A') 12 | * b = Node('B') 13 | * c = Node('C') 14 | * 15 | * a.next = b 16 | * b.next = c 17 | * 18 | * delete_node(b) 19 | */ 20 | 21 | "use strict"; 22 | 23 | var Node = require('./linkedlist.js'); 24 | 25 | var a = new Node('A'); 26 | var b = new Node('B'); 27 | var c = new Node('C'); 28 | 29 | a.next(b); 30 | b.next(c); 31 | 32 | console.log("Before deletion: " + a.walkDown()); 33 | 34 | b.removeNode(); 35 | 36 | console.log("After deletion : " + a.walkDown()); -------------------------------------------------------------------------------- /make-change/money.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-03-13 3 | */ 4 | 5 | /** 6 | * Money Tools 7 | */ 8 | 9 | "use strict"; 10 | 11 | /* changePossibilitiesBottomUp 12 | * 13 | * Use a bottom-up, non-recursive approach to determining the number of ways to make change for a given 14 | * 'amount' using a set of denominations. 15 | * 16 | * This approach requires O(n * m) time and O(n) space 17 | * 18 | */ 19 | 20 | function changePossibilitiesBottomUp(amount, denominations) { 21 | 22 | var waysOfDoingNCents = [1], 23 | higherAmount, 24 | higherAmountRemainder, 25 | i; 26 | 27 | for (i = 1; i < (amount + 1); i += 1) { 28 | waysOfDoingNCents[i] = 0; 29 | } 30 | 31 | denominations.forEach(function (coin) { 32 | for (higherAmount = coin; higherAmount <= (amount + 1); higherAmount += 1) { 33 | higherAmountRemainder = higherAmount - coin; 34 | waysOfDoingNCents[higherAmount] += waysOfDoingNCents[higherAmountRemainder]; 35 | } 36 | }); 37 | 38 | return waysOfDoingNCents[amount]; 39 | } 40 | 41 | /* numWaysToMakeChange 42 | * 43 | * Computes the number of ways to make change of a given amount from a set of coin denominations 44 | */ 45 | 46 | module.exports.numWaysToMakeChange = function numWaysToMakeChange(amount, denominations) { 47 | 48 | // Error checking and special cases 49 | 50 | if (amount < 0) { 51 | return new Error("'numWaysToMakeChange': Amount to change must be a positive number"); 52 | } 53 | 54 | if (denominations.length < 1) { 55 | return new Error("'numWaysToMakeChange': At least one coin of denomination '1' must be provided"); 56 | } 57 | 58 | if (amount === 0) { 59 | return 0; 60 | } 61 | 62 | // All error checking passed, do the actual work 63 | 64 | return changePossibilitiesBottomUp(amount, denominations); 65 | } -------------------------------------------------------------------------------- /make-change/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-03-13 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Imagine you landed a new job as a cashier... 9 | * 10 | * Your quirky boss found out that you're a programmer and has a weird request about something they've been wondering 11 | * for a long time. 12 | * 13 | * Write a function that, given: 14 | * 15 | * 1. an amount of money 16 | * 2. a list of coin denominations 17 | * 18 | * computes the number of ways to make amount of money with coins of the available denominations. 19 | * 20 | * Example: for amount=4 (4¢) and denominations=[1,2,3] (1¢, 2¢ and 3¢), your program would output 4—the number 21 | * of ways to make 4¢ with those denominations: 22 | * 23 | * 1¢, 1¢, 1¢, 1¢ 24 | * 1¢, 1¢, 2¢ 25 | * 1¢, 3¢ 26 | * 2¢, 2¢ 27 | */ 28 | 29 | "use strict"; 30 | 31 | var money = require('./money.js'); 32 | 33 | console.log(money.numWaysToMakeChange( 4, [])); // no denomination 34 | console.log(money.numWaysToMakeChange(-1, [1, 2, 3])); // negative amount 35 | console.log(money.numWaysToMakeChange( 0, [1, 2, 3])); // zero amount 36 | console.log(money.numWaysToMakeChange( 4, [1, 2, 3])); // exercise example 37 | console.log(money.numWaysToMakeChange( 4, [1, 3, 2])); // out of order list 38 | console.log(money.numWaysToMakeChange( 7, [2, 4])); // impossible to make change -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interview-cake-answers", 3 | "description": "Nodejs JavaScript Solutions to Interview Cake problems", 4 | "version": "0.0.1", 5 | "private": true, 6 | "dependencies": { 7 | "async":"0.9.x", 8 | "crc":"3.2.x", 9 | "dsjslib":"0.6.x" 10 | }, 11 | "repository": { 12 | "type":"git", 13 | "url":"https://github.com/ptdecker/interview-cake-answers-javascript.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rand5/random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-03-19 3 | */ 4 | 5 | /** 6 | * Random Number Tools 7 | */ 8 | 9 | "use strict"; 10 | 11 | /* rand(x) 12 | * 13 | * Returns a random integer from one to 'x' inclusively. 14 | */ 15 | 16 | function rand(x) { 17 | return Math.floor((Math.random() * x) + 1); 18 | } 19 | 20 | 21 | /* rand5UsingRand7() 22 | * 23 | * Tries to generate a random number from one to five inclusive using rand7() which generates random numbers 24 | * from one to seven inclusive. It does this by repeatedly picking a rand7() number until a number less than 25 | * six is returned (thus being in the range of one to five). Since rand7() is based on a purely random 26 | * distribution, the derived distribution will be random too. In theory, this approach could infinitely run 27 | * if a six or seven were always returned by rand7(). To guard against this possibility, the algorithm gives 28 | * up and returns an error after 10,000 tries. 29 | */ 30 | 31 | module.exports.rand5UsingRand7 = function rand5UsingRand7() { 32 | 33 | var triesRemaining = 10000, 34 | result = 0; 35 | 36 | do { 37 | result = rand(7); 38 | triesRemaining -= 1; 39 | } while (result > 5 && triesRemaining > 0); 40 | 41 | if (triesRemaining > 0) { 42 | return result; 43 | } 44 | 45 | return new Error("'rand5UsingRand7': Could not find a valid rand5() number in a reasonable number of tries."); 46 | } -------------------------------------------------------------------------------- /rand5/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-03-19 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * You have a function rand7() that generates a random integer from 1 to 7. Use it to write a function rand5() 9 | * that generates a random integer from 1 to 5. rand7() returns each integer with equal probability. rand5() must 10 | * also return each integer with equal probability. 11 | * 12 | * 13 | * / 14 | 15 | "use strict"; 16 | 17 | // Test by running 1,000,000 trials and displaying the distribution 18 | 19 | var random = require('./random.js'), 20 | runs = 1000000, 21 | result, 22 | results = [0, 0, 0, 0, 0, 0]; 23 | 24 | console.log("Running 1,000,000 trials to determine distribution . . ."); 25 | 26 | while (runs > 0) { 27 | result = random.rand5UsingRand7(); 28 | if (!isNaN(result)) { 29 | results[random.rand5UsingRand7()] += 1; 30 | runs -= 1; 31 | } 32 | } 33 | 34 | console.log("Results . . .\n"); 35 | 36 | for (result = 1; result <= 5; result += 1) { 37 | console.log(results[result], Math.round(results[result] / 10000) + "%"); 38 | } -------------------------------------------------------------------------------- /range-merge/rangemerge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-22 3 | */ 4 | 5 | /* 6 | * Merge Ranges 7 | **/ 8 | 9 | "use strict"; 10 | 11 | module.exports.rangemerge = function rangemerge(array) { 12 | 13 | var merged, // Array of merged tuple results 14 | element, // Next element to be added to the merged tuple results 15 | i; // Index to the current tuple being evaluated; 16 | 17 | if (array.length === 0) { 18 | return []; 19 | } 20 | 21 | array.sort(function(a, b) { 22 | return (a[0] > b[0]); 23 | }); 24 | 25 | i = 0; 26 | element = array[i]; 27 | merged = []; 28 | do { 29 | i += 1; 30 | if (i < array.length && element[1] >= array[i][0]) { 31 | if (element[1] < array[i][1]) { 32 | element[1] = array[i][1]; 33 | } 34 | } else { 35 | merged.push(element); 36 | element = array[i]; 37 | } 38 | } while (i < array.length); 39 | 40 | return merged; 41 | }; -------------------------------------------------------------------------------- /range-merge/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-22 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Your company built an in-house calendar tool called HiCal. You want to add a feature to see the times in a 9 | * day when everyone is available. To do this, you’ll need to know when any team is having a meeting. In HiCal, 10 | * a meeting is stored as a tuple of integers (start_time, end_time) . These integers represent the number of 11 | * 30-minute blocks past 9:00am. 12 | * 13 | * For example: 14 | * 15 | * (2, 3) # meeting from 10:00 – 10:30 am 16 | * (6, 9) # meeting from 12:00 – 1:30 pm 17 | * 18 | * Write a function condense_meeting_times() that takes an array of meeting time ranges and returns an array of 19 | * condensed ranges. 20 | * 21 | * For example, given: 22 | * 23 | * [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)] 24 | * 25 | * your function would return: 26 | * 27 | * [(0, 1), (3, 8), (9, 12)] 28 | * 29 | * Do not assume the meetings are in order. The meeting times are coming from multiple teams. 30 | * 31 | * In this case the possibilities for start_time and end_time are bounded by the number of 30-minute slots in a 32 | * day. But soon you plan to refactor HiCal to store times as Unix timestamps (which are big numbers). Write 33 | * something that's efficient even when we can't put a nice upper bound on the numbers representing our time 34 | * ranges. 35 | */ 36 | 37 | "use strict"; 38 | 39 | var rangeMerge = require('./rangemerge.js'); 40 | 41 | // Examples from the InterviewCake problem statement 42 | 43 | console.log(rangeMerge.rangemerge([[0, 1], [3, 5], [4, 8], [10, 12], [9, 10]])); 44 | console.log(rangeMerge.rangemerge([[1, 2], [2, 3]])); 45 | console.log(rangeMerge.rangemerge([[1, 5], [2, 3]])); 46 | console.log(rangeMerge.rangemerge([[1, 10], [2, 6], [3, 5], [7, 9]])); 47 | console.log(rangeMerge.rangemerge([[1, 2], [3, 5], [7, 10]])); 48 | -------------------------------------------------------------------------------- /riffle-shuffle/riffleshuffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-13 3 | */ 4 | 5 | /* 6 | * Card Deck Utilities 7 | */ 8 | 9 | "use strict"; 10 | 11 | // Deck constructor 12 | 13 | function Deck(content) { 14 | this.deck = content; 15 | } 16 | 17 | /* 18 | * Clear all the elements of an array quickly 19 | * 20 | * (c.f. http://stackoverflow.com/a/1232046/3893444) 21 | */ 22 | 23 | function fastClear(a) { 24 | while(a.length > 0) { 25 | a.pop(); 26 | } 27 | } 28 | 29 | /* 30 | * Return a random number that is between 'min' and 'max' inclusive 31 | * 32 | * (based on http://stackoverflow.com/a/363692) 33 | */ 34 | 35 | Deck.prototype.getRandom = function getRandom(min, max) { 36 | 37 | // Return a random number between 'min' and 'max' inclusive so long as min is less than max 38 | 39 | if (min < max) { 40 | return Math.floor(Math.random() * (max - min + 1) + min); 41 | } 42 | 43 | // If 'min' and 'max' are the same then just return that value 44 | 45 | if (min === max) { 46 | return Math.floor(min); 47 | } 48 | 49 | // Otherwise, we were passed a condition where 'max' is less than 'min' so throw an error 50 | 51 | return new Error("Function 'getRandom' was passed a minimum value greater than the passed maximum value"); 52 | }; 53 | 54 | /* 55 | * Swap two array elements ('i' and 'j') 56 | */ 57 | 58 | Deck.prototype.swap = function swap(i, j) { 59 | 60 | var size = this.deck.length, 61 | temp; 62 | 63 | // Check the boundaries of the array and throw an error if we're being asked to swap items outside these bounds 64 | 65 | if (i < 0 || i >= size || j < 0 || j >= size) { 66 | return new Error("Function 'swap' was passed array indexes outside of the size of deck array"); 67 | } 68 | 69 | // If we're being asked to swap the same two elements, than there is nothing to do 70 | 71 | if (i === j) { 72 | return null; 73 | } 74 | 75 | // Otherwise, swap the two elements 76 | 77 | temp = this.deck[i]; 78 | this.deck[i] = this.deck[j]; 79 | this.deck[j] = temp; 80 | return null; 81 | 82 | }; 83 | 84 | /* 85 | * Shuffle the deck using the Fisher-Yates in-place shuffle algorithm 86 | * 87 | * (c.f. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 88 | */ 89 | 90 | Deck.prototype.shuffle = function shuffle() { 91 | var i; 92 | if (this.deck.length > 0) { 93 | for (i = (this.deck.length) - 1; i > 0; i -= 1) { 94 | this.swap(this.getRandom(0, i), i); 95 | } 96 | } 97 | return null; 98 | }; 99 | 100 | /* 101 | * Shuffle the deck using a riffle shuffle 102 | */ 103 | 104 | Deck.prototype.riffleShuffle = function riffleShuffle() { 105 | 106 | var sliceAt = this.getRandom(0, this.deck.length), 107 | half1 = this.deck.slice(0, sliceAt), 108 | half2 = this.deck.slice(sliceAt, this.deck.length), 109 | i; 110 | 111 | fastClear(this.deck); 112 | 113 | // Store the two halves of the deck used for the riffle shuffle. This is only needed to fulfill the riffleShuffle 114 | // test from InterviewCake 115 | 116 | this.half1 = half1.slice(0); 117 | this.half2 = half2.slice(0); 118 | 119 | while(half1.length > 0 || half2.length > 0) { 120 | for (i = this.getRandom(0, half1.length); i > 0; i -= 1) { 121 | this.deck.push(half1.shift()); 122 | } 123 | for (i = this.getRandom(0, half2.length); i > 0; i -= 1) { 124 | this.deck.push(half2.shift()); 125 | } 126 | } 127 | 128 | }; 129 | 130 | /* 131 | * Test to see if a deck has been riffle shuffled given two known halves 132 | */ 133 | 134 | Deck.prototype.isRiffleShuffled = function isRiffleShuffled() { 135 | 136 | // If the two halves of the deck used to create the riffle shuffled deck haven't been saved, then we cannot 137 | // complete the riffle shuffle test as defined by the InterviewCake problem. 138 | 139 | if (this.half1 === undefined || this.half2 === undefined) { 140 | return false; 141 | } 142 | 143 | // If the size of the deck is less than the size of the two halves then cards are missing and we have an errors 144 | 145 | if (this.half1.length + this.half2.length !== this.deck.length) { 146 | return new Error("'isRiffleShuffled' detected less cards in the shuffled collection then in the two halves"); 147 | } 148 | 149 | var i = 0, // index pointer for shuffled deck 150 | j = 0, // index pointer for deck half one 151 | k = 0; // index pointer for deck half two 152 | 153 | for (i; i < this.deck.length; i += 1) { 154 | if (this.deck[i] !== this.half1[j] && this.deck[i] !== this.half2[k]) { 155 | return false; 156 | } 157 | if (this.deck[i] === this.half1[j]) { 158 | j += 1; 159 | } else { 160 | k += 1; 161 | } 162 | } 163 | 164 | return true; 165 | 166 | }; 167 | 168 | module.exports = Deck; 169 | -------------------------------------------------------------------------------- /riffle-shuffle/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-13 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Write a function for doing an in-place (http://en.wikipedia.org/wiki/In-place_algorithm) shuffle of an array. 9 | * 10 | * The shuffle must be "uniform," meaning each item in the original array must have the same probability of ending up 11 | * in each spot in the final array. Assume that you have a function getRandom(floor, ceiling) for getting a random 12 | * integer that is >=floor and <=ceiling. 13 | */ 14 | 15 | "use strict"; 16 | 17 | var Deck = require('./riffleshuffle.js'); 18 | 19 | var deck1 = new Deck([ 20 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 21 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 22 | 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 23 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 24 | ]); 25 | 26 | var deck2 = new Deck([ 27 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 28 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29 | 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 30 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 31 | ]); 32 | 33 | console.log("Regular Shuffle Test"); 34 | console.log("Not Shuffled ", deck1); 35 | deck1.shuffle(); 36 | console.log("Shuffled ", deck1); 37 | console.log("Is Riffle Shuffled? ", (deck2.isRiffleShuffled() ? "Yes" : "No")); 38 | 39 | console.log("Riffle Shuffle Test"); 40 | console.log("Not Shuffled ", deck2); 41 | deck2.riffleShuffle(); 42 | console.log("Shuffled ", deck2); 43 | console.log("Is Riffle Shuffled? ", (deck2.isRiffleShuffled() ? "Yes" : "No")); 44 | -------------------------------------------------------------------------------- /second-largest/bst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-07 3 | */ 4 | 5 | /* 6 | * Binary Search Tree (BST) 7 | * 8 | * Recurrsion is heavily used in this implementation of BST. Also, this version only supports unique elements, 9 | * i.e. duplicate elements will not be inserted into the BST but will instead be ignored. 10 | */ 11 | 12 | "use strict"; 13 | 14 | /* traverse 15 | * 16 | * Helper function that recursively traverses a binary search tree using a depth-first approach. For each node 17 | * traversed, a callback function is called passing the node and the result argument. 18 | * 19 | * 'result' must either be a primitive or an object if multiple return values are needed 20 | */ 21 | 22 | function traverse(node, result, callback) { 23 | if (node === undefined) { 24 | return; 25 | } 26 | traverse(node.left, result, callback); 27 | callback(node, result); 28 | traverse(node.right, result, callback); 29 | } 30 | 31 | /* insert 32 | * 33 | * Insert an element into the binary search tree. 34 | */ 35 | 36 | var insert = function insert(data) { 37 | 38 | var current, 39 | node = { 40 | value: data 41 | }; 42 | 43 | // At this time, we don't support adding objects nor arrays--only native types, so abort 44 | 45 | if (typeof data === "object") { 46 | return new Error("BST does not yet support inserting objects or arrays"); 47 | } 48 | 49 | // if root is undefined, then the tree is empty and the new element by default becomes the root. 50 | 51 | if (this.root === undefined) { 52 | this.root = node; 53 | return; 54 | } 55 | 56 | // start at the root of the tree, search down the tree for where the new element should go, then insert it 57 | // at the appropriate point. Return to caller when inserted. 58 | 59 | current = this.root; 60 | while (true) { 61 | if (data < current.value) { 62 | 63 | // Element to be inserted is less than the value at the current node. If the left node of the current 64 | // node is undefined, then the new element becomes the left node. Otherwise, move the current node 65 | // further down the tree to the left. 66 | 67 | if (current.left === undefined) { 68 | current.left = node; 69 | return; 70 | } 71 | current = current.left; 72 | 73 | } else if (data > current.value) { 74 | 75 | // Element to be inserted is greater than the value at the current node. If the right node of the 76 | // current node is undefined, then the new element becomes the right node. Otherwise, move the current 77 | // node further down the tree to the right. 78 | 79 | if (current.right === undefined) { 80 | current.right = node; 81 | return; 82 | } 83 | current = current.right; 84 | 85 | } else { 86 | 87 | // Element to be inserted has the same value at the current node. Since this version of a BST does 88 | // not support duplicate elements, then ignore inserting this element. 89 | 90 | return; 91 | 92 | } 93 | } 94 | 95 | }; 96 | 97 | /* remove 98 | * 99 | * Remove an element from the binary search tree. 100 | */ 101 | 102 | var remove = function remove(data) { 103 | 104 | var foundNode, 105 | parent, 106 | current, 107 | childCount, 108 | replacement, 109 | replacementParent; 110 | 111 | // At this time, we don't support objects nor arrays--only native types, so abort 112 | 113 | if (typeof data === "object") { 114 | return new Error("remove: BST does not yet support removing objects or arrays"); 115 | } 116 | 117 | // Locate the node to be removed. If found, save the node and its parent. Otherwise, exit out. 118 | 119 | foundNode = this.get(data); 120 | if (foundNode.current === undefined) { 121 | return false; 122 | } 123 | 124 | parent = foundNode.parent; 125 | current = foundNode.current; 126 | 127 | // Determine how many children the node has. 128 | 129 | childCount = (current.left === undefined) ? 0 : 1 + (current.right === undefined) ? 0 : 1; 130 | 131 | // Handle the case where the element to be removed is the root element. 132 | 133 | if (current === this.root) { 134 | switch (childCount) { 135 | case 0: // no children so just un-define the root 136 | this.root = undefined; 137 | break; 138 | case 1: // one child so make that child the new root 139 | this.root = (current.right === undefined) ? current.left : current.right; 140 | break; 141 | case 2: // two children so find the node that should be the replacement 142 | replacement = this.root.left; 143 | while (replacement.right !== undefined) { 144 | replacementParent = replacement; 145 | replacement = replacement.right; 146 | } 147 | if (replacementParent === undefined) { 148 | replacement.right = this.root.right; 149 | } else { 150 | replacementParent.right = replacement.left; 151 | replacement.right = this.root.right; 152 | replacement.left = this.root.left; 153 | } 154 | break; 155 | default: 156 | return new Error("remove: Impossible state reached--more than two children of a binary tree"); 157 | } 158 | } else { 159 | switch (childCount) { 160 | case 0: // no children, null out the proper branch of the parent 161 | if (current.value < parent.value) { 162 | parent.left = undefined; 163 | } else { 164 | parent.right = undefined; 165 | } 166 | break; 167 | case 1: // one child, so reassign it to the proper branch of the parent 168 | if (current.value < parent.value) { 169 | parent.left = (current.left === undefined) ? current.right : current.left; 170 | } else { 171 | parent.right = (current.left === undefined) ? current.right : current.left; 172 | } 173 | break; 174 | case 2: 175 | replacement = current.left; 176 | replacementParent = current; 177 | while (replacement.right !== undefined) { 178 | replacementParent = replacement; 179 | replacement = replacement.right; 180 | } 181 | replacementParent.right = replacement.left; 182 | replacement.right = current.right; 183 | replacement.left = current.left; 184 | if (current.value < parent.value) { 185 | parent.left = replacement; 186 | } else { 187 | parent.right = replacement; 188 | } 189 | break; 190 | default: 191 | return new Error("remove: Impossible state reached--more than two children of a binary tree"); 192 | } 193 | } 194 | 195 | return true; 196 | 197 | }; 198 | 199 | /* get 200 | * 201 | * Search the BST to see if it contains an element that matches the passed element. Return the element node if 202 | * it was found as the attribute 'current' and return it's parent too; otherwise, return an empty object. This 203 | * bonus parent is returned because it is required by the 'remove' method. 204 | */ 205 | 206 | var get = function get(data) { 207 | var current = this.root, 208 | parent = null; 209 | while (current) { 210 | if (data === current.value) { 211 | return { 212 | current: current, 213 | parent: parent 214 | }; 215 | } 216 | parent = current; 217 | current = (data < current.value) ? current.left : current.right; 218 | } 219 | return { 220 | current : current, 221 | parent : parent 222 | }; 223 | }; 224 | 225 | /* contains 226 | * 227 | * Test to see if the BST contains an object. Test for this by trying to get the node using the 'get' method. 228 | * If the 'current' returned in the result is undefined, than the node does not exist. 229 | */ 230 | 231 | var contains = function contains(data) { 232 | return (this.get(data).current !== undefined); 233 | }; 234 | 235 | /* size 236 | * 237 | * Walk the BST to determine its size. 238 | */ 239 | 240 | var size = function size() { 241 | var count = 0; 242 | traverse(this.root, count, function counter() { 243 | count += 1; 244 | }); 245 | return count; 246 | }; 247 | 248 | /* largest 249 | * 250 | * Find the largest element of a BST tree directly [O(h) time, O(1) space where h is the height of the tree] 251 | * 252 | * If 'largest' is passed 'node', it searches for the largest relative starting at 'node' 253 | */ 254 | 255 | var largest = function largest(node) { 256 | var current = node || this.root; 257 | while (current !== undefined) { 258 | if (current.right === undefined) { 259 | return current.value; 260 | } 261 | current = current.right; 262 | } 263 | return undefined; 264 | }; 265 | 266 | /* secondLargest 267 | * 268 | * Find the largest element of a BST tree directly [O(h) time, O(1) space where h is the height of the tree] 269 | */ 270 | 271 | var secondLargest = function secondLargest() { 272 | var current = this.root; 273 | while (current !== undefined) { 274 | 275 | // "If we have a left subtree but not a right subtree, then the current node is the largest overall (the 276 | // "rightmost") node. The second largest element must be the largest element in the left subtree. We use our 277 | // get_largest() function above to find the largest in that left subtree!" 278 | 279 | if (current.left !== undefined && current.right === undefined) { 280 | return largest(current.left); 281 | } 282 | 283 | // "If we have a right child, but that right child node doesn't have any children, then the right child 284 | // must be the largest element and our current node must be the second largest element! 285 | 286 | if (current.right !== undefined && current.right.left === undefined && current.right.right === undefined) { 287 | return current.value; 288 | } 289 | 290 | // "Else, we have a right subtree with more than one element, so the largest and second largest are 291 | // somewhere in that subtree. So we step right. 292 | 293 | current = current.right; 294 | } 295 | return undefined; 296 | }; 297 | 298 | /* nthLargest 299 | * 300 | * Walk the BST to in order and return the nthLargest. 301 | * 302 | * This approach works by traversing the full tree then returning the element at the nth to the last position. 303 | * It runs in O(n) time and, if the tree isn't balanced, O(h) space where h is the height of the tree which could 304 | * also be 'n'--so O(n) for both space and time worst case. 305 | * 306 | * Returns 'undefined' if called with 'nth' parameter larger than the size of the BST 307 | */ 308 | 309 | var nthLargest = function nthLargest(nth) { 310 | 311 | var result = { 312 | count: 0 313 | }; 314 | 315 | // Must provide an argument 316 | 317 | if (nth === undefined) { 318 | return new Error("nthLargest: required parameter 'nth' omitted from function call"); 319 | } 320 | 321 | // Since we're looking for the 'nth' largest, adjust nth so that it matches the incremental count as we 322 | // traverse the tree from smallest (0) to largest (size). 323 | 324 | nth = this.size() - nth + 1; 325 | 326 | // Traverse the array and save off the value we find when we reach the correct element 327 | 328 | traverse(this.root, result, function nthLargest(node, result) { 329 | result.count += 1; 330 | if (result.count === nth) { 331 | result.value = node.value; 332 | } 333 | }); 334 | 335 | return result.value; 336 | }; 337 | 338 | /* toArray 339 | * 340 | * Walk the BST inserting the elements into an array. 341 | */ 342 | 343 | var toArray = function toArray() { 344 | var result = []; 345 | traverse(this.root, result, function toArray(node, result) { 346 | result.push(node.value); 347 | }); 348 | return result; 349 | }; 350 | 351 | /* toString 352 | * 353 | * Convert the BST to a string 354 | */ 355 | 356 | var toString = function toString() { 357 | return JSON.stringify(this.root) || {}; 358 | }; 359 | 360 | /* 361 | * Export the BST object 362 | */ 363 | 364 | var BST = function() { 365 | return { 366 | insert: insert, 367 | remove: remove, 368 | get: get, 369 | contains: contains, 370 | size: size, 371 | largest: largest, 372 | secondLargest: secondLargest, 373 | nthLargest: nthLargest, 374 | toArray: toArray, 375 | toString: toString 376 | }; 377 | }; 378 | 379 | module.exports = BST; 380 | -------------------------------------------------------------------------------- /second-largest/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2015-01-07 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Write a function to find the 2nd largest element in a binary search tree 9 | */ 10 | 11 | "use strict"; 12 | 13 | var bst = require('./bst.js'); 14 | 15 | //var myBST = Object.create(bst()); 16 | var myBST = bst(); 17 | 18 | console.log(""); 19 | console.log("Insert An Element"); 20 | console.log("-----------------"); 21 | myBST.insert({}); 22 | console.log(myBST.toString(), myBST.size()); 23 | console.log(myBST.toArray()); 24 | console.log(myBST.largest()); 25 | console.log(myBST.secondLargest()); 26 | console.log(myBST.contains('A')); 27 | console.log(myBST.contains('X')); 28 | 29 | console.log(""); 30 | console.log("Insert An Element"); 31 | console.log("-----------------"); 32 | myBST.insert('C'); 33 | console.log(myBST.toString(), myBST.size()); 34 | console.log(myBST.toArray()); 35 | console.log(myBST.largest()); 36 | console.log(myBST.secondLargest()); 37 | console.log(myBST.contains('C')); 38 | console.log(myBST.contains('X')); 39 | 40 | console.log(""); 41 | console.log("Insert An Element"); 42 | console.log("-----------------"); 43 | myBST.insert('E'); 44 | console.log(myBST.toString(), myBST.size()); 45 | console.log(myBST.toArray()); 46 | console.log(myBST.largest()); 47 | console.log(myBST.secondLargest()); 48 | console.log(myBST.contains('E')); 49 | console.log(myBST.contains('X')); 50 | 51 | console.log(""); 52 | console.log("Insert An Element"); 53 | console.log("-----------------"); 54 | myBST.insert('B'); 55 | console.log(myBST.toString(), myBST.size()); 56 | console.log(myBST.toArray()); 57 | console.log(myBST.largest()); 58 | console.log(myBST.secondLargest()); 59 | console.log(myBST.contains('B')); 60 | console.log(myBST.contains('X')); 61 | 62 | console.log(""); 63 | console.log("Insert An Element"); 64 | console.log("-----------------"); 65 | myBST.insert('D'); 66 | console.log(myBST.toString(), myBST.size()); 67 | console.log(myBST.toArray()); 68 | console.log(myBST.largest()); 69 | console.log(myBST.secondLargest()); 70 | console.log(myBST.contains('D')); 71 | console.log(myBST.contains('X')); 72 | 73 | console.log(""); 74 | console.log("Insert An Element"); 75 | console.log("-----------------"); 76 | myBST.insert('A'); 77 | console.log(myBST.toString(), myBST.size()); 78 | console.log(myBST.toArray()); 79 | console.log(myBST.largest()); 80 | console.log(myBST.secondLargest()); 81 | console.log(myBST.contains('A')); 82 | console.log(myBST.contains('C')); 83 | console.log(myBST.contains('X')); 84 | 85 | 86 | console.log(""); 87 | console.log("Largest using 'nth largest' traversal"); 88 | console.log("-------------------------------------"); 89 | console.log(myBST.nthLargest(1)); 90 | 91 | console.log(""); 92 | console.log("2nd Largest using 'nth largest' traversal"); 93 | console.log("-----------------------------------------"); 94 | console.log(myBST.nthLargest(2)); 95 | 96 | console.log(""); 97 | console.log("3rd Largest using 'nth largest' traversal"); 98 | console.log("-----------------------------------------"); 99 | console.log(myBST.nthLargest(3)); 100 | 101 | 102 | console.log(""); 103 | console.log("Edge Cases"); 104 | console.log("----------"); 105 | console.log(myBST.contains()); 106 | console.log(myBST.contains({})); 107 | console.log(myBST.nthLargest()); 108 | console.log(myBST.nthLargest(500)); 109 | 110 | 111 | console.log(""); 112 | console.log("Insert Empty Object"); 113 | console.log("-------------------"); 114 | myBST.insert({}); 115 | console.log(myBST.toString(), myBST.size()); 116 | console.log(myBST.toArray()); 117 | console.log(myBST.contains({})); 118 | console.log(myBST.contains('X')); 119 | 120 | console.log(""); 121 | console.log("Delete Some Stuff"); 122 | console.log("-----------------"); 123 | console.log(myBST.remove('A')); 124 | console.log(myBST.toString(), myBST.size()); 125 | console.log(myBST.remove('B')); 126 | console.log(myBST.toString(), myBST.size()); 127 | console.log(myBST.remove('X')); 128 | console.log(myBST.toString(), myBST.size()); 129 | console.log(myBST.remove({})); 130 | console.log(myBST.toString(), myBST.size()); 131 | console.log(myBST.toArray()); 132 | 133 | console.log(""); 134 | console.log("How About A Second BST?"); 135 | console.log("-----------------------"); 136 | 137 | var mySecondBST = bst(); 138 | console.log(""); 139 | console.log("Insert An Element"); 140 | console.log("-----------------"); 141 | mySecondBST.insert(1); 142 | console.log(mySecondBST.toString(), mySecondBST.size()); 143 | console.log(mySecondBST.toArray()); 144 | console.log(mySecondBST.contains(1)); 145 | console.log(mySecondBST.contains(2)); 146 | 147 | 148 | console.log(""); 149 | console.log("And Still The First is Okay?"); 150 | console.log("----------------------------"); 151 | console.log(myBST.toString(), myBST.size()); 152 | 153 | console.log(""); 154 | console.log("Test Second Largest Using InterviewCake Example"); 155 | console.log("-----------------------------------------------"); 156 | var myThirdBST = bst(); 157 | myThirdBST.insert(5); 158 | myThirdBST.insert(3); 159 | myThirdBST.insert(1); 160 | myThirdBST.insert(4); 161 | myThirdBST.insert(8); 162 | myThirdBST.insert(7); 163 | myThirdBST.insert(12); 164 | myThirdBST.insert(10); 165 | myThirdBST.insert(9); 166 | myThirdBST.insert(11); 167 | console.log(myThirdBST.toString(), myThirdBST.size()); 168 | console.log(myThirdBST.toArray()); 169 | console.log(myThirdBST.largest()); 170 | console.log(myThirdBST.secondLargest()); 171 | -------------------------------------------------------------------------------- /shuffle/shuffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-13 3 | */ 4 | 5 | /* 6 | * Card Deck Utilities 7 | */ 8 | 9 | "use strict"; 10 | 11 | // Deck constructor 12 | 13 | function Deck(content) { 14 | this.deck = content; 15 | } 16 | 17 | /* 18 | * Return a random number that is between 'min' and 'max' inclusive 19 | * 20 | * (based on http://stackoverflow.com/a/363692) 21 | */ 22 | 23 | Deck.prototype.getRandom = function getRandom(min, max) { 24 | 25 | // Return a random number between 'min' and 'max' inclusive so long as min is less than max 26 | 27 | if (min < max) { 28 | return Math.floor(Math.random() * (max - min + 1) + min); 29 | } 30 | 31 | // If 'min' and 'max' are the same then just return that value 32 | 33 | if (min === max) { 34 | return Math.floor(min); 35 | } 36 | 37 | // Otherwise, we were passed a condition where 'max' is less than 'min' so throw an error 38 | 39 | return new Error("Function 'getRandom' was passed a minimum value greater than the passed maximum value"); 40 | }; 41 | 42 | /* 43 | * Swap two array elements ('i' and 'j') 44 | */ 45 | 46 | Deck.prototype.swap = function swap(i, j) { 47 | 48 | var size = this.deck.length, 49 | temp; 50 | 51 | // Check the boundaries of the array and throw an error if we're being asked to swap items outside these bounds 52 | 53 | if (i < 0 || i >= size || j < 0 || j >= size) { 54 | return new Error("Function 'swap' was passed array indexes outside of the size of deck array"); 55 | } 56 | 57 | // If we're being asked to swap the same two elements, than there is nothing to do 58 | 59 | if (i === j) { 60 | return null; 61 | } 62 | 63 | // Otherwise, swap the two elements 64 | 65 | temp = this.deck[i]; 66 | this.deck[i] = this.deck[j]; 67 | this.deck[j] = temp; 68 | return null; 69 | 70 | }; 71 | 72 | /* 73 | * Shuffle the deck using the Fisher-Yates in-place shuffle algorithm 74 | * 75 | * (c.f. http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 76 | */ 77 | 78 | Deck.prototype.shuffle = function() { 79 | var i; 80 | if (this.deck.length > 0) { 81 | for (i = (this.deck.length) - 1; i > 0; i -= 1) { 82 | this.swap(this.getRandom(0, i), i); 83 | } 84 | } 85 | return null; 86 | }; 87 | 88 | module.exports = Deck; 89 | -------------------------------------------------------------------------------- /shuffle/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-13 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Write a function for doing an in-place (http://en.wikipedia.org/wiki/In-place_algorithm) shuffle of an array. 9 | * 10 | * The shuffle must be "uniform," meaning each item in the original array must have the same probability of ending up 11 | * in each spot in the final array. Assume that you have a function getRandom(floor, ceiling) for getting a random 12 | * integer that is >=floor and <=ceiling. 13 | */ 14 | 15 | "use strict"; 16 | 17 | var Deck = require('./shuffle.js'); 18 | 19 | var deck = new Deck([ 20 | "AS", "1S", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "JS", "QS", "KS", 21 | "AD", "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "JD", "QD", "KD", 22 | "AC", "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "JC", "QC", "KC", 23 | "AH", "1H", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "JH", "QH", "KH" 24 | ]); 25 | 26 | console.log("Not Shuffled ", deck); 27 | 28 | deck.shuffle(); 29 | 30 | console.log("Shuffled ", deck); -------------------------------------------------------------------------------- /string-permutations/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2018-07-07 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * Write a recursive function for generating all permutations of an input string. Return them as a set. 9 | * 10 | * Don't worry about time or space complexity—if we wanted efficiency we'd write an iterative version. 11 | * To start, assume every character in the input string is unique. Your function can have loops—it just 12 | * needs to also be recursive. 13 | * 14 | * NOTE: Solved without using any loops 15 | */ 16 | 17 | function rsp(str, result, prefix, index) { 18 | 19 | // If function was called with a string alone, set up the array which will contain the 20 | // result set and call the function again passing an empty array and an empty string 21 | // prefix. When we regain control return the array to the caller. 22 | if (typeof result === "undefined") { 23 | const result = []; 24 | rsp(str, result, ""); 25 | return result; 26 | } 27 | 28 | // If index is undefined, then se need to call ourself again with a zero index setting 29 | // up a recursive walk through the string. Once we regain control, we return to the 30 | // caller without making another recursive call. 31 | if (typeof index === "undefined") { 32 | rsp(str, result, prefix, 0); 33 | return; 34 | } 35 | 36 | // If the string is zero length, then we have one permutation so we push it onto our 37 | // result set array and return to the caller without making another recursive call. 38 | // This is the ultimate 'base case' as Interview Cake calls it that halts the recursion 39 | // once we have our answer. 40 | if (str.length === 0) { 41 | result.push(prefix); 42 | return; 43 | } 44 | 45 | // This is the meat of the algorithm and it involves two recursive calls to itself. If 46 | // the character we are working on ('index') is less than the length of the string then 47 | // we have work to do. The first thing we do is we call ourselves with a shorter string 48 | // that does not contain the character we are currently working on and, instead, adds that 49 | // character to the 'prefix' which contains the partial left-hand permutation of the 50 | // current permutation we are calculating. The function then does its thing recursively 51 | // to determine all the permutations of the smaller string. Once that is done, then we 52 | // call ourselves again but this time looking at the next character in the string we 53 | // are working on by advancing our index. 54 | if (index < str.length) { 55 | rsp(str.replace(str[index], ''), result, prefix + str[index]); 56 | rsp(str, result, prefix, (index + 1)); 57 | return; 58 | } 59 | 60 | // If we made it here, then we have nothing to do exept return back one layer in the 61 | // recursive call stack. This represents the secondary base case we have to have to 62 | // stop the walk through the characters of a given permutation. 63 | } 64 | 65 | console.log(rsp("cat")); 66 | -------------------------------------------------------------------------------- /test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "P-107", 4 | "parent_id": null, 5 | "position": "1", 6 | "type": "configurable-product", 7 | "sku": "20000-CFG", 8 | "name": "Gramercy Pink", 9 | "description": null, 10 | "short_description": null, 11 | "url_path": "https://store.starshop.com/gramercy-pink-118", 12 | "image": "http://images.starshop.com/catalog/product/3/e/3ef49003c65f43a30cf691ad85e78c0fea6ba857f9f739524618135f21c15682_1280-1280_2.png", 13 | "video_dash_url": null, 14 | "video_hls_url": null, 15 | "video_mp4_url": null, 16 | "price": "$19.00", 17 | "special_price": null, 18 | "brand": null, 19 | "vendor": null, 20 | "qty": "0", 21 | "stock_status": "1", 22 | "created_at": "2015-08-03T16:03:49-05:00", 23 | "updated_at": "2015-03-18 21:07:55", 24 | "media_gallery": [ 25 | { 26 | "position": "2", 27 | "label": "", 28 | "image_url": "http://images.starshop.com/catalog/product/c/c/cc92600ee15a313ed3c1cccf8697c8ac1a77bd42cca3ded7f77a4ab6bc161c4d_1280-1280_2.png" 29 | }, 30 | { 31 | "position": "3", 32 | "label": "", 33 | "image_url": "http://images.starshop.com/catalog/product/5/6/56595f025084764b56fcd1d039a688a004afba996e4f105e04a04f2125478ffb_1280-1280_2.png" 34 | } 35 | ], 36 | "options": [ 37 | { 38 | "attribute": "shade", 39 | "label": "Shade", 40 | "type": "dropdown", 41 | "values": [ 42 | "Gramercy Pink", 43 | "Time Square Tawny", 44 | "Upper West Praline" 45 | ] 46 | } 47 | ], 48 | "items": [ 49 | { 50 | "id": "P-106", 51 | "parent_id": "P-107", 52 | "position": 1, 53 | "type": "simple-product", 54 | "sku": "28", 55 | "name": "Gramercy Pink", 56 | "url_path": "gramercy-pink", 57 | "image": "http://images.starshop.com/catalog/product/3/e/3ef49003c65f43a30cf691ad85e78c0fea6ba857f9f739524618135f21c15682_1280-1280_1.png", 58 | "price": "$19.00", 59 | "special_price": null, 60 | "brand": null, 61 | "vendor": null, 62 | "qty": "133", 63 | "stock_status": "1", 64 | "media_gallery": [ 65 | { 66 | "position": "2", 67 | "label": "", 68 | "image_url": "http://images.starshop.com/catalog/product/c/c/cc92600ee15a313ed3c1cccf8697c8ac1a77bd42cca3ded7f77a4ab6bc161c4d_1280-1280_1.png" 69 | }, 70 | { 71 | "position": "3", 72 | "label": "", 73 | "image_url": "http://images.starshop.com/catalog/product/5/6/56595f025084764b56fcd1d039a688a004afba996e4f105e04a04f2125478ffb_1280-1280_1.png" 74 | } 75 | ], 76 | "option": { 77 | "size": false, 78 | "color": false 79 | } 80 | }, 81 | { 82 | "id": "P-108", 83 | "parent_id": "P-107", 84 | "position": 2, 85 | "type": "simple-product", 86 | "sku": "27", 87 | "name": "Time Square Tawny", 88 | "url_path": "time-square-tawny", 89 | "image": "http://images.starshop.com/catalog/product/3/e/3ef49003c65f43a30cf691ad85e78c0fea6ba857f9f739524618135f21c15682_1280-1280_3.png", 90 | "price": "$19.00", 91 | "special_price": null, 92 | "brand": null, 93 | "vendor": null, 94 | "qty": "126", 95 | "stock_status": "1", 96 | "media_gallery": [ 97 | { 98 | "position": "2", 99 | "label": "", 100 | "image_url": "http://images.starshop.com/catalog/product/c/c/cc92600ee15a313ed3c1cccf8697c8ac1a77bd42cca3ded7f77a4ab6bc161c4d_1280-1280_3.png" 101 | }, 102 | { 103 | "position": "3", 104 | "label": "", 105 | "image_url": "http://images.starshop.com/catalog/product/5/6/56595f025084764b56fcd1d039a688a004afba996e4f105e04a04f2125478ffb_1280-1280_3.png" 106 | } 107 | ], 108 | "option": { 109 | "size": false, 110 | "color": false 111 | } 112 | }, 113 | { 114 | "id": "P-109", 115 | "parent_id": "P-107", 116 | "position": 3, 117 | "type": "simple-product", 118 | "sku": "29", 119 | "name": "Upper West Praline", 120 | "url_path": "upper-west-praline", 121 | "image": "http://images.starshop.com/catalog/product/3/e/3ef49003c65f43a30cf691ad85e78c0fea6ba857f9f739524618135f21c15682_1280-1280_4.png", 122 | "price": "$19.00", 123 | "special_price": null, 124 | "brand": null, 125 | "vendor": null, 126 | "qty": "0", 127 | "stock_status": "0", 128 | "media_gallery": [ 129 | { 130 | "position": "2", 131 | "label": "", 132 | "image_url": "http://images.starshop.com/catalog/product/c/c/cc92600ee15a313ed3c1cccf8697c8ac1a77bd42cca3ded7f77a4ab6bc161c4d_1280-1280_4.png" 133 | }, 134 | { 135 | "position": "3", 136 | "label": "", 137 | "image_url": "http://images.starshop.com/catalog/product/5/6/56595f025084764b56fcd1d039a688a004afba996e4f105e04a04f2125478ffb_1280-1280_4.png" 138 | } 139 | ], 140 | "option": { 141 | "size": false, 142 | "color": false 143 | } 144 | } 145 | ] 146 | } 147 | ] -------------------------------------------------------------------------------- /word-reverse/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-29 3 | */ 4 | 5 | /** 6 | * Problem Statement (from Interview Cake, http://www.interviewcake.com) 7 | * 8 | * You're working on a secret team solving coded transmissions. Your team is scrambling to decipher a recent message, 9 | * worried it's a plot to break into a major European National Cake Vault. The message has been mostly deciphered, but 10 | * all the words are backwards! Your colleagues have handed off the last step to you. Write a function reverse_words() 11 | * that takes a string message and reverses the order of the words in place. 12 | * 13 | * For example: 14 | * 15 | * message = 'find you will pain only go you recordings security the into if' 16 | * reverse_words(message) 17 | * # returns: 'if into the security recordings you go only pain will you find' 18 | * 19 | * When writing your function, assume the message contains only letters and spaces, and all words are separated by one 20 | * space. Strings are immutable in Python, so we can't use Python for in-place operations on a string. We'll use Ruby 21 | * instead. 22 | * 23 | * If you're not comfortable coding in a language with mutable strings, you could split the string into a list of 24 | * characters, do the in-place word reversal on that list, and re-join that list into a string before returning it. But 25 | * keep in mind that this isn't technically "in-place," and the list of characters would cost O(n) additional space! 26 | */ 27 | 28 | "use strict"; 29 | 30 | var wordReverse = require('./wordreverse.js'); 31 | 32 | var message = 'find you will pain only go you recordings security the into if'; 33 | 34 | console.log("Happy Path"); 35 | console.log("----------"); 36 | console.log(message); 37 | console.log(wordReverse.reverse(message)); 38 | 39 | console.log("Test Cases"); 40 | console.log("----------"); 41 | console.log("'' -> ", "'"+wordReverse.reverse("")+"'"); // Empty string 42 | console.log("a -> ", wordReverse.reverse("a")); // One letter 43 | console.log("ab -> ", wordReverse.reverse("ab")); // Even number of letters 44 | console.log("abc -> ", wordReverse.reverse("abc")); // Odd number of letters 45 | console.log("abc def -> ", wordReverse.reverse("abc def")); // Two words with word break in middle 46 | console.log("abcd efg -> ", wordReverse.reverse("abcd efg")); // Two words with word break right of middle 47 | console.log("abc defg -> ", wordReverse.reverse("abc defg")); // Two words with word break left of middle 48 | console.log("abc def ghi -> ", wordReverse.reverse("abc def ghi")); // Three words odd 49 | console.log("abc defg hij -> ", wordReverse.reverse("abc defg hij")); // Three words even 50 | console.log("abcde fg hij -> ", wordReverse.reverse("abcde fg hij")); // Three words even with shift 51 | 52 | -------------------------------------------------------------------------------- /word-reverse/wordreverse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ptdecker on 2014-12-29 3 | */ 4 | 5 | /* 6 | * Reverse Words in a String 7 | */ 8 | 9 | "use strict"; 10 | 11 | /* 12 | * Swap the words in an array 13 | * 14 | * When passed only an array, 'swap' will swap all of the words in the array. 'Swap' can also be called by passing it 15 | * an array plus indices indicating a sub-portion of the array and the 'sub' defined as true. This calling method is 16 | * used by 'swap' itself recursively to correct word order. 17 | */ 18 | 19 | function swap(source, leftIndex, rightIndex, sub) { 20 | 21 | // Set the left and right starting index points if swap was called without them already defined. This will happen 22 | // when swap is called for the first time with just an array. When it is recursively called to correct the letter 23 | // order of words, the indices will be defined and sub should be 'true'. 24 | 25 | leftIndex = (leftIndex !== undefined ? leftIndex : 0); 26 | rightIndex = (rightIndex !== undefined ? rightIndex : source.length - 1); 27 | 28 | var temp, 29 | leftWordStartIndex = leftIndex, 30 | rightWordStartIndex = rightIndex; 31 | 32 | for (leftIndex, rightIndex; leftIndex < rightIndex; leftIndex += 1, rightIndex -= 1) { 33 | 34 | // Swap the two array items pointed to by the left and right indices 35 | 36 | temp = source[leftIndex]; 37 | source[leftIndex] = source[rightIndex]; 38 | source[rightIndex] = temp; 39 | 40 | // console.log((" "+leftIndex).slice(-4), (" "+rightIndex).slice(-4), source.join("")); 41 | 42 | // Working from the left side of the array, if we have come to a word break then backtrack and fix the 43 | // letter ordering of the word by recursively calling swap again with indices set. 44 | 45 | if (source[leftIndex] === ' ') { 46 | swap(source, leftWordStartIndex, leftIndex - 1, true); 47 | leftWordStartIndex = (leftIndex + 1); 48 | } 49 | 50 | // Working from the right side of the array, if we have come to a word break then backtrack and fix the 51 | // letter ordering of the word by recursively calling swap again with indices set. 52 | 53 | if (source[rightIndex] === ' ') { 54 | swap(source, rightIndex + 1, rightWordStartIndex, true); 55 | rightWordStartIndex = (rightIndex - 1); 56 | } 57 | } 58 | 59 | // We may have one remaining word to fix. If so, fix it. But, don't attempt this if this is a recursively 60 | // called version of swap otherwise we will infinitely recurse. 61 | 62 | if (!sub && leftWordStartIndex < rightWordStartIndex) { 63 | swap(source, leftWordStartIndex, rightWordStartIndex, true); 64 | } 65 | } 66 | 67 | module.exports.reverse = function reverse(source) { 68 | var mutableTempArray = source.split(""); 69 | swap(mutableTempArray); 70 | return mutableTempArray.join(""); 71 | }; 72 | --------------------------------------------------------------------------------