diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..f63c6ad27e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# AlgoCasts
+
+Companion repo to a course on Udemy.com
diff --git a/clean/anagrams/index.js b/clean/anagrams/index.js
deleted file mode 100644
index 35cd037015..0000000000
--- a/clean/anagrams/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// --- Directions
-// Check to see if two provided strings are anagrams of eachother.
-// One string is an anagram of another if it uses the same characters
-// in the same quantity. Only consider characters, not spaces
-// or punctuation. Consider capital letters to be the same as lower case
-// --- Examples
-// anagrams('rail safety', 'fairy tales') --> True
-// anagrams('RAIL! SAFETY!', 'fairy tales') --> True
-// anagrams('Hi there', 'Bye there') --> False
-
-function anagrams(stringA, stringB) {}
-
-module.exports = anagrams;
diff --git a/clean/capitalize/index.js b/clean/capitalize/index.js
deleted file mode 100644
index ba9cb5988a..0000000000
--- a/clean/capitalize/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// --- Directions
-// Write a function that accepts a string. The function should
-// capitalize the first letter of each word in the string then
-// return the capitalized string.
-// --- Examples
-// capitalize('a short sentence') --> 'A Short Sentence'
-// capitalize('a lazy fox') --> 'A Lazy Fox'
-// capitalize('look, it is working!') --> 'Look, It Is Working!'
-
-function capitalize(str) {}
-
-module.exports = capitalize;
diff --git a/clean/chunk/index.js b/clean/chunk/index.js
deleted file mode 100644
index 8e7a800cfc..0000000000
--- a/clean/chunk/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// --- Directions
-// Given an array and chunk size, divide the array into many subarrays
-// where each subarray is of length size
-// --- Examples
-// chunk([1, 2, 3, 4], 2) --> [[ 1, 2], [3, 4]]
-// chunk([1, 2, 3, 4, 5], 2) --> [[ 1, 2], [3, 4], [5]]
-// chunk([1, 2, 3, 4, 5, 6, 7, 8], 3) --> [[ 1, 2, 3], [4, 5, 6], [7, 8]]
-// chunk([1, 2, 3, 4, 5], 4) --> [[ 1, 2, 3, 4], [5]]
-// chunk([1, 2, 3, 4, 5], 10) --> [[ 1, 2, 3, 4, 5]]
-
-function chunk(array, size) {}
-
-module.exports = chunk;
diff --git a/clean/fib/index.js b/clean/fib/index.js
deleted file mode 100644
index 9efe84faf2..0000000000
--- a/clean/fib/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// --- Directions
-// Print out the n-th entry in the fibonacci series.
-// The fibonacci series is an ordering of numbers where
-// each number is the sum of the preceeding two.
-// For example, the sequence
-// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
-// forms the first ten entries of the fibonacci series.
-// Example:
-// fib(4) === 3
-
-function fib(n) {}
-
-module.exports = fib;
diff --git a/clean/linkedlist/index.js b/clean/linkedlist/index.js
deleted file mode 100644
index c75cc872b0..0000000000
--- a/clean/linkedlist/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// --- Directions
-// Implement classes Node and Linked Lists
-// See 'directions' document
-
-class Node {}
-
-class LinkedList {}
-
-module.exports = { Node, LinkedList };
diff --git a/clean/matrix/index.js b/clean/matrix/index.js
deleted file mode 100644
index 13bdc31f82..0000000000
--- a/clean/matrix/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// --- Directions
-// Write a function that accepts an integer N
-// and returns a NxN spiral matrix.
-// --- Examples
-// matrix(2)
-// [[1, 2],
-// [4, 3]]
-// matrix(3)
-// [[1, 2, 3],
-// [8, 9, 4],
-// [7, 6, 5]]
-// matrix(4)
-// [[1, 2, 3, 4],
-// [12, 13, 14, 5],
-// [11, 16, 15, 6],
-// [10, 9, 8, 7]]
-
-function matrix(n) {}
-
-module.exports = matrix;
diff --git a/clean/maxchar/index.js b/clean/maxchar/index.js
deleted file mode 100644
index 12f32ba998..0000000000
--- a/clean/maxchar/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// --- Directions
-// Given a string, return the character that is most
-// commonly used in the string.
-// --- Examples
-// maxChar("abcccccccd") === "c"
-// maxChar("apple 1231111") === "1"
-
-function maxChar(str) {}
-
-module.exports = maxChar;
diff --git a/clean/pyramid/index.js b/clean/pyramid/index.js
deleted file mode 100644
index 627bab341d..0000000000
--- a/clean/pyramid/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// --- Directions
-// Write a function that accepts a positive number N.
-// The function should console log a pyramid shape
-// with N levels using the # character. Make sure the
-// pyramid has spaces on both the left *and* right hand sides
-// --- Examples
-// pyramid(1)
-// '#'
-// pyramid(2)
-// ' # '
-// '###'
-// pyramid(3)
-// ' # '
-// ' ### '
-// '#####'
-
-function pyramid(n) {}
-
-module.exports = pyramid;
diff --git a/clean/qfroms/index.js b/clean/qfroms/index.js
deleted file mode 100644
index 93af154f18..0000000000
--- a/clean/qfroms/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// --- Directions
-// Implement a Queue datastructure using two stacks.
-// *Do not* create an array inside of the 'Queue' class.
-// Queue should implement the methods 'add', 'remove', and 'peek'.
-// For a reminder on what each method does, look back
-// at the Queue exercise.
-// --- Examples
-// const q = new Queue();
-// q.add(1);
-// q.add(2);
-// q.peek(); // returns 1
-// q.remove(); // returns 1
-// q.remove(); // returns 2
-
-const Stack = require('./stack');
-
-class Queue {}
-
-module.exports = Queue;
diff --git a/clean/reversestring/index.js b/clean/reversestring/index.js
deleted file mode 100644
index fc909037d4..0000000000
--- a/clean/reversestring/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// --- Directions
-// Given a string, return a new string with the reversed
-// order of characters
-// --- Examples
-// reverse('apple') === 'leppa'
-// reverse('hello') === 'olleh'
-// reverse('Greetings!') === '!sgniteerG'
-
-function reverse(str) {}
-
-module.exports = reverse;
diff --git a/clean/steps/index.js b/clean/steps/index.js
deleted file mode 100644
index 400adc9ac0..0000000000
--- a/clean/steps/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// --- Directions
-// Write a function that accepts a positive number N.
-// The function should console log a step shape
-// with N levels using the # character. Make sure the
-// step has spaces on the right hand side!
-// --- Examples
-// steps(2)
-// '# '
-// '##'
-// steps(3)
-// '# '
-// '## '
-// '###'
-// steps(4)
-// '# '
-// '## '
-// '### '
-// '####'
-
-function steps(n) {}
-
-module.exports = steps;
diff --git a/clean/vowels/index.js b/clean/vowels/index.js
deleted file mode 100644
index ad3b67b250..0000000000
--- a/clean/vowels/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// --- Directions
-// Write a function that returns the number of vowels
-// used in a string. Vowels are the characters 'a', 'e'
-// 'i', 'o', and 'u'.
-// --- Examples
-// vowels('Hi There!') --> 3
-// vowels('Why do you ask?') --> 4
-// vowels('Why?') --> 0
-
-function vowels(str) {}
-
-module.exports = vowels;
diff --git a/completed_exercises/anagrams/index.js b/completed_exercises/anagrams/index.js
new file mode 100644
index 0000000000..1a1b598447
--- /dev/null
+++ b/completed_exercises/anagrams/index.js
@@ -0,0 +1,51 @@
+// --- Directions
+// Check to see if two provided strings are anagrams of eachother.
+// One string is an anagram of another if it uses the same characters
+// in the same quantity. Only consider characters, not spaces
+// or punctuation. Consider capital letters to be the same as lower case
+// --- Examples
+// anagrams('rail safety', 'fairy tales') --> True
+// anagrams('RAIL! SAFETY!', 'fairy tales') --> True
+// anagrams('Hi there', 'Bye there') --> False
+
+function anagrams(stringA, stringB) {
+ return cleanString(stringA) === cleanString(stringB);
+}
+
+function cleanString(str) {
+ return str
+ .replace(/[^\w]/g, '')
+ .toLowerCase()
+ .split('')
+ .sort()
+ .join('');
+}
+
+module.exports = anagrams;
+
+// function anagrams(stringA, stringB) {
+// const aCharMap = buildCharMap(stringA);
+// const bCharMap = buildCharMap(stringB);
+//
+// if (Object.keys(aCharMap).length !== Object.keys(bCharMap).length) {
+// return false;
+// }
+//
+// for (let char in aCharMap) {
+// if (aCharMap[char] !== bCharMap[char]) {
+// return false;
+// }
+// }
+//
+// return true;
+// }
+//
+// function buildCharMap(str) {
+// const charMap = {};
+//
+// for (let char of str.replace(/[^\w]/g, '').toLowerCase()) {
+// charMap[char] = charMap[char] + 1 || 1;
+// }
+//
+// return charMap;
+// }
diff --git a/clean/anagrams/test.js b/completed_exercises/anagrams/test.js
similarity index 100%
rename from clean/anagrams/test.js
rename to completed_exercises/anagrams/test.js
diff --git a/completed_exercises/bst/index.js b/completed_exercises/bst/index.js
new file mode 100644
index 0000000000..673fd7d080
--- /dev/null
+++ b/completed_exercises/bst/index.js
@@ -0,0 +1,49 @@
+// --- Directions
+// 1) Implement the Node class to create
+// a binary search tree. The constructor
+// should initialize values 'data', 'left',
+// and 'right'.
+// 2) Implement the 'insert' method for the
+// Node class. Insert should accept an argument
+// 'data', then create an insert a new node
+// at the appropriate location in the tree.
+// 3) Implement the 'contains' method for the Node
+// class. Contains should accept a 'data' argument
+// and return the Node in the tree with the same value.
+// If the value isn't in the tree return null.
+
+class Node {
+ constructor(data) {
+ this.data = data;
+ this.left = null;
+ this.right = null;
+ }
+
+ insert(data) {
+ if (data < this.data && this.left) {
+ this.left.insert(data);
+ } else if (data < this.data) {
+ this.left = new Node(data);
+ } else if (data > this.data && this.right) {
+ this.right.insert(data);
+ } else if (data > this.data) {
+ this.right = new Node(data);
+ }
+ }
+
+ contains(data) {
+ if (this.data === data) {
+ return this;
+ }
+
+ if (this.data < data && this.right) {
+ return this.right.contains(data);
+ } else if (this.data > data && this.left) {
+ return this.left.contains(data);
+ }
+
+ return null;
+ }
+}
+
+module.exports = Node;
diff --git a/completed_exercises/bst/test.js b/completed_exercises/bst/test.js
new file mode 100644
index 0000000000..331ae22520
--- /dev/null
+++ b/completed_exercises/bst/test.js
@@ -0,0 +1,41 @@
+const Node = require('./index');
+
+test('Node is a constructor', () => {
+ expect(typeof Node.prototype.constructor).toEqual('function');
+});
+
+test('Node can insert correctly', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(17);
+
+ expect(node.left.data).toEqual(5);
+ expect(node.right.data).toEqual(15);
+ expect(node.right.right.data).toEqual(17);
+});
+
+test('Contains returns node with the same data', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(20);
+ node.insert(0);
+ node.insert(-5);
+ node.insert(3);
+
+ const three = node.left.left.right;
+ expect(node.contains(3)).toEqual(three);
+});
+
+test('Contains returns null if value not found', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(20);
+ node.insert(0);
+ node.insert(-5);
+ node.insert(3);
+
+ expect(node.contains(9999)).toEqual(null);
+});
diff --git a/completed_exercises/capitalize/index.js b/completed_exercises/capitalize/index.js
new file mode 100644
index 0000000000..353993f21e
--- /dev/null
+++ b/completed_exercises/capitalize/index.js
@@ -0,0 +1,34 @@
+// --- Directions
+// Write a function that accepts a string. The function should
+// capitalize the first letter of each word in the string then
+// return the capitalized string.
+// --- Examples
+// capitalize('a short sentence') --> 'A Short Sentence'
+// capitalize('a lazy fox') --> 'A Lazy Fox'
+// capitalize('look, it is working!') --> 'Look, It Is Working!'
+
+function capitalize(str) {
+ let result = str[0].toUpperCase();
+
+ for (let i = 1; i < str.length; i++) {
+ if (str[i - 1] === ' ') {
+ result += str[i].toUpperCase();
+ } else {
+ result += str[i];
+ }
+ }
+
+ return result;
+}
+
+module.exports = capitalize;
+
+// function capitalize(str) {
+// const words = [];
+//
+// for (let word of str.split(' ')) {
+// words.push(word[0].toUpperCase() + word.slice(1));
+// }
+//
+// return words.join(' ');
+// }
diff --git a/clean/capitalize/test.js b/completed_exercises/capitalize/test.js
similarity index 100%
rename from clean/capitalize/test.js
rename to completed_exercises/capitalize/test.js
diff --git a/completed_exercises/chunk/index.js b/completed_exercises/chunk/index.js
new file mode 100644
index 0000000000..02c9bbb3e2
--- /dev/null
+++ b/completed_exercises/chunk/index.js
@@ -0,0 +1,39 @@
+// --- Directions
+// Given an array and chunk size, divide the array into many subarrays
+// where each subarray is of length size
+// --- Examples
+// chunk([1, 2, 3, 4], 2) --> [[ 1, 2], [3, 4]]
+// chunk([1, 2, 3, 4, 5], 2) --> [[ 1, 2], [3, 4], [5]]
+// chunk([1, 2, 3, 4, 5, 6, 7, 8], 3) --> [[ 1, 2, 3], [4, 5, 6], [7, 8]]
+// chunk([1, 2, 3, 4, 5], 4) --> [[ 1, 2, 3, 4], [5]]
+// chunk([1, 2, 3, 4, 5], 10) --> [[ 1, 2, 3, 4, 5]]
+
+function chunk(array, size) {
+ const chunked = [];
+ let index = 0;
+
+ while (index < array.length) {
+ chunked.push(array.slice(index, index + size));
+ index += size;
+ }
+
+ return chunked;
+}
+
+module.exports = chunk;
+
+// function chunk(array, size) {
+// const chunked = [];
+//
+// for (let element of array) {
+// const last = chunked[chunked.length - 1];
+//
+// if (!last || last.length === size) {
+// chunked.push([element]);
+// } else {
+// last.push(element);
+// }
+// }
+//
+// return chunked;
+// }
diff --git a/clean/chunk/test.js b/completed_exercises/chunk/test.js
similarity index 100%
rename from clean/chunk/test.js
rename to completed_exercises/chunk/test.js
diff --git a/clean/circular/index.js b/completed_exercises/circular/index.js
similarity index 58%
rename from clean/circular/index.js
rename to completed_exercises/circular/index.js
index 6a1a9a9cd9..508b09fde8 100644
--- a/clean/circular/index.js
+++ b/completed_exercises/circular/index.js
@@ -12,6 +12,20 @@
// c.next = b;
// circular(l) // true
-function circular(list) {}
+function circular(list) {
+ let slow = list.getFirst();
+ let fast = list.getFirst();
+
+ while (fast.next && fast.next.next) {
+ slow = slow.next;
+ fast = fast.next.next;
+
+ if (slow === fast) {
+ return true;
+ }
+ }
+
+ return false;
+}
module.exports = circular;
diff --git a/clean/circular/linkedlist.js b/completed_exercises/circular/linkedlist.js
similarity index 100%
rename from clean/circular/linkedlist.js
rename to completed_exercises/circular/linkedlist.js
diff --git a/clean/circular/test.js b/completed_exercises/circular/test.js
similarity index 95%
rename from clean/circular/test.js
rename to completed_exercises/circular/test.js
index 7cb50b2e40..99c21fe02b 100644
--- a/clean/circular/test.js
+++ b/completed_exercises/circular/test.js
@@ -3,7 +3,7 @@ const L = require('./linkedlist');
const List = L.LinkedList;
const Node = L.Node;
-test('circular', () => {
+test('circular function is defined', () => {
expect(typeof circular).toEqual('function');
});
diff --git a/completed_exercises/events/example.html b/completed_exercises/events/example.html
new file mode 100644
index 0000000000..c1a8303f3b
--- /dev/null
+++ b/completed_exercises/events/example.html
@@ -0,0 +1,19 @@
+
+
+
+
+
Click the button
+
+
+
diff --git a/completed_exercises/events/index.js b/completed_exercises/events/index.js
new file mode 100644
index 0000000000..ee5a600bde
--- /dev/null
+++ b/completed_exercises/events/index.js
@@ -0,0 +1,37 @@
+// --- Directions
+// Create an 'eventing' library out of the
+// Events class. The Events class should
+// have methods 'on', 'trigger', and 'off'.
+
+class Events {
+ constructor() {
+ this.events = {};
+ }
+
+ // Register an event handler
+ on(eventName, callback) {
+ if (this.events[eventName]) {
+ this.events[eventName].push(callback);
+ } else {
+ this.events[eventName] = [callback];
+ }
+ }
+
+ // Trigger all callbacks associated
+ // with a given eventName
+ trigger(eventName) {
+ if (this.events[eventName]) {
+ for (let cb of this.events[eventName]) {
+ cb();
+ }
+ }
+ }
+
+ // Remove all event handlers associated
+ // with the given eventName
+ off(eventName) {
+ delete this.events[eventName];
+ }
+}
+
+module.exports = Events;
diff --git a/completed_exercises/events/test.js b/completed_exercises/events/test.js
new file mode 100644
index 0000000000..e015819d09
--- /dev/null
+++ b/completed_exercises/events/test.js
@@ -0,0 +1,77 @@
+const Events = require('./index');
+
+test('Events can be registered then triggered', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(1);
+});
+
+test('Multiple events can be registered then triggered', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.on('click', cb2);
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(1);
+ expect(cb2.mock.calls.length).toBe(1);
+});
+
+test('Events can be triggered multiple times', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.on('click', cb2);
+ events.trigger('click');
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(3);
+ expect(cb2.mock.calls.length).toBe(2);
+});
+
+test('Events can have different names', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.on('hover', cb2);
+ events.trigger('click');
+ events.trigger('hover');
+
+ expect(cb1.mock.calls.length).toBe(2);
+ expect(cb2.mock.calls.length).toBe(1);
+});
+
+test('Events can be toggled off', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('hover', cb2);
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.off('click');
+ events.trigger('click');
+
+ events.trigger('hover');
+
+ expect(cb1.mock.calls.length).toBe(1);
+ expect(cb2.mock.calls.length).toBe(1);
+});
diff --git a/completed_exercises/fib/index.js b/completed_exercises/fib/index.js
new file mode 100644
index 0000000000..51e2ae17a7
--- /dev/null
+++ b/completed_exercises/fib/index.js
@@ -0,0 +1,48 @@
+// --- Directions
+// Print out the n-th entry in the fibonacci series.
+// The fibonacci series is an ordering of numbers where
+// each number is the sum of the preceeding two.
+// For example, the sequence
+// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
+// forms the first ten entries of the fibonacci series.
+// Example:
+// fib(4) === 3
+
+function memoize(fn) {
+ const cache = {};
+ return function(...args) {
+ if (cache[args]) {
+ return cache[args];
+ }
+
+ const result = fn.apply(this, args);
+ cache[args] = result;
+
+ return result;
+ };
+}
+
+function slowFib(n) {
+ if (n < 2) {
+ return n;
+ }
+
+ return fib(n - 1) + fib(n - 2);
+}
+
+const fib = memoize(slowFib);
+
+module.exports = fib;
+
+// function fib(n) {
+// const result = [0, 1];
+//
+// for (let i = 2; i <= n; i++) {
+// const a = result[i - 1];
+// const b = result[i - 2];
+//
+// result.push(a + b);
+// }
+//
+// return result[n];
+// }
diff --git a/clean/fib/test.js b/completed_exercises/fib/test.js
similarity index 100%
rename from clean/fib/test.js
rename to completed_exercises/fib/test.js
diff --git a/clean/fizzbuzz/index.js b/completed_exercises/fizzbuzz/index.js
similarity index 51%
rename from clean/fizzbuzz/index.js
rename to completed_exercises/fizzbuzz/index.js
index 0aeab8873b..8fe64c47b5 100644
--- a/clean/fizzbuzz/index.js
+++ b/completed_exercises/fizzbuzz/index.js
@@ -12,6 +12,20 @@
// 4
// buzz
-function fizzBuzz(n) {}
+function fizzBuzz(n) {
+ for (let i = 1; i <= n; i++) {
+ // Is the number a multiple of 3 and 5?
+ if (i % 3 === 0 && i % 5 === 0) {
+ console.log('fizzbuzz');
+ } else if (i % 3 === 0) {
+ // Is the number a multiple of 3?
+ console.log('fizz');
+ } else if (i % 5 === 0) {
+ console.log('buzz');
+ } else {
+ console.log(i);
+ }
+ }
+}
module.exports = fizzBuzz;
diff --git a/clean/fizzbuzz/test.js b/completed_exercises/fizzbuzz/test.js
similarity index 95%
rename from clean/fizzbuzz/test.js
rename to completed_exercises/fizzbuzz/test.js
index b25dc82c71..fa5108e1ec 100644
--- a/clean/fizzbuzz/test.js
+++ b/completed_exercises/fizzbuzz/test.js
@@ -31,7 +31,7 @@ test('Calling fizzbuzz with 15 prints out the correct values', () => {
});
beforeEach(() => {
- jest.spyOn(console, 'log');
+ jest.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
diff --git a/completed_exercises/fromlast/index.js b/completed_exercises/fromlast/index.js
new file mode 100644
index 0000000000..935ca228d2
--- /dev/null
+++ b/completed_exercises/fromlast/index.js
@@ -0,0 +1,31 @@
+// --- Directions
+// Given a linked list and integer n, return the element n
+// spaces from the last node in the list. Do not call the
+// 'size' method of the linked list. Assume that n will always
+// be less than the length of the list.
+// --- Examples
+// const list = new List();
+// list.insertLast('a');
+// list.insertLast('b');
+// list.insertLast('c');
+// list.insertLast('d');
+// fromLast(list, 2).data // 'b'
+
+function fromLast(list, n) {
+ let slow = list.getFirst();
+ let fast = list.getFirst();
+
+ while (n > 0) {
+ fast = fast.next;
+ n--;
+ }
+
+ while (fast.next) {
+ slow = slow.next;
+ fast = fast.next;
+ }
+
+ return slow;
+}
+
+module.exports = fromLast;
diff --git a/completed_exercises/fromlast/linkedlist.js b/completed_exercises/fromlast/linkedlist.js
new file mode 100644
index 0000000000..2029b3b71e
--- /dev/null
+++ b/completed_exercises/fromlast/linkedlist.js
@@ -0,0 +1,156 @@
+class Node {
+ constructor(data, next = null) {
+ this.data = data;
+ this.next = next;
+ }
+}
+
+class LinkedList {
+ constructor() {
+ this.head = null;
+ }
+
+ insertFirst(data) {
+ this.head = new Node(data, this.head);
+ }
+
+ size() {
+ let counter = 0;
+ let node = this.head;
+
+ while (node) {
+ counter++;
+ node = node.next;
+ }
+
+ return counter;
+ }
+
+ getFirst() {
+ return this.head;
+ }
+
+ getLast() {
+ if (!this.head) {
+ return null;
+ }
+
+ let node = this.head;
+ while (node) {
+ if (!node.next) {
+ return node;
+ }
+ node = node.next;
+ }
+ }
+
+ clear() {
+ this.head = null;
+ }
+
+ removeFirst() {
+ if (!this.head) {
+ return;
+ }
+
+ this.head = this.head.next;
+ }
+
+ removeLast() {
+ if (!this.head) {
+ return;
+ }
+
+ if (!this.head.next) {
+ this.head = null;
+ return;
+ }
+
+ let previous = this.head;
+ let node = this.head.next;
+ while (node.next) {
+ previous = node;
+ node = node.next;
+ }
+ previous.next = null;
+ }
+
+ insertLast(data) {
+ const last = this.getLast();
+
+ if (last) {
+ // There are some existing nodes in our chain
+ last.next = new Node(data);
+ } else {
+ // The chain is empty!
+ this.head = new Node(data);
+ }
+ }
+
+ getAt(index) {
+ let counter = 0;
+ let node = this.head;
+ while (node) {
+ if (counter === index) {
+ return node;
+ }
+
+ counter++;
+ node = node.next;
+ }
+ return null;
+ }
+
+ removeAt(index) {
+ if (!this.head) {
+ return;
+ }
+
+ if (index === 0) {
+ this.head = this.head.next;
+ return;
+ }
+
+ const previous = this.getAt(index - 1);
+ if (!previous || !previous.next) {
+ return;
+ }
+ previous.next = previous.next.next;
+ }
+
+ insertAt(data, index) {
+ if (!this.head) {
+ this.head = new Node(data);
+ return;
+ }
+
+ if (index === 0) {
+ this.head = new Node(data, this.head);
+ return;
+ }
+
+ const previous = this.getAt(index - 1) || this.getLast();
+ const node = new Node(data, previous.next);
+ previous.next = node;
+ }
+
+ forEach(fn) {
+ let node = this.head;
+ let counter = 0;
+ while (node) {
+ fn(node, counter);
+ node = node.next;
+ counter++;
+ }
+ }
+
+ *[Symbol.iterator]() {
+ let node = this.head;
+ while (node) {
+ yield node;
+ node = node.next;
+ }
+ }
+}
+
+module.exports = { Node, LinkedList };
diff --git a/completed_exercises/fromlast/test.js b/completed_exercises/fromlast/test.js
new file mode 100644
index 0000000000..e4684d5258
--- /dev/null
+++ b/completed_exercises/fromlast/test.js
@@ -0,0 +1,20 @@
+const fromLast = require('./index');
+const L = require('./linkedlist');
+const List = L.LinkedList;
+const Node = L.Node;
+
+test('fromLast is a function', () => {
+ expect(typeof fromLast).toEqual('function');
+});
+
+test('fromLast returns the node n elements from the end', () => {
+ const l = new List();
+
+ l.insertLast('a');
+ l.insertLast('b');
+ l.insertLast('c');
+ l.insertLast('d');
+ l.insertLast('e');
+
+ expect(fromLast(l, 3).data).toEqual('b');
+});
diff --git a/completed_exercises/levelwidth/index.js b/completed_exercises/levelwidth/index.js
new file mode 100644
index 0000000000..349c0c4904
--- /dev/null
+++ b/completed_exercises/levelwidth/index.js
@@ -0,0 +1,33 @@
+// --- Directions
+// Given the root node of a tree, return
+// an array where each element is the width
+// of the tree at each level.
+// --- Example
+// Given:
+// 0
+// / | \
+// 1 2 3
+// | |
+// 4 5
+// Answer: [1, 3, 2]
+
+function levelWidth(root) {
+ const arr = [root, 's'];
+ const counters = [0];
+
+ while (arr.length > 1) {
+ const node = arr.shift();
+
+ if (node === 's') {
+ counters.push(0);
+ arr.push('s');
+ } else {
+ arr.push(...node.children);
+ counters[counters.length - 1]++;
+ }
+ }
+
+ return counters;
+}
+
+module.exports = levelWidth;
diff --git a/completed_exercises/levelwidth/node.js b/completed_exercises/levelwidth/node.js
new file mode 100644
index 0000000000..5238e8f597
--- /dev/null
+++ b/completed_exercises/levelwidth/node.js
@@ -0,0 +1,10 @@
+module.exports = class Node {
+ constructor(data) {
+ this.data = data;
+ this.children = [];
+ }
+
+ add(data) {
+ this.children.push(new Node(data));
+ }
+};
diff --git a/completed_exercises/levelwidth/test.js b/completed_exercises/levelwidth/test.js
new file mode 100644
index 0000000000..35d20c2a91
--- /dev/null
+++ b/completed_exercises/levelwidth/test.js
@@ -0,0 +1,27 @@
+const Node = require('./node');
+const levelWidth = require('./index');
+
+test('levelWidth is a function', () => {
+ expect(typeof levelWidth).toEqual('function');
+});
+
+test('levelWidth returns number of nodes at widest point', () => {
+ const root = new Node(0);
+ root.add(1);
+ root.add(2);
+ root.add(3);
+ root.children[0].add(4);
+ root.children[2].add(5);
+
+ expect(levelWidth(root)).toEqual([1, 3, 2]);
+});
+
+test('levelWidth returns number of nodes at widest point', () => {
+ const root = new Node(0);
+ root.add(1);
+ root.children[0].add(2);
+ root.children[0].add(3);
+ root.children[0].children[0].add(4);
+
+ expect(levelWidth(root)).toEqual([1, 1, 2, 1]);
+});
diff --git a/clean/linkedlist/directions.html b/completed_exercises/linkedlist/directions.html
similarity index 97%
rename from clean/linkedlist/directions.html
rename to completed_exercises/linkedlist/directions.html
index ecb118d11c..3bae946819 100644
--- a/clean/linkedlist/directions.html
+++ b/completed_exercises/linkedlist/directions.html
@@ -21,7 +21,7 @@
Node Class API
-
Node.constructor
+
constructor
(Data, Node)
Node
@@ -34,10 +34,10 @@
Node Class API
- const n = new Node('There');
+ const n = new Node('Hi');
n.data // 'Hi'
n.next // null
- const n2 = new Node('Hi', n);
+ const n2 = new Node('There', n);
n.next // returns n
@@ -324,7 +324,8 @@
LinkedList Class API
-
- Calls the provided function with every node of the chain
+ Calls the provided function with every node of the chain and the index
+ of the node.
@@ -335,7 +336,7 @@
LinkedList Class API
list.insertLast(3);
list.insertLast(4);
- list.forEach(node => {
+ list.forEach((node, index) => {
node.data += 10;
});
list.getAt(0); // Returns node with data '11'
diff --git a/completed_exercises/linkedlist/index.js b/completed_exercises/linkedlist/index.js
new file mode 100644
index 0000000000..5bd5efbdad
--- /dev/null
+++ b/completed_exercises/linkedlist/index.js
@@ -0,0 +1,160 @@
+// --- Directions
+// Implement classes Node and Linked Lists
+// See 'directions' document
+
+class Node {
+ constructor(data, next = null) {
+ this.data = data;
+ this.next = next;
+ }
+}
+
+class LinkedList {
+ constructor() {
+ this.head = null;
+ }
+
+ insertFirst(data) {
+ this.head = new Node(data, this.head);
+ }
+
+ size() {
+ let counter = 0;
+ let node = this.head;
+
+ while (node) {
+ counter++;
+ node = node.next;
+ }
+
+ return counter;
+ }
+
+ getFirst() {
+ return this.head;
+ }
+
+ getLast() {
+ if (!this.head) {
+ return null;
+ }
+
+ let node = this.head;
+ while (node) {
+ if (!node.next) {
+ return node;
+ }
+ node = node.next;
+ }
+ }
+
+ clear() {
+ this.head = null;
+ }
+
+ removeFirst() {
+ if (!this.head) {
+ return;
+ }
+
+ this.head = this.head.next;
+ }
+
+ removeLast() {
+ if (!this.head) {
+ return;
+ }
+
+ if (!this.head.next) {
+ this.head = null;
+ return;
+ }
+
+ let previous = this.head;
+ let node = this.head.next;
+ while (node.next) {
+ previous = node;
+ node = node.next;
+ }
+ previous.next = null;
+ }
+
+ insertLast(data) {
+ const last = this.getLast();
+
+ if (last) {
+ // There are some existing nodes in our chain
+ last.next = new Node(data);
+ } else {
+ // The chain is empty!
+ this.head = new Node(data);
+ }
+ }
+
+ getAt(index) {
+ let counter = 0;
+ let node = this.head;
+ while (node) {
+ if (counter === index) {
+ return node;
+ }
+
+ counter++;
+ node = node.next;
+ }
+ return null;
+ }
+
+ removeAt(index) {
+ if (!this.head) {
+ return;
+ }
+
+ if (index === 0) {
+ this.head = this.head.next;
+ return;
+ }
+
+ const previous = this.getAt(index - 1);
+ if (!previous || !previous.next) {
+ return;
+ }
+ previous.next = previous.next.next;
+ }
+
+ insertAt(data, index) {
+ if (!this.head) {
+ this.head = new Node(data);
+ return;
+ }
+
+ if (index === 0) {
+ this.head = new Node(data, this.head);
+ return;
+ }
+
+ const previous = this.getAt(index - 1) || this.getLast();
+ const node = new Node(data, previous.next);
+ previous.next = node;
+ }
+
+ forEach(fn) {
+ let node = this.head;
+ let counter = 0;
+ while (node) {
+ fn(node, counter);
+ node = node.next;
+ counter++;
+ }
+ }
+
+ *[Symbol.iterator]() {
+ let node = this.head;
+ while (node) {
+ yield node;
+ node = node.next;
+ }
+ }
+}
+
+module.exports = { Node, LinkedList };
diff --git a/clean/linkedlist/test.js b/completed_exercises/linkedlist/test.js
similarity index 93%
rename from clean/linkedlist/test.js
rename to completed_exercises/linkedlist/test.js
index 18bdb64ea6..f4de0462e0 100644
--- a/clean/linkedlist/test.js
+++ b/completed_exercises/linkedlist/test.js
@@ -10,7 +10,7 @@ test('Node is a class', () => {
expect(typeof Node.prototype.constructor).toEqual('function');
});
-describe.skip('A Node', () => {
+describe('A Node', () => {
test('has properties "data" and "next"', () => {
const node = new Node('a', 'b');
expect(node.data).toEqual('a');
@@ -18,7 +18,7 @@ describe.skip('A Node', () => {
});
});
-describe.skip('Insert First', () => {
+describe('Insert First', () => {
test('appends a node to the start of the list', () => {
const l = new List();
l.insertFirst(1);
@@ -28,7 +28,7 @@ describe.skip('Insert First', () => {
});
});
-describe.skip('Size', () => {
+describe('Size', () => {
test('returns the number of items in the linked list', () => {
const l = new List();
expect(l.size()).toEqual(0);
@@ -40,7 +40,7 @@ describe.skip('Size', () => {
});
});
-describe.skip('GetFirst', () => {
+describe('GetFirst', () => {
test('returns the first element', () => {
const l = new List();
l.insertFirst(1);
@@ -50,7 +50,7 @@ describe.skip('GetFirst', () => {
});
});
-describe.skip('GetLast', () => {
+describe('GetLast', () => {
test('returns the last element', () => {
const l = new List();
l.insertFirst(2);
@@ -60,7 +60,7 @@ describe.skip('GetLast', () => {
});
});
-describe.skip('Clear', () => {
+describe('Clear', () => {
test('empties out the list', () => {
const l = new List();
expect(l.size()).toEqual(0);
@@ -74,7 +74,7 @@ describe.skip('Clear', () => {
});
});
-describe.skip('RemoveFirst', () => {
+describe('RemoveFirst', () => {
test('removes the first node when the list has a size of one', () => {
const l = new List();
l.insertFirst('a');
@@ -97,7 +97,7 @@ describe.skip('RemoveFirst', () => {
});
});
-describe.skip('RemoveLast', () => {
+describe('RemoveLast', () => {
test('RemoveLast removes the last node when list is empty', () => {
const l = new List();
expect(() => {
@@ -135,7 +135,7 @@ describe.skip('RemoveLast', () => {
});
});
-describe.skip('InsertLast', () => {
+describe('InsertLast', () => {
test('adds to the end of the list', () => {
const l = new List();
l.insertFirst('a');
@@ -147,7 +147,7 @@ describe.skip('InsertLast', () => {
});
});
-describe.skip('GetAt', () => {
+describe('GetAt', () => {
test('returns the node at given index', () => {
const l = new List();
expect(l.getAt(10)).toEqual(null);
@@ -164,7 +164,7 @@ describe.skip('GetAt', () => {
});
});
-describe.skip('RemoveAt', () => {
+describe('RemoveAt', () => {
test('removeAt doesnt crash on an empty list', () => {
const l = new List();
expect(() => {
@@ -217,7 +217,7 @@ describe.skip('RemoveAt', () => {
});
});
-describe.skip('InsertAt', () => {
+describe('InsertAt', () => {
test('inserts a new node with data at the 0 index when the list is empty', () => {
const l = new List();
l.insertAt('hi', 0);
@@ -272,7 +272,7 @@ describe.skip('InsertAt', () => {
});
});
-describe.skip('ForEach', () => {
+describe('ForEach', () => {
test('applies a transform to each node', () => {
const l = new List();
@@ -292,7 +292,7 @@ describe.skip('ForEach', () => {
});
});
-describe.skip('for...of loops', () => {
+describe('for...of loops', () => {
test('works with the linked list', () => {
const l = new List();
diff --git a/completed_exercises/matrix/index.js b/completed_exercises/matrix/index.js
new file mode 100644
index 0000000000..f1d0651e48
--- /dev/null
+++ b/completed_exercises/matrix/index.js
@@ -0,0 +1,63 @@
+// --- Directions
+// Write a function that accepts an integer N
+// and returns a NxN spiral matrix.
+// --- Examples
+// matrix(2)
+// [[undefined, undefined],
+// [undefined, undefined]]
+// matrix(3)
+// [[1, 2, 3],
+// [8, 9, 4],
+// [7, 6, 5]]
+// matrix(4)
+// [[1, 2, 3, 4],
+// [12, 13, 14, 5],
+// [11, 16, 15, 6],
+// [10, 9, 8, 7]]
+
+function matrix(n) {
+ const results = [];
+
+ for (let i = 0; i < n; i++) {
+ results.push([]);
+ }
+
+ let counter = 1;
+ let startColumn = 0;
+ let endColumn = n - 1;
+ let startRow = 0;
+ let endRow = n - 1;
+ while (startColumn <= endColumn && startRow <= endRow) {
+ // Top row
+ for (let i = startColumn; i <= endColumn; i++) {
+ results[startRow][i] = counter;
+ counter++;
+ }
+ startRow++;
+
+ // Right column
+ for (let i = startRow; i <= endRow; i++) {
+ results[i][endColumn] = counter;
+ counter++;
+ }
+ endColumn--;
+
+ // Bottom row
+ for (let i = endColumn; i >= startColumn; i--) {
+ results[endRow][i] = counter;
+ counter++;
+ }
+ endRow--;
+
+ // start column
+ for (let i = endRow; i >= startRow; i--) {
+ results[i][startColumn] = counter;
+ counter++;
+ }
+ startColumn++;
+ }
+
+ return results;
+}
+
+module.exports = matrix;
diff --git a/clean/matrix/test.js b/completed_exercises/matrix/test.js
similarity index 100%
rename from clean/matrix/test.js
rename to completed_exercises/matrix/test.js
diff --git a/completed_exercises/maxchar/index.js b/completed_exercises/maxchar/index.js
new file mode 100644
index 0000000000..4ddbeac8ac
--- /dev/null
+++ b/completed_exercises/maxchar/index.js
@@ -0,0 +1,31 @@
+// --- Directions
+// Given a string, return the character that is most
+// commonly used in the string.
+// --- Examples
+// maxChar("abcccccccd") === "c"
+// maxChar("apple 1231111") === "1"
+
+function maxChar(str) {
+ const charMap = {};
+ let max = 0;
+ let maxChar = '';
+
+ for (let char of str) {
+ if (charMap[char]) {
+ charMap[char]++;
+ } else {
+ charMap[char] = 1;
+ }
+ }
+
+ for (let char in charMap) {
+ if (charMap[char] > max) {
+ max = charMap[char];
+ maxChar = char;
+ }
+ }
+
+ return maxChar;
+}
+
+module.exports = maxChar;
diff --git a/clean/maxchar/test.js b/completed_exercises/maxchar/test.js
similarity index 100%
rename from clean/maxchar/test.js
rename to completed_exercises/maxchar/test.js
diff --git a/clean/midpoint/index.js b/completed_exercises/midpoint/index.js
similarity index 71%
rename from clean/midpoint/index.js
rename to completed_exercises/midpoint/index.js
index 8bf6894ac1..10503dc688 100644
--- a/clean/midpoint/index.js
+++ b/completed_exercises/midpoint/index.js
@@ -12,6 +12,16 @@
// l.insertLast('c')
// midpoint(l); // returns { data: 'b' }
-function midpoint(list) {}
+function midpoint(list) {
+ let slow = list.getFirst();
+ let fast = list.getFirst();
+
+ while (fast.next && fast.next.next) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+
+ return slow;
+}
module.exports = midpoint;
diff --git a/clean/midpoint/linkedlist.js b/completed_exercises/midpoint/linkedlist.js
similarity index 100%
rename from clean/midpoint/linkedlist.js
rename to completed_exercises/midpoint/linkedlist.js
diff --git a/clean/midpoint/test.js b/completed_exercises/midpoint/test.js
similarity index 100%
rename from clean/midpoint/test.js
rename to completed_exercises/midpoint/test.js
diff --git a/clean/package.json b/completed_exercises/package.json
similarity index 100%
rename from clean/package.json
rename to completed_exercises/package.json
diff --git a/clean/palindrome/index.js b/completed_exercises/palindrome/index.js
similarity index 58%
rename from clean/palindrome/index.js
rename to completed_exercises/palindrome/index.js
index 58115ed243..43bffe83cb 100644
--- a/clean/palindrome/index.js
+++ b/completed_exercises/palindrome/index.js
@@ -7,6 +7,19 @@
// palindrome("abba") === true
// palindrome("abcdefg") === false
-function palindrome(str) {}
+function palindrome(str) {
+ return str.split('').every((char, i) => {
+ return char === str[str.length - i - 1];
+ });
+}
module.exports = palindrome;
+
+// function palindrome(str) {
+// const reversed = str
+// .split('')
+// .reverse()
+// .join('');
+//
+// return str === reversed;
+// }
diff --git a/clean/palindrome/test.js b/completed_exercises/palindrome/test.js
similarity index 100%
rename from clean/palindrome/test.js
rename to completed_exercises/palindrome/test.js
diff --git a/completed_exercises/pyramid/index.js b/completed_exercises/pyramid/index.js
new file mode 100644
index 0000000000..003b0a094c
--- /dev/null
+++ b/completed_exercises/pyramid/index.js
@@ -0,0 +1,55 @@
+// --- Directions
+// Write a function that accepts a positive number N.
+// The function should console log a pyramid shape
+// with N levels using the # character. Make sure the
+// pyramid has spaces on both the left *and* right hand sides
+// --- Examples
+// pyramid(1)
+// '#'
+// pyramid(2)
+// ' # '
+// '###'
+// pyramid(3)
+// ' # '
+// ' ### '
+// '#####'
+
+function pyramid(n, row = 0, level = '') {
+ if (row === n) {
+ return;
+ }
+
+ if (level.length === 2 * n - 1) {
+ console.log(level);
+ return pyramid(n, row + 1);
+ }
+
+ const midpoint = Math.floor((2 * n - 1) / 2);
+ let add;
+ if (midpoint - row <= level.length && midpoint + row >= level.length) {
+ add = '#';
+ } else {
+ add = ' ';
+ }
+ pyramid(n, row, level + add);
+}
+
+module.exports = pyramid;
+//
+// function pyramid(n) {
+// const midpoint = Math.floor((2 * n - 1) / 2);
+//
+// for (let row = 0; row < n; row++) {
+// let level = '';
+//
+// for (let column = 0; column < 2 * n - 1; column++) {
+// if (midpoint - row <= column && midpoint + row >= column) {
+// level += '#';
+// } else {
+// level += ' ';
+// }
+// }
+//
+// console.log(level);
+// }
+// }
diff --git a/clean/pyramid/test.js b/completed_exercises/pyramid/test.js
similarity index 100%
rename from clean/pyramid/test.js
rename to completed_exercises/pyramid/test.js
diff --git a/completed_exercises/qfroms/index.js b/completed_exercises/qfroms/index.js
new file mode 100644
index 0000000000..829c30e97d
--- /dev/null
+++ b/completed_exercises/qfroms/index.js
@@ -0,0 +1,56 @@
+// --- Directions
+// Implement a Queue datastructure using two stacks.
+// *Do not* create an array inside of the 'Queue' class.
+// Queue should implement the methods 'add', 'remove', and 'peek'.
+// For a reminder on what each method does, look back
+// at the Queue exercise.
+// --- Examples
+// const q = new Queue();
+// q.add(1);
+// q.add(2);
+// q.peek(); // returns 1
+// q.remove(); // returns 1
+// q.remove(); // returns 2
+
+const Stack = require('./stack');
+
+class Queue {
+ constructor() {
+ this.first = new Stack();
+ this.second = new Stack();
+ }
+
+ add(record) {
+ this.first.push(record);
+ }
+
+ remove() {
+ while (this.first.peek()) {
+ this.second.push(this.first.pop());
+ }
+
+ const record = this.second.pop();
+
+ while (this.second.peek()) {
+ this.first.push(this.second.pop());
+ }
+
+ return record;
+ }
+
+ peek() {
+ while (this.first.peek()) {
+ this.second.push(this.first.pop());
+ }
+
+ const record = this.second.peek();
+
+ while (this.second.peek()) {
+ this.first.push(this.second.pop());
+ }
+
+ return record;
+ }
+}
+
+module.exports = Queue;
diff --git a/clean/qfroms/stack.js b/completed_exercises/qfroms/stack.js
similarity index 100%
rename from clean/qfroms/stack.js
rename to completed_exercises/qfroms/stack.js
diff --git a/clean/qfroms/test.js b/completed_exercises/qfroms/test.js
similarity index 100%
rename from clean/qfroms/test.js
rename to completed_exercises/qfroms/test.js
diff --git a/clean/queue/index.js b/completed_exercises/queue/index.js
similarity index 66%
rename from clean/queue/index.js
rename to completed_exercises/queue/index.js
index e11ff2a13e..e475d6e58b 100644
--- a/clean/queue/index.js
+++ b/completed_exercises/queue/index.js
@@ -8,6 +8,18 @@
// q.add(1);
// q.remove(); // returns 1;
-class Queue {}
+class Queue {
+ constructor() {
+ this.data = [];
+ }
+
+ add(record) {
+ this.data.unshift(record);
+ }
+
+ remove() {
+ return this.data.pop();
+ }
+}
module.exports = Queue;
diff --git a/clean/queue/test.js b/completed_exercises/queue/test.js
similarity index 100%
rename from clean/queue/test.js
rename to completed_exercises/queue/test.js
diff --git a/clean/reverseint/index.js b/completed_exercises/reverseint/index.js
similarity index 65%
rename from clean/reverseint/index.js
rename to completed_exercises/reverseint/index.js
index 5db3bfdee1..c2e6a0dd39 100644
--- a/clean/reverseint/index.js
+++ b/completed_exercises/reverseint/index.js
@@ -8,6 +8,14 @@
// reverseInt(-15) === -51
// reverseInt(-90) === -9
-function reverseInt(n) {}
+function reverseInt(n) {
+ const reversed = n
+ .toString()
+ .split('')
+ .reverse()
+ .join('');
+
+ return parseInt(reversed) * Math.sign(n);
+}
module.exports = reverseInt;
diff --git a/clean/reverseint/test.js b/completed_exercises/reverseint/test.js
similarity index 100%
rename from clean/reverseint/test.js
rename to completed_exercises/reverseint/test.js
diff --git a/completed_exercises/reversestring/index.js b/completed_exercises/reversestring/index.js
new file mode 100644
index 0000000000..5f036a9757
--- /dev/null
+++ b/completed_exercises/reversestring/index.js
@@ -0,0 +1,30 @@
+// --- Directions
+// Given a string, return a new string with the reversed
+// order of characters
+// --- Examples
+// reverse('apple') === 'leppa'
+// reverse('hello') === 'olleh'
+// reverse('Greetings!') === '!sgniteerG'
+
+function reverse(str) {
+ return str.split('').reduce((rev, char) => char + rev, '');
+}
+
+module.exports = reverse;
+
+// function reverse(str) {
+// return str
+// .split('')
+// .reverse()
+// .join('');
+// }
+
+// function reverse(str) {
+// let reversed = '';
+//
+// for (let character of str) {
+// reversed = character + reversed;
+// }
+//
+// return reversed;
+// }
diff --git a/clean/reversestring/test.js b/completed_exercises/reversestring/test.js
similarity index 100%
rename from clean/reversestring/test.js
rename to completed_exercises/reversestring/test.js
diff --git a/completed_exercises/sorting/index.js b/completed_exercises/sorting/index.js
new file mode 100644
index 0000000000..5ad4201343
--- /dev/null
+++ b/completed_exercises/sorting/index.js
@@ -0,0 +1,66 @@
+// --- Directions
+// Implement bubbleSort, selectionSort, and mergeSort
+
+function bubbleSort(arr) {
+ // Implement bubblesort
+ for (let i = 0; i < arr.length; i++) {
+ for (let j = 0; j < (arr.length - i - 1); j++) {
+ if (arr[j] > arr[j+1]) {
+ const lesser = arr[j+1];
+ arr[j+1] = arr[j];
+ arr[j] = lesser;
+ }
+ }
+ }
+
+ // return the sorted array
+ return arr;
+}
+
+function selectionSort(arr) {
+ for (let i = 0; i < arr.length; i++) {
+ let indexOfMin = i;
+
+ for (let j = i+1; j {
+ test('sorts an array', () => {
+ expect(bubbleSort(getArray())).toEqual(getSortedArray());
+ });
+});
+
+describe('Selection sort', () => {
+ test('sorts an array', () => {
+ expect(selectionSort(getArray())).toEqual(getSortedArray());
+ });
+});
+
+describe('Merge sort', () => {
+ test('merge function can join together two sorted arrays', () => {
+ const left = [1, 10];
+ const right = [2, 8, 12];
+
+ expect(merge(left, right)).toEqual([1,2,8,10,12]);
+ });
+
+ test('sorts an array', () => {
+ expect(mergeSort(getArray())).toEqual(getSortedArray());
+ });
+});
diff --git a/clean/stack/index.js b/completed_exercises/stack/index.js
similarity index 62%
rename from clean/stack/index.js
rename to completed_exercises/stack/index.js
index 5c7c4608ca..7e95441874 100644
--- a/clean/stack/index.js
+++ b/completed_exercises/stack/index.js
@@ -10,6 +10,22 @@
// s.pop(); // returns 2
// s.pop(); // returns 1
-class Stack {}
+class Stack {
+ constructor() {
+ this.data = [];
+ }
+
+ push(record) {
+ this.data.push(record);
+ }
+
+ pop() {
+ return this.data.pop();
+ }
+
+ peek() {
+ return this.data[this.data.length - 1];
+ }
+}
module.exports = Stack;
diff --git a/clean/stack/test.js b/completed_exercises/stack/test.js
similarity index 100%
rename from clean/stack/test.js
rename to completed_exercises/stack/test.js
diff --git a/completed_exercises/steps/index.js b/completed_exercises/steps/index.js
new file mode 100644
index 0000000000..fd73326955
--- /dev/null
+++ b/completed_exercises/steps/index.js
@@ -0,0 +1,50 @@
+// --- Directions
+// Write a function that accepts a positive number N.
+// The function should console log a step shape
+// with N levels using the # character. Make sure the
+// step has spaces on the right hand side!
+// --- Examples
+// steps(2)
+// '# '
+// '##'
+// steps(3)
+// '# '
+// '## '
+// '###'
+// steps(4)
+// '# '
+// '## '
+// '### '
+// '####'
+
+function steps(n, row = 0, stair = '') {
+ if (n === row) {
+ return;
+ }
+
+ if (n === stair.length) {
+ console.log(stair);
+ return steps(n, row + 1);
+ }
+
+ const add = stair.length <= row ? '#' : ' ';
+ steps(n, row, stair + add);
+}
+
+module.exports = steps;
+
+// function steps(n) {
+// for (let row = 0; row < n; row++) {
+// let stair = '';
+//
+// for (let column = 0; column < n; column++) {
+// if (column <= row) {
+// stair += '#';
+// } else {
+// stair += ' ';
+// }
+// }
+//
+// console.log(stair);
+// }
+// }
diff --git a/clean/steps/test.js b/completed_exercises/steps/test.js
similarity index 100%
rename from clean/steps/test.js
rename to completed_exercises/steps/test.js
diff --git a/completed_exercises/tree/index.js b/completed_exercises/tree/index.js
new file mode 100644
index 0000000000..3bfb5365a2
--- /dev/null
+++ b/completed_exercises/tree/index.js
@@ -0,0 +1,56 @@
+// --- Directions
+// 1) Create a node class. The constructor
+// should accept an argument that gets assigned
+// to the data property and initialize an
+// empty array for storing children. The node
+// class should have methods 'add' and 'remove'.
+// 2) Create a tree class. The tree constructor
+// should initialize a 'root' property to null.
+// 3) Implement 'traverseBF' and 'traverseDF'
+// on the tree class. Each method should accept a
+// function that gets called with each element in the tree
+
+class Node {
+ constructor(data) {
+ this.data = data;
+ this.children = [];
+ }
+
+ add(data) {
+ this.children.push(new Node(data));
+ }
+
+ remove(data) {
+ this.children = this.children.filter(node => {
+ return node.data !== data;
+ });
+ }
+}
+
+class Tree {
+ constructor() {
+ this.root = null;
+ }
+
+ traverseBF(fn) {
+ const arr = [this.root];
+ while (arr.length) {
+ const node = arr.shift();
+
+ arr.push(...node.children);
+ fn(node);
+ }
+ }
+
+ traverseDF(fn) {
+ const arr = [this.root];
+ while (arr.length) {
+ const node = arr.shift();
+
+ arr.unshift(...node.children);
+ fn(node);
+ }
+ }
+}
+
+module.exports = { Tree, Node };
diff --git a/completed_exercises/tree/test.js b/completed_exercises/tree/test.js
new file mode 100644
index 0000000000..69dab5da36
--- /dev/null
+++ b/completed_exercises/tree/test.js
@@ -0,0 +1,67 @@
+const T = require('./index');
+const Node = T.Node;
+const Tree = T.Tree;
+
+describe('Node', () => {
+ test('Node is a constructor', () => {
+ expect(typeof Node.prototype.constructor).toEqual('function');
+ });
+
+ test('Node has a data and children properties', () => {
+ const n = new Node('a');
+ expect(n.data).toEqual('a');
+ expect(n.children.length).toEqual(0);
+ });
+
+ test('Node can add children', () => {
+ const n = new Node('a');
+ n.add('b');
+ expect(n.children.length).toEqual(1);
+ expect(n.children[0].children).toEqual([]);
+ });
+
+ test('Node can remove children', () => {
+ const n = new Node('a');
+ n.add('b');
+ expect(n.children.length).toEqual(1);
+ n.remove('b');
+ expect(n.children.length).toEqual(0);
+ });
+});
+
+describe('Tree', () => {
+ test('starts empty', () => {
+ const t = new Tree();
+ expect(t.root).toEqual(null);
+ });
+
+ test('Can traverse bf', () => {
+ const letters = [];
+ const t = new Tree();
+ t.root = new Node('a');
+ t.root.add('b');
+ t.root.add('c');
+ t.root.children[0].add('d');
+
+ t.traverseBF(node => {
+ letters.push(node.data);
+ });
+
+ expect(letters).toEqual(['a', 'b', 'c', 'd']);
+ });
+
+ test('Can traverse DF', () => {
+ const letters = [];
+ const t = new Tree();
+ t.root = new Node('a');
+ t.root.add('b');
+ t.root.add('d');
+ t.root.children[0].add('c');
+
+ t.traverseDF(node => {
+ letters.push(node.data);
+ });
+
+ expect(letters).toEqual(['a', 'b', 'c', 'd']);
+ });
+});
diff --git a/completed_exercises/validate/index.js b/completed_exercises/validate/index.js
new file mode 100644
index 0000000000..9f4da37258
--- /dev/null
+++ b/completed_exercises/validate/index.js
@@ -0,0 +1,28 @@
+// --- Directions
+// Given a node, validate the binary search tree,
+// ensuring that every node's left hand child is
+// less than the parent node's value, and that
+// every node's right hand child is greater than
+// the parent
+
+function validate(node, min = null, max = null) {
+ if (max !== null && node.data > max) {
+ return false;
+ }
+
+ if (min !== null && node.data < min) {
+ return false;
+ }
+
+ if (node.left && !validate(node.left, min, node.data)) {
+ return false;
+ }
+
+ if (node.right && !validate(node.right, node.data, max)) {
+ return false;
+ }
+
+ return true;
+}
+
+module.exports = validate;
diff --git a/completed_exercises/validate/node.js b/completed_exercises/validate/node.js
new file mode 100644
index 0000000000..d19e208a59
--- /dev/null
+++ b/completed_exercises/validate/node.js
@@ -0,0 +1,21 @@
+class Node {
+ constructor(data) {
+ this.data = data;
+ this.left = null;
+ this.right = null;
+ }
+
+ insert(data) {
+ if (data < this.data && this.left) {
+ this.left.insert(data);
+ } else if (data < this.data) {
+ this.left = new Node(data);
+ } else if (data > this.data && this.right) {
+ this.right.insert(data);
+ } else if (data > this.data) {
+ this.right = new Node(data);
+ }
+ }
+}
+
+module.exports = Node;
diff --git a/completed_exercises/validate/test.js b/completed_exercises/validate/test.js
new file mode 100644
index 0000000000..6ea4dc3f08
--- /dev/null
+++ b/completed_exercises/validate/test.js
@@ -0,0 +1,23 @@
+const Node = require('./node');
+const validate = require('./index');
+
+test('Validate recognizes a valid BST', () => {
+ const n = new Node(10);
+ n.insert(5);
+ n.insert(15);
+ n.insert(0);
+ n.insert(20);
+
+ expect(validate(n)).toEqual(true);
+});
+
+test('Validate recognizes an invalid BST', () => {
+ const n = new Node(10);
+ n.insert(5);
+ n.insert(15);
+ n.insert(0);
+ n.insert(20);
+ n.left.left.right = new Node(999);
+
+ expect(validate(n)).toEqual(false);
+});
diff --git a/completed_exercises/vowels/index.js b/completed_exercises/vowels/index.js
new file mode 100644
index 0000000000..379ba6c60a
--- /dev/null
+++ b/completed_exercises/vowels/index.js
@@ -0,0 +1,28 @@
+// --- Directions
+// Write a function that returns the number of vowels
+// used in a string. Vowels are the characters 'a', 'e'
+// 'i', 'o', and 'u'.
+// --- Examples
+// vowels('Hi There!') --> 3
+// vowels('Why do you ask?') --> 4
+// vowels('Why?') --> 0
+
+function vowels(str) {
+ const matches = str.match(/[aeiou]/gi);
+ return matches ? matches.length : 0;
+}
+
+module.exports = vowels;
+
+// function vowels(str) {
+// let count = 0;
+// const checker = ['a', 'e', 'i', 'o', 'u'];
+//
+// for (let char of str.toLowerCase()) {
+// if (checker.includes(char)) {
+// count++;
+// }
+// }
+//
+// return count;
+// }
diff --git a/clean/vowels/test.js b/completed_exercises/vowels/test.js
similarity index 78%
rename from clean/vowels/test.js
rename to completed_exercises/vowels/test.js
index 6283592c0b..0bbf43055e 100644
--- a/clean/vowels/test.js
+++ b/completed_exercises/vowels/test.js
@@ -8,10 +8,6 @@ test('returns the number of vowels used', () => {
expect(vowels('aeiou')).toEqual(5);
});
-test('returns the number of vowels used when they are capitalized', () => {
- expect(vowels('AEIOU')).toEqual(5);
-});
-
test('returns the number of vowels used', () => {
expect(vowels('abcdefghijklmnopqrstuvwxyz')).toEqual(5);
});
diff --git a/clean/weave/index.js b/completed_exercises/weave/index.js
similarity index 76%
rename from clean/weave/index.js
rename to completed_exercises/weave/index.js
index 3d49df9694..0818cb2385 100644
--- a/clean/weave/index.js
+++ b/completed_exercises/weave/index.js
@@ -24,6 +24,20 @@
const Queue = require('./queue');
-function weave(sourceOne, sourceTwo) {}
+function weave(sourceOne, sourceTwo) {
+ const q = new Queue();
+
+ while (sourceOne.peek() || sourceTwo.peek()) {
+ if (sourceOne.peek()) {
+ q.add(sourceOne.remove());
+ }
+
+ if (sourceTwo.peek()) {
+ q.add(sourceTwo.remove());
+ }
+ }
+
+ return q;
+}
module.exports = weave;
diff --git a/clean/weave/queue.js b/completed_exercises/weave/queue.js
similarity index 85%
rename from clean/weave/queue.js
rename to completed_exercises/weave/queue.js
index 451eb90223..20044c2ef3 100644
--- a/clean/weave/queue.js
+++ b/completed_exercises/weave/queue.js
@@ -16,6 +16,10 @@ class Queue {
remove() {
return this.data.pop();
}
+
+ peek() {
+ return this.data[this.data.length - 1];
+ }
}
module.exports = Queue;
diff --git a/clean/weave/test.js b/completed_exercises/weave/test.js
similarity index 97%
rename from clean/weave/test.js
rename to completed_exercises/weave/test.js
index c3658c8605..f173360f29 100644
--- a/clean/weave/test.js
+++ b/completed_exercises/weave/test.js
@@ -1,5 +1,5 @@
const weave = require('./index');
-const Queue = require('./Queue');
+const Queue = require('./queue');
test('queues have a peek function', () => {
const q = new Queue();
diff --git a/diagrams/08/diagrams.xml b/diagrams/08/diagrams.xml
index cb27416bf0..c461e6e345 100644
--- a/diagrams/08/diagrams.xml
+++ b/diagrams/08/diagrams.xml
@@ -1 +1 @@
-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
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/diagrams/09/diagrams.xml b/diagrams/09/diagrams.xml
index 4d1986566a..657f03c27f 100644
--- a/diagrams/09/diagrams.xml
+++ b/diagrams/09/diagrams.xml
@@ -1 +1 @@
-UzV2zq1wL0osyPDNT0nNUTV2VTV2LsrPL4GwciucU3NyVI0MMlNUjV1UjYwMgFjVyA2HrCFY1qAgsSg1rwSLBiADYTaQg2Y1AA==
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/diagrams/10/diagrams.xml b/diagrams/10/diagrams.xml
new file mode 100644
index 0000000000..0273620baa
--- /dev/null
+++ b/diagrams/10/diagrams.xml
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/exercises/anagrams/index.js b/exercises/anagrams/index.js
index 1a1b598447..35cd037015 100644
--- a/exercises/anagrams/index.js
+++ b/exercises/anagrams/index.js
@@ -8,44 +8,6 @@
// anagrams('RAIL! SAFETY!', 'fairy tales') --> True
// anagrams('Hi there', 'Bye there') --> False
-function anagrams(stringA, stringB) {
- return cleanString(stringA) === cleanString(stringB);
-}
-
-function cleanString(str) {
- return str
- .replace(/[^\w]/g, '')
- .toLowerCase()
- .split('')
- .sort()
- .join('');
-}
+function anagrams(stringA, stringB) {}
module.exports = anagrams;
-
-// function anagrams(stringA, stringB) {
-// const aCharMap = buildCharMap(stringA);
-// const bCharMap = buildCharMap(stringB);
-//
-// if (Object.keys(aCharMap).length !== Object.keys(bCharMap).length) {
-// return false;
-// }
-//
-// for (let char in aCharMap) {
-// if (aCharMap[char] !== bCharMap[char]) {
-// return false;
-// }
-// }
-//
-// return true;
-// }
-//
-// function buildCharMap(str) {
-// const charMap = {};
-//
-// for (let char of str.replace(/[^\w]/g, '').toLowerCase()) {
-// charMap[char] = charMap[char] + 1 || 1;
-// }
-//
-// return charMap;
-// }
diff --git a/exercises/anagrams/test.js b/exercises/anagrams/test.js
index 60f117b1fa..c6451a712e 100644
--- a/exercises/anagrams/test.js
+++ b/exercises/anagrams/test.js
@@ -25,3 +25,7 @@ test('"A tree, a life, a bench" is not an anagram of "A tree, a fence, a yard"',
anagrams('A tree, a life, a bench', 'A tree, a fence, a yard')
).toBeFalsy();
});
+
+test('"RAIL! SAFETY!" is an anagram of "fairy tales"', () => {
+ expect(anagrams("RAIL! SAFETY!", "fairy tales")).toBeTruthy();
+});
diff --git a/exercises/bst/index.js b/exercises/bst/index.js
new file mode 100644
index 0000000000..25aabd638d
--- /dev/null
+++ b/exercises/bst/index.js
@@ -0,0 +1,16 @@
+// --- Directions
+// 1) Implement the Node class to create
+// a binary search tree. The constructor
+// should initialize values 'data', 'left',
+// and 'right'.
+// 2) Implement the 'insert' method for the
+// Node class. Insert should accept an argument
+// 'data', then create an insert a new node
+// at the appropriate location in the tree.
+// 3) Implement the 'contains' method for the Node
+// class. Contains should accept a 'data' argument
+// and return the Node in the tree with the same value.
+
+class Node {}
+
+module.exports = Node;
diff --git a/exercises/bst/test.js b/exercises/bst/test.js
new file mode 100644
index 0000000000..331ae22520
--- /dev/null
+++ b/exercises/bst/test.js
@@ -0,0 +1,41 @@
+const Node = require('./index');
+
+test('Node is a constructor', () => {
+ expect(typeof Node.prototype.constructor).toEqual('function');
+});
+
+test('Node can insert correctly', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(17);
+
+ expect(node.left.data).toEqual(5);
+ expect(node.right.data).toEqual(15);
+ expect(node.right.right.data).toEqual(17);
+});
+
+test('Contains returns node with the same data', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(20);
+ node.insert(0);
+ node.insert(-5);
+ node.insert(3);
+
+ const three = node.left.left.right;
+ expect(node.contains(3)).toEqual(three);
+});
+
+test('Contains returns null if value not found', () => {
+ const node = new Node(10);
+ node.insert(5);
+ node.insert(15);
+ node.insert(20);
+ node.insert(0);
+ node.insert(-5);
+ node.insert(3);
+
+ expect(node.contains(9999)).toEqual(null);
+});
diff --git a/exercises/capitalize/index.js b/exercises/capitalize/index.js
index 353993f21e..ba9cb5988a 100644
--- a/exercises/capitalize/index.js
+++ b/exercises/capitalize/index.js
@@ -7,28 +7,6 @@
// capitalize('a lazy fox') --> 'A Lazy Fox'
// capitalize('look, it is working!') --> 'Look, It Is Working!'
-function capitalize(str) {
- let result = str[0].toUpperCase();
-
- for (let i = 1; i < str.length; i++) {
- if (str[i - 1] === ' ') {
- result += str[i].toUpperCase();
- } else {
- result += str[i];
- }
- }
-
- return result;
-}
+function capitalize(str) {}
module.exports = capitalize;
-
-// function capitalize(str) {
-// const words = [];
-//
-// for (let word of str.split(' ')) {
-// words.push(word[0].toUpperCase() + word.slice(1));
-// }
-//
-// return words.join(' ');
-// }
diff --git a/exercises/chunk/index.js b/exercises/chunk/index.js
index 02c9bbb3e2..8e7a800cfc 100644
--- a/exercises/chunk/index.js
+++ b/exercises/chunk/index.js
@@ -8,32 +8,6 @@
// chunk([1, 2, 3, 4, 5], 4) --> [[ 1, 2, 3, 4], [5]]
// chunk([1, 2, 3, 4, 5], 10) --> [[ 1, 2, 3, 4, 5]]
-function chunk(array, size) {
- const chunked = [];
- let index = 0;
-
- while (index < array.length) {
- chunked.push(array.slice(index, index + size));
- index += size;
- }
-
- return chunked;
-}
+function chunk(array, size) {}
module.exports = chunk;
-
-// function chunk(array, size) {
-// const chunked = [];
-//
-// for (let element of array) {
-// const last = chunked[chunked.length - 1];
-//
-// if (!last || last.length === size) {
-// chunked.push([element]);
-// } else {
-// last.push(element);
-// }
-// }
-//
-// return chunked;
-// }
diff --git a/exercises/events/example.html b/exercises/events/example.html
new file mode 100644
index 0000000000..3de3daed7a
--- /dev/null
+++ b/exercises/events/example.html
@@ -0,0 +1,10 @@
+
+
+
+
+
Click the button
+
+
+
diff --git a/exercises/events/index.js b/exercises/events/index.js
new file mode 100644
index 0000000000..7d9b0dddb2
--- /dev/null
+++ b/exercises/events/index.js
@@ -0,0 +1,19 @@
+// --- Directions
+// Create an 'eventing' library out of the
+// Events class. The Events class should
+// have methods 'on', 'trigger', and 'off'.
+
+class Events {
+ // Register an event handler
+ on(eventName, callback) {}
+
+ // Trigger all callbacks associated
+ // with a given eventName
+ trigger(eventName) {}
+
+ // Remove all event handlers associated
+ // with the given eventName
+ off(eventName) {}
+}
+
+module.exports = Events;
diff --git a/exercises/events/test.js b/exercises/events/test.js
new file mode 100644
index 0000000000..e015819d09
--- /dev/null
+++ b/exercises/events/test.js
@@ -0,0 +1,77 @@
+const Events = require('./index');
+
+test('Events can be registered then triggered', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(1);
+});
+
+test('Multiple events can be registered then triggered', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.on('click', cb2);
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(1);
+ expect(cb2.mock.calls.length).toBe(1);
+});
+
+test('Events can be triggered multiple times', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.on('click', cb2);
+ events.trigger('click');
+ events.trigger('click');
+
+ expect(cb1.mock.calls.length).toBe(3);
+ expect(cb2.mock.calls.length).toBe(2);
+});
+
+test('Events can have different names', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.on('hover', cb2);
+ events.trigger('click');
+ events.trigger('hover');
+
+ expect(cb1.mock.calls.length).toBe(2);
+ expect(cb2.mock.calls.length).toBe(1);
+});
+
+test('Events can be toggled off', () => {
+ const events = new Events();
+
+ const cb1 = jest.fn();
+ const cb2 = jest.fn();
+
+ events.on('hover', cb2);
+
+ events.on('click', cb1);
+ events.trigger('click');
+ events.off('click');
+ events.trigger('click');
+
+ events.trigger('hover');
+
+ expect(cb1.mock.calls.length).toBe(1);
+ expect(cb2.mock.calls.length).toBe(1);
+});
diff --git a/exercises/fib/index.js b/exercises/fib/index.js
index 51e2ae17a7..9efe84faf2 100644
--- a/exercises/fib/index.js
+++ b/exercises/fib/index.js
@@ -8,41 +8,6 @@
// Example:
// fib(4) === 3
-function memoize(fn) {
- const cache = {};
- return function(...args) {
- if (cache[args]) {
- return cache[args];
- }
-
- const result = fn.apply(this, args);
- cache[args] = result;
-
- return result;
- };
-}
-
-function slowFib(n) {
- if (n < 2) {
- return n;
- }
-
- return fib(n - 1) + fib(n - 2);
-}
-
-const fib = memoize(slowFib);
+function fib(n) {}
module.exports = fib;
-
-// function fib(n) {
-// const result = [0, 1];
-//
-// for (let i = 2; i <= n; i++) {
-// const a = result[i - 1];
-// const b = result[i - 2];
-//
-// result.push(a + b);
-// }
-//
-// return result[n];
-// }
diff --git a/exercises/fizzbuzz/index.js b/exercises/fizzbuzz/index.js
index 8fe64c47b5..0aeab8873b 100644
--- a/exercises/fizzbuzz/index.js
+++ b/exercises/fizzbuzz/index.js
@@ -12,20 +12,6 @@
// 4
// buzz
-function fizzBuzz(n) {
- for (let i = 1; i <= n; i++) {
- // Is the number a multiple of 3 and 5?
- if (i % 3 === 0 && i % 5 === 0) {
- console.log('fizzbuzz');
- } else if (i % 3 === 0) {
- // Is the number a multiple of 3?
- console.log('fizz');
- } else if (i % 5 === 0) {
- console.log('buzz');
- } else {
- console.log(i);
- }
- }
-}
+function fizzBuzz(n) {}
module.exports = fizzBuzz;
diff --git a/exercises/fizzbuzz/test.js b/exercises/fizzbuzz/test.js
index fa5108e1ec..b25dc82c71 100644
--- a/exercises/fizzbuzz/test.js
+++ b/exercises/fizzbuzz/test.js
@@ -31,7 +31,7 @@ test('Calling fizzbuzz with 15 prints out the correct values', () => {
});
beforeEach(() => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
+ jest.spyOn(console, 'log');
});
afterEach(() => {
diff --git a/exercises/fromlast/index.js b/exercises/fromlast/index.js
new file mode 100644
index 0000000000..e5c9f31a58
--- /dev/null
+++ b/exercises/fromlast/index.js
@@ -0,0 +1,16 @@
+// --- Directions
+// Given a linked list, return the element n spaces
+// from the last node in the list. Do not call the 'size'
+// method of the linked list. Assume that n will always
+// be less than the length of the list.
+// --- Examples
+// const list = new List();
+// list.insertLast('a');
+// list.insertLast('b');
+// list.insertLast('c');
+// list.insertLast('d');
+// fromLast(list, 2).data // 'b'
+
+function fromLast(list, n) {}
+
+module.exports = fromLast;
diff --git a/exercises/fromlast/linkedlist.js b/exercises/fromlast/linkedlist.js
new file mode 100644
index 0000000000..2029b3b71e
--- /dev/null
+++ b/exercises/fromlast/linkedlist.js
@@ -0,0 +1,156 @@
+class Node {
+ constructor(data, next = null) {
+ this.data = data;
+ this.next = next;
+ }
+}
+
+class LinkedList {
+ constructor() {
+ this.head = null;
+ }
+
+ insertFirst(data) {
+ this.head = new Node(data, this.head);
+ }
+
+ size() {
+ let counter = 0;
+ let node = this.head;
+
+ while (node) {
+ counter++;
+ node = node.next;
+ }
+
+ return counter;
+ }
+
+ getFirst() {
+ return this.head;
+ }
+
+ getLast() {
+ if (!this.head) {
+ return null;
+ }
+
+ let node = this.head;
+ while (node) {
+ if (!node.next) {
+ return node;
+ }
+ node = node.next;
+ }
+ }
+
+ clear() {
+ this.head = null;
+ }
+
+ removeFirst() {
+ if (!this.head) {
+ return;
+ }
+
+ this.head = this.head.next;
+ }
+
+ removeLast() {
+ if (!this.head) {
+ return;
+ }
+
+ if (!this.head.next) {
+ this.head = null;
+ return;
+ }
+
+ let previous = this.head;
+ let node = this.head.next;
+ while (node.next) {
+ previous = node;
+ node = node.next;
+ }
+ previous.next = null;
+ }
+
+ insertLast(data) {
+ const last = this.getLast();
+
+ if (last) {
+ // There are some existing nodes in our chain
+ last.next = new Node(data);
+ } else {
+ // The chain is empty!
+ this.head = new Node(data);
+ }
+ }
+
+ getAt(index) {
+ let counter = 0;
+ let node = this.head;
+ while (node) {
+ if (counter === index) {
+ return node;
+ }
+
+ counter++;
+ node = node.next;
+ }
+ return null;
+ }
+
+ removeAt(index) {
+ if (!this.head) {
+ return;
+ }
+
+ if (index === 0) {
+ this.head = this.head.next;
+ return;
+ }
+
+ const previous = this.getAt(index - 1);
+ if (!previous || !previous.next) {
+ return;
+ }
+ previous.next = previous.next.next;
+ }
+
+ insertAt(data, index) {
+ if (!this.head) {
+ this.head = new Node(data);
+ return;
+ }
+
+ if (index === 0) {
+ this.head = new Node(data, this.head);
+ return;
+ }
+
+ const previous = this.getAt(index - 1) || this.getLast();
+ const node = new Node(data, previous.next);
+ previous.next = node;
+ }
+
+ forEach(fn) {
+ let node = this.head;
+ let counter = 0;
+ while (node) {
+ fn(node, counter);
+ node = node.next;
+ counter++;
+ }
+ }
+
+ *[Symbol.iterator]() {
+ let node = this.head;
+ while (node) {
+ yield node;
+ node = node.next;
+ }
+ }
+}
+
+module.exports = { Node, LinkedList };
diff --git a/exercises/fromlast/test.js b/exercises/fromlast/test.js
new file mode 100644
index 0000000000..e4684d5258
--- /dev/null
+++ b/exercises/fromlast/test.js
@@ -0,0 +1,20 @@
+const fromLast = require('./index');
+const L = require('./linkedlist');
+const List = L.LinkedList;
+const Node = L.Node;
+
+test('fromLast is a function', () => {
+ expect(typeof fromLast).toEqual('function');
+});
+
+test('fromLast returns the node n elements from the end', () => {
+ const l = new List();
+
+ l.insertLast('a');
+ l.insertLast('b');
+ l.insertLast('c');
+ l.insertLast('d');
+ l.insertLast('e');
+
+ expect(fromLast(l, 3).data).toEqual('b');
+});
diff --git a/exercises/levelwidth/index.js b/exercises/levelwidth/index.js
new file mode 100644
index 0000000000..802cc4aa04
--- /dev/null
+++ b/exercises/levelwidth/index.js
@@ -0,0 +1,16 @@
+// --- Directions
+// Given the root node of a tree, return
+// an array where each element is the width
+// of the tree at each level.
+// --- Example
+// Given:
+// 0
+// / | \
+// 1 2 3
+// | |
+// 4 5
+// Answer: [1, 3, 2]
+
+function levelWidth(root) {}
+
+module.exports = levelWidth;
diff --git a/exercises/levelwidth/node.js b/exercises/levelwidth/node.js
new file mode 100644
index 0000000000..5238e8f597
--- /dev/null
+++ b/exercises/levelwidth/node.js
@@ -0,0 +1,10 @@
+module.exports = class Node {
+ constructor(data) {
+ this.data = data;
+ this.children = [];
+ }
+
+ add(data) {
+ this.children.push(new Node(data));
+ }
+};
diff --git a/exercises/levelwidth/test.js b/exercises/levelwidth/test.js
new file mode 100644
index 0000000000..35d20c2a91
--- /dev/null
+++ b/exercises/levelwidth/test.js
@@ -0,0 +1,27 @@
+const Node = require('./node');
+const levelWidth = require('./index');
+
+test('levelWidth is a function', () => {
+ expect(typeof levelWidth).toEqual('function');
+});
+
+test('levelWidth returns number of nodes at widest point', () => {
+ const root = new Node(0);
+ root.add(1);
+ root.add(2);
+ root.add(3);
+ root.children[0].add(4);
+ root.children[2].add(5);
+
+ expect(levelWidth(root)).toEqual([1, 3, 2]);
+});
+
+test('levelWidth returns number of nodes at widest point', () => {
+ const root = new Node(0);
+ root.add(1);
+ root.children[0].add(2);
+ root.children[0].add(3);
+ root.children[0].children[0].add(4);
+
+ expect(levelWidth(root)).toEqual([1, 1, 2, 1]);
+});
diff --git a/exercises/linkedlist/directions.html b/exercises/linkedlist/directions.html
index 3bae946819..ecb118d11c 100644
--- a/exercises/linkedlist/directions.html
+++ b/exercises/linkedlist/directions.html
@@ -21,7 +21,7 @@
Node Class API
-
constructor
+
Node.constructor
(Data, Node)
Node
@@ -34,10 +34,10 @@
Node Class API
- const n = new Node('Hi');
+ const n = new Node('There');
n.data // 'Hi'
n.next // null
- const n2 = new Node('There', n);
+ const n2 = new Node('Hi', n);
n.next // returns n
@@ -324,8 +324,7 @@
LinkedList Class API
-
- Calls the provided function with every node of the chain and the index
- of the node.
+ Calls the provided function with every node of the chain
@@ -336,7 +335,7 @@
LinkedList Class API
list.insertLast(3);
list.insertLast(4);
- list.forEach((node, index) => {
+ list.forEach(node => {
node.data += 10;
});
list.getAt(0); // Returns node with data '11'
diff --git a/exercises/linkedlist/index.js b/exercises/linkedlist/index.js
index 5bd5efbdad..c75cc872b0 100644
--- a/exercises/linkedlist/index.js
+++ b/exercises/linkedlist/index.js
@@ -2,159 +2,8 @@
// Implement classes Node and Linked Lists
// See 'directions' document
-class Node {
- constructor(data, next = null) {
- this.data = data;
- this.next = next;
- }
-}
+class Node {}
-class LinkedList {
- constructor() {
- this.head = null;
- }
-
- insertFirst(data) {
- this.head = new Node(data, this.head);
- }
-
- size() {
- let counter = 0;
- let node = this.head;
-
- while (node) {
- counter++;
- node = node.next;
- }
-
- return counter;
- }
-
- getFirst() {
- return this.head;
- }
-
- getLast() {
- if (!this.head) {
- return null;
- }
-
- let node = this.head;
- while (node) {
- if (!node.next) {
- return node;
- }
- node = node.next;
- }
- }
-
- clear() {
- this.head = null;
- }
-
- removeFirst() {
- if (!this.head) {
- return;
- }
-
- this.head = this.head.next;
- }
-
- removeLast() {
- if (!this.head) {
- return;
- }
-
- if (!this.head.next) {
- this.head = null;
- return;
- }
-
- let previous = this.head;
- let node = this.head.next;
- while (node.next) {
- previous = node;
- node = node.next;
- }
- previous.next = null;
- }
-
- insertLast(data) {
- const last = this.getLast();
-
- if (last) {
- // There are some existing nodes in our chain
- last.next = new Node(data);
- } else {
- // The chain is empty!
- this.head = new Node(data);
- }
- }
-
- getAt(index) {
- let counter = 0;
- let node = this.head;
- while (node) {
- if (counter === index) {
- return node;
- }
-
- counter++;
- node = node.next;
- }
- return null;
- }
-
- removeAt(index) {
- if (!this.head) {
- return;
- }
-
- if (index === 0) {
- this.head = this.head.next;
- return;
- }
-
- const previous = this.getAt(index - 1);
- if (!previous || !previous.next) {
- return;
- }
- previous.next = previous.next.next;
- }
-
- insertAt(data, index) {
- if (!this.head) {
- this.head = new Node(data);
- return;
- }
-
- if (index === 0) {
- this.head = new Node(data, this.head);
- return;
- }
-
- const previous = this.getAt(index - 1) || this.getLast();
- const node = new Node(data, previous.next);
- previous.next = node;
- }
-
- forEach(fn) {
- let node = this.head;
- let counter = 0;
- while (node) {
- fn(node, counter);
- node = node.next;
- counter++;
- }
- }
-
- *[Symbol.iterator]() {
- let node = this.head;
- while (node) {
- yield node;
- node = node.next;
- }
- }
-}
+class LinkedList {}
module.exports = { Node, LinkedList };
diff --git a/exercises/linkedlist/test.js b/exercises/linkedlist/test.js
index f4de0462e0..18bdb64ea6 100644
--- a/exercises/linkedlist/test.js
+++ b/exercises/linkedlist/test.js
@@ -10,7 +10,7 @@ test('Node is a class', () => {
expect(typeof Node.prototype.constructor).toEqual('function');
});
-describe('A Node', () => {
+describe.skip('A Node', () => {
test('has properties "data" and "next"', () => {
const node = new Node('a', 'b');
expect(node.data).toEqual('a');
@@ -18,7 +18,7 @@ describe('A Node', () => {
});
});
-describe('Insert First', () => {
+describe.skip('Insert First', () => {
test('appends a node to the start of the list', () => {
const l = new List();
l.insertFirst(1);
@@ -28,7 +28,7 @@ describe('Insert First', () => {
});
});
-describe('Size', () => {
+describe.skip('Size', () => {
test('returns the number of items in the linked list', () => {
const l = new List();
expect(l.size()).toEqual(0);
@@ -40,7 +40,7 @@ describe('Size', () => {
});
});
-describe('GetFirst', () => {
+describe.skip('GetFirst', () => {
test('returns the first element', () => {
const l = new List();
l.insertFirst(1);
@@ -50,7 +50,7 @@ describe('GetFirst', () => {
});
});
-describe('GetLast', () => {
+describe.skip('GetLast', () => {
test('returns the last element', () => {
const l = new List();
l.insertFirst(2);
@@ -60,7 +60,7 @@ describe('GetLast', () => {
});
});
-describe('Clear', () => {
+describe.skip('Clear', () => {
test('empties out the list', () => {
const l = new List();
expect(l.size()).toEqual(0);
@@ -74,7 +74,7 @@ describe('Clear', () => {
});
});
-describe('RemoveFirst', () => {
+describe.skip('RemoveFirst', () => {
test('removes the first node when the list has a size of one', () => {
const l = new List();
l.insertFirst('a');
@@ -97,7 +97,7 @@ describe('RemoveFirst', () => {
});
});
-describe('RemoveLast', () => {
+describe.skip('RemoveLast', () => {
test('RemoveLast removes the last node when list is empty', () => {
const l = new List();
expect(() => {
@@ -135,7 +135,7 @@ describe('RemoveLast', () => {
});
});
-describe('InsertLast', () => {
+describe.skip('InsertLast', () => {
test('adds to the end of the list', () => {
const l = new List();
l.insertFirst('a');
@@ -147,7 +147,7 @@ describe('InsertLast', () => {
});
});
-describe('GetAt', () => {
+describe.skip('GetAt', () => {
test('returns the node at given index', () => {
const l = new List();
expect(l.getAt(10)).toEqual(null);
@@ -164,7 +164,7 @@ describe('GetAt', () => {
});
});
-describe('RemoveAt', () => {
+describe.skip('RemoveAt', () => {
test('removeAt doesnt crash on an empty list', () => {
const l = new List();
expect(() => {
@@ -217,7 +217,7 @@ describe('RemoveAt', () => {
});
});
-describe('InsertAt', () => {
+describe.skip('InsertAt', () => {
test('inserts a new node with data at the 0 index when the list is empty', () => {
const l = new List();
l.insertAt('hi', 0);
@@ -272,7 +272,7 @@ describe('InsertAt', () => {
});
});
-describe('ForEach', () => {
+describe.skip('ForEach', () => {
test('applies a transform to each node', () => {
const l = new List();
@@ -292,7 +292,7 @@ describe('ForEach', () => {
});
});
-describe('for...of loops', () => {
+describe.skip('for...of loops', () => {
test('works with the linked list', () => {
const l = new List();
diff --git a/exercises/matrix/index.js b/exercises/matrix/index.js
index f1d0651e48..13bdc31f82 100644
--- a/exercises/matrix/index.js
+++ b/exercises/matrix/index.js
@@ -3,8 +3,8 @@
// and returns a NxN spiral matrix.
// --- Examples
// matrix(2)
-// [[undefined, undefined],
-// [undefined, undefined]]
+// [[1, 2],
+// [4, 3]]
// matrix(3)
// [[1, 2, 3],
// [8, 9, 4],
@@ -15,49 +15,6 @@
// [11, 16, 15, 6],
// [10, 9, 8, 7]]
-function matrix(n) {
- const results = [];
-
- for (let i = 0; i < n; i++) {
- results.push([]);
- }
-
- let counter = 1;
- let startColumn = 0;
- let endColumn = n - 1;
- let startRow = 0;
- let endRow = n - 1;
- while (startColumn <= endColumn && startRow <= endRow) {
- // Top row
- for (let i = startColumn; i <= endColumn; i++) {
- results[startRow][i] = counter;
- counter++;
- }
- startRow++;
-
- // Right column
- for (let i = startRow; i <= endRow; i++) {
- results[i][endColumn] = counter;
- counter++;
- }
- endColumn--;
-
- // Bottom row
- for (let i = endColumn; i >= startColumn; i--) {
- results[endRow][i] = counter;
- counter++;
- }
- endRow--;
-
- // start column
- for (let i = endRow; i >= startRow; i--) {
- results[i][startColumn] = counter;
- counter++;
- }
- startColumn++;
- }
-
- return results;
-}
+function matrix(n) {}
module.exports = matrix;
diff --git a/exercises/maxchar/index.js b/exercises/maxchar/index.js
index 4ddbeac8ac..12f32ba998 100644
--- a/exercises/maxchar/index.js
+++ b/exercises/maxchar/index.js
@@ -5,27 +5,6 @@
// maxChar("abcccccccd") === "c"
// maxChar("apple 1231111") === "1"
-function maxChar(str) {
- const charMap = {};
- let max = 0;
- let maxChar = '';
-
- for (let char of str) {
- if (charMap[char]) {
- charMap[char]++;
- } else {
- charMap[char] = 1;
- }
- }
-
- for (let char in charMap) {
- if (charMap[char] > max) {
- max = charMap[char];
- maxChar = char;
- }
- }
-
- return maxChar;
-}
+function maxChar(str) {}
module.exports = maxChar;
diff --git a/exercises/package.json b/exercises/package.json
index 7ab32a4f9b..e60d0fad73 100644
--- a/exercises/package.json
+++ b/exercises/package.json
@@ -6,6 +6,9 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
+ "jest": {
+ "testEnvironment": "node"
+ },
"author": "",
"license": "ISC"
}
diff --git a/exercises/palindrome/index.js b/exercises/palindrome/index.js
index 43bffe83cb..58115ed243 100644
--- a/exercises/palindrome/index.js
+++ b/exercises/palindrome/index.js
@@ -7,19 +7,6 @@
// palindrome("abba") === true
// palindrome("abcdefg") === false
-function palindrome(str) {
- return str.split('').every((char, i) => {
- return char === str[str.length - i - 1];
- });
-}
+function palindrome(str) {}
module.exports = palindrome;
-
-// function palindrome(str) {
-// const reversed = str
-// .split('')
-// .reverse()
-// .join('');
-//
-// return str === reversed;
-// }
diff --git a/exercises/pyramid/index.js b/exercises/pyramid/index.js
index 003b0a094c..627bab341d 100644
--- a/exercises/pyramid/index.js
+++ b/exercises/pyramid/index.js
@@ -14,42 +14,6 @@
// ' ### '
// '#####'
-function pyramid(n, row = 0, level = '') {
- if (row === n) {
- return;
- }
-
- if (level.length === 2 * n - 1) {
- console.log(level);
- return pyramid(n, row + 1);
- }
-
- const midpoint = Math.floor((2 * n - 1) / 2);
- let add;
- if (midpoint - row <= level.length && midpoint + row >= level.length) {
- add = '#';
- } else {
- add = ' ';
- }
- pyramid(n, row, level + add);
-}
+function pyramid(n) {}
module.exports = pyramid;
-//
-// function pyramid(n) {
-// const midpoint = Math.floor((2 * n - 1) / 2);
-//
-// for (let row = 0; row < n; row++) {
-// let level = '';
-//
-// for (let column = 0; column < 2 * n - 1; column++) {
-// if (midpoint - row <= column && midpoint + row >= column) {
-// level += '#';
-// } else {
-// level += ' ';
-// }
-// }
-//
-// console.log(level);
-// }
-// }
diff --git a/exercises/qfroms/index.js b/exercises/qfroms/index.js
index 829c30e97d..93af154f18 100644
--- a/exercises/qfroms/index.js
+++ b/exercises/qfroms/index.js
@@ -14,43 +14,6 @@
const Stack = require('./stack');
-class Queue {
- constructor() {
- this.first = new Stack();
- this.second = new Stack();
- }
-
- add(record) {
- this.first.push(record);
- }
-
- remove() {
- while (this.first.peek()) {
- this.second.push(this.first.pop());
- }
-
- const record = this.second.pop();
-
- while (this.second.peek()) {
- this.first.push(this.second.pop());
- }
-
- return record;
- }
-
- peek() {
- while (this.first.peek()) {
- this.second.push(this.first.pop());
- }
-
- const record = this.second.peek();
-
- while (this.second.peek()) {
- this.first.push(this.second.pop());
- }
-
- return record;
- }
-}
+class Queue {}
module.exports = Queue;
diff --git a/exercises/queue/index.js b/exercises/queue/index.js
index e475d6e58b..e11ff2a13e 100644
--- a/exercises/queue/index.js
+++ b/exercises/queue/index.js
@@ -8,18 +8,6 @@
// q.add(1);
// q.remove(); // returns 1;
-class Queue {
- constructor() {
- this.data = [];
- }
-
- add(record) {
- this.data.unshift(record);
- }
-
- remove() {
- return this.data.pop();
- }
-}
+class Queue {}
module.exports = Queue;
diff --git a/exercises/reverseint/index.js b/exercises/reverseint/index.js
index c2e6a0dd39..5db3bfdee1 100644
--- a/exercises/reverseint/index.js
+++ b/exercises/reverseint/index.js
@@ -8,14 +8,6 @@
// reverseInt(-15) === -51
// reverseInt(-90) === -9
-function reverseInt(n) {
- const reversed = n
- .toString()
- .split('')
- .reverse()
- .join('');
-
- return parseInt(reversed) * Math.sign(n);
-}
+function reverseInt(n) {}
module.exports = reverseInt;
diff --git a/exercises/reversestring/index.js b/exercises/reversestring/index.js
index 5f036a9757..fc909037d4 100644
--- a/exercises/reversestring/index.js
+++ b/exercises/reversestring/index.js
@@ -6,25 +6,6 @@
// reverse('hello') === 'olleh'
// reverse('Greetings!') === '!sgniteerG'
-function reverse(str) {
- return str.split('').reduce((rev, char) => char + rev, '');
-}
+function reverse(str) {}
module.exports = reverse;
-
-// function reverse(str) {
-// return str
-// .split('')
-// .reverse()
-// .join('');
-// }
-
-// function reverse(str) {
-// let reversed = '';
-//
-// for (let character of str) {
-// reversed = character + reversed;
-// }
-//
-// return reversed;
-// }
diff --git a/exercises/sorting/index.js b/exercises/sorting/index.js
new file mode 100644
index 0000000000..acbaf3feb0
--- /dev/null
+++ b/exercises/sorting/index.js
@@ -0,0 +1,20 @@
+// --- Directions
+// Implement bubbleSort, selectionSort, and mergeSort
+
+function bubbleSort(arr) {
+
+}
+
+function selectionSort(arr) {
+
+}
+
+function mergeSort(arr) {
+
+}
+
+function merge(left, right) {
+
+}
+
+module.exports = { bubbleSort, selectionSort, mergeSort, merge };
diff --git a/exercises/sorting/test.js b/exercises/sorting/test.js
new file mode 100644
index 0000000000..0ff475b0c8
--- /dev/null
+++ b/exercises/sorting/test.js
@@ -0,0 +1,38 @@
+const S = require('./index');
+const bubbleSort = S.bubbleSort;
+const selectionSort = S.selectionSort;
+const mergeSort = S.mergeSort;
+const merge = S.merge;
+
+function getArray() {
+ return [100, -40, 500, -124, 0, 21, 7];
+}
+
+function getSortedArray() {
+ return [-124, -40, 0, 7, 21, 100, 500];
+}
+
+describe('Bubble sort', () => {
+ test('sorts an array', () => {
+ expect(bubbleSort(getArray())).toEqual(getSortedArray());
+ });
+});
+
+describe('Selection sort', () => {
+ test('sorts an array', () => {
+ expect(selectionSort(getArray())).toEqual(getSortedArray());
+ });
+});
+
+describe('Merge sort', () => {
+ test('merge function can join together two sorted arrays', () => {
+ const left = [1, 10];
+ const right = [2, 8, 12];
+
+ expect(merge(left, right)).toEqual([1,2,8,10,12]);
+ });
+
+ test('sorts an array', () => {
+ expect(mergeSort(getArray())).toEqual(getSortedArray());
+ });
+});
diff --git a/exercises/stack/index.js b/exercises/stack/index.js
index 7e95441874..5c7c4608ca 100644
--- a/exercises/stack/index.js
+++ b/exercises/stack/index.js
@@ -10,22 +10,6 @@
// s.pop(); // returns 2
// s.pop(); // returns 1
-class Stack {
- constructor() {
- this.data = [];
- }
-
- push(record) {
- this.data.push(record);
- }
-
- pop() {
- return this.data.pop();
- }
-
- peek() {
- return this.data[this.data.length - 1];
- }
-}
+class Stack {}
module.exports = Stack;
diff --git a/exercises/steps/index.js b/exercises/steps/index.js
index fd73326955..400adc9ac0 100644
--- a/exercises/steps/index.js
+++ b/exercises/steps/index.js
@@ -17,34 +17,6 @@
// '### '
// '####'
-function steps(n, row = 0, stair = '') {
- if (n === row) {
- return;
- }
-
- if (n === stair.length) {
- console.log(stair);
- return steps(n, row + 1);
- }
-
- const add = stair.length <= row ? '#' : ' ';
- steps(n, row, stair + add);
-}
+function steps(n) {}
module.exports = steps;
-
-// function steps(n) {
-// for (let row = 0; row < n; row++) {
-// let stair = '';
-//
-// for (let column = 0; column < n; column++) {
-// if (column <= row) {
-// stair += '#';
-// } else {
-// stair += ' ';
-// }
-// }
-//
-// console.log(stair);
-// }
-// }
diff --git a/exercises/tree/index.js b/exercises/tree/index.js
new file mode 100644
index 0000000000..0ef59b82ad
--- /dev/null
+++ b/exercises/tree/index.js
@@ -0,0 +1,17 @@
+// --- Directions
+// 1) Create a node class. The constructor
+// should accept an argument that gets assigned
+// to the data property and initialize an
+// empty array for storing children. The node
+// class should have methods 'add' and 'remove'.
+// 2) Create a tree class. The tree constructor
+// should initialize a 'root' property to null.
+// 3) Implement 'traverseBF' and 'traverseDF'
+// on the tree class. Each method should accept a
+// function that gets called with each element in the tree
+
+class Node {}
+
+class Tree {}
+
+module.exports = { Tree, Node };
diff --git a/exercises/tree/test.js b/exercises/tree/test.js
new file mode 100644
index 0000000000..1a103edb60
--- /dev/null
+++ b/exercises/tree/test.js
@@ -0,0 +1,67 @@
+const T = require('./index');
+const Node = T.Node;
+const Tree = T.Tree;
+
+describe('Node', () => {
+ test('Node is a constructor', () => {
+ expect(typeof Node.prototype.constructor).toEqual('function');
+ });
+
+ test('Node has a data and children properties', () => {
+ const n = new Node('a');
+ expect(n.data).toEqual('a');
+ expect(n.children.length).toEqual(0);
+ });
+
+ test('Node can add children', () => {
+ const n = new Node('a');
+ n.add('b');
+ expect(n.children.length).toEqual(1);
+ expect(n.children[0].children).toEqual([]);
+ });
+
+ test('Node can remove children', () => {
+ const n = new Node('a');
+ n.add('b');
+ expect(n.children.length).toEqual(1);
+ n.remove('b');
+ expect(n.children.length).toEqual(0);
+ });
+});
+
+describe.skip('Tree', () => {
+ test('starts empty', () => {
+ const t = new Tree();
+ expect(t.root).toEqual(null);
+ });
+
+ test('Can traverse bf', () => {
+ const letters = [];
+ const t = new Tree();
+ t.root = new Node('a');
+ t.root.add('b');
+ t.root.add('c');
+ t.root.children[0].add('d');
+
+ t.traverseBF(node => {
+ letters.push(node.data);
+ });
+
+ expect(letters).toEqual(['a', 'b', 'c', 'd']);
+ });
+
+ test('Can traverse DF', () => {
+ const letters = [];
+ const t = new Tree();
+ t.root = new Node('a');
+ t.root.add('b');
+ t.root.add('d');
+ t.root.children[0].add('c');
+
+ t.traverseDF(node => {
+ letters.push(node.data);
+ });
+
+ expect(letters).toEqual(['a', 'b', 'c', 'd']);
+ });
+});
diff --git a/exercises/validate/index.js b/exercises/validate/index.js
new file mode 100644
index 0000000000..a5f99d669a
--- /dev/null
+++ b/exercises/validate/index.js
@@ -0,0 +1,10 @@
+// --- Directions
+// Given a node, validate the binary search tree,
+// ensuring that every node's left hand child is
+// less than the parent node's value, and that
+// every node's right hand child is greater than
+// the parent
+
+function validate(node, min = null, max = null) {}
+
+module.exports = validate;
diff --git a/exercises/validate/node.js b/exercises/validate/node.js
new file mode 100644
index 0000000000..d19e208a59
--- /dev/null
+++ b/exercises/validate/node.js
@@ -0,0 +1,21 @@
+class Node {
+ constructor(data) {
+ this.data = data;
+ this.left = null;
+ this.right = null;
+ }
+
+ insert(data) {
+ if (data < this.data && this.left) {
+ this.left.insert(data);
+ } else if (data < this.data) {
+ this.left = new Node(data);
+ } else if (data > this.data && this.right) {
+ this.right.insert(data);
+ } else if (data > this.data) {
+ this.right = new Node(data);
+ }
+ }
+}
+
+module.exports = Node;
diff --git a/exercises/validate/test.js b/exercises/validate/test.js
new file mode 100644
index 0000000000..6ea4dc3f08
--- /dev/null
+++ b/exercises/validate/test.js
@@ -0,0 +1,23 @@
+const Node = require('./node');
+const validate = require('./index');
+
+test('Validate recognizes a valid BST', () => {
+ const n = new Node(10);
+ n.insert(5);
+ n.insert(15);
+ n.insert(0);
+ n.insert(20);
+
+ expect(validate(n)).toEqual(true);
+});
+
+test('Validate recognizes an invalid BST', () => {
+ const n = new Node(10);
+ n.insert(5);
+ n.insert(15);
+ n.insert(0);
+ n.insert(20);
+ n.left.left.right = new Node(999);
+
+ expect(validate(n)).toEqual(false);
+});
diff --git a/exercises/vowels/index.js b/exercises/vowels/index.js
index 379ba6c60a..ad3b67b250 100644
--- a/exercises/vowels/index.js
+++ b/exercises/vowels/index.js
@@ -7,22 +7,6 @@
// vowels('Why do you ask?') --> 4
// vowels('Why?') --> 0
-function vowels(str) {
- const matches = str.match(/[aeiou]/gi);
- return matches ? matches.length : 0;
-}
+function vowels(str) {}
module.exports = vowels;
-
-// function vowels(str) {
-// let count = 0;
-// const checker = ['a', 'e', 'i', 'o', 'u'];
-//
-// for (let char of str.toLowerCase()) {
-// if (checker.includes(char)) {
-// count++;
-// }
-// }
-//
-// return count;
-// }
diff --git a/exercises/vowels/test.js b/exercises/vowels/test.js
index 0bbf43055e..6283592c0b 100644
--- a/exercises/vowels/test.js
+++ b/exercises/vowels/test.js
@@ -8,6 +8,10 @@ test('returns the number of vowels used', () => {
expect(vowels('aeiou')).toEqual(5);
});
+test('returns the number of vowels used when they are capitalized', () => {
+ expect(vowels('AEIOU')).toEqual(5);
+});
+
test('returns the number of vowels used', () => {
expect(vowels('abcdefghijklmnopqrstuvwxyz')).toEqual(5);
});
diff --git a/exercises/weave/index.js b/exercises/weave/index.js
index 0818cb2385..3d49df9694 100644
--- a/exercises/weave/index.js
+++ b/exercises/weave/index.js
@@ -24,20 +24,6 @@
const Queue = require('./queue');
-function weave(sourceOne, sourceTwo) {
- const q = new Queue();
-
- while (sourceOne.peek() || sourceTwo.peek()) {
- if (sourceOne.peek()) {
- q.add(sourceOne.remove());
- }
-
- if (sourceTwo.peek()) {
- q.add(sourceTwo.remove());
- }
- }
-
- return q;
-}
+function weave(sourceOne, sourceTwo) {}
module.exports = weave;
diff --git a/exercises/weave/queue.js b/exercises/weave/queue.js
index 20044c2ef3..451eb90223 100644
--- a/exercises/weave/queue.js
+++ b/exercises/weave/queue.js
@@ -16,10 +16,6 @@ class Queue {
remove() {
return this.data.pop();
}
-
- peek() {
- return this.data[this.data.length - 1];
- }
}
module.exports = Queue;
diff --git a/exercises/weave/test.js b/exercises/weave/test.js
index c3658c8605..f173360f29 100644
--- a/exercises/weave/test.js
+++ b/exercises/weave/test.js
@@ -1,5 +1,5 @@
const weave = require('./index');
-const Queue = require('./Queue');
+const Queue = require('./queue');
test('queues have a peek function', () => {
const q = new Queue();