Repository: exercism/python
Pull request #507 (2017-08-06 22:01:40)

change: Fixed duplicate test in change_test.py
There were two identical tests.  I modified this one to be more reflective of the name.


Patch:
diff --git a/config.json b/config.json
index 2122329c..024a11e6 100644
--- a/config.json
+++ b/config.json
@@ -801,6 +801,28 @@
         "Algorithms"
       ]
     },
+    {
+      "uuid": "bacb2184-025f-0980-38d6-34fb419e03a6d3d8b44",
+      "slug": "two-fer",
+      "core": false,
+      "unlocked_by": null,
+      "difficulty": 1,
+      "topics": [
+        "control-flow (if-else statements)",
+        "optional values",
+        "text formatting"
+      ]
+    },
+    {
+      "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df",
+      "slug": "collatz-conjecture",
+      "core": false,
+      "unlocked_by": null,
+      "difficulty": 1,
+      "topics": [
+            "control-flow (loops)"
+      ]
+    },
     {
       "uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",
       "slug": "accumulate",
diff --git a/docs/ABOUT.md b/docs/ABOUT.md
index e0ab9378..a9d54b2c 100644
--- a/docs/ABOUT.md
+++ b/docs/ABOUT.md
@@ -1,7 +1,15 @@
-Python is a strong language for beginners. There are many resources available for programmers of all levels, the code is highly readable, and in many cases phrases are comparable to those in the English language. Code can be written and executed from the command line, in an interactive IPython session, or in a [Jupyter](http://jupyter.org) (IPython) notebook.
+Python is a strong language for beginners. 
+There are many resources available for programmers of all levels, the code is highly readable, and in many cases phrases are comparable to those in the English language.
+Code can be written and executed from the command line, in an interactive IPython session, or in a [Jupyter](http://jupyter.org) (IPython) notebook.
 
-The most common form of Python is compiled in C. This is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
+The most common form of Python is compiled in C.
+This is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
 
 [Python is used extensively](https://www.python.org/about/apps/) in scientific computing, finance, games, networking, internet development, and in assembling pipelines of other programs.
 
-Python was started by Guido van Rossum in 1989. Its name is an homage to the comedy troupe Monty Python. Python 2 is used widely but support may end by 2020. Python 3 was introduced in 2008 and is beginning to be adopted more widely. They are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3). Python development is shepherded by [The Python Software Foundation](https://www.python.org/about/) and there are active community-based user groups worldwide.
+Python was started by Guido van Rossum in 1989.
+Its name is an homage to the comedy troupe Monty Python. 
+Python 2 is used widely but support may end by 2020.
+Python 3 was introduced in 2008 and is beginning to be adopted more widely.
+They are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3). 
+Python development is shepherded by [The Python Software Foundation](https://www.python.org/about/) and there are active community-based user groups worldwide.
\ No newline at end of file
diff --git a/exercises/collatz-conjecture/README.md b/exercises/collatz-conjecture/README.md
new file mode 100644
index 00000000..6b2ede8b
--- /dev/null
+++ b/exercises/collatz-conjecture/README.md
@@ -0,0 +1,61 @@
+# Collatz Conjecture
+
+The Collatz Conjecture or 3x+1 problem can be summarized as follows:
+
+Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is
+odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely.
+The conjecture states that no matter which number you start with, you will
+always reach 1 eventually.
+
+Given a number n, return the number of steps required to reach 1.
+
+(Keep in mind that the Conjecture is only concerned with strictly positive
+integers, so your solution should return `None` if given 0 or a negative
+integer.)
+
+## Examples
+
+Starting with n = 3, the steps would be as follows:
+
+0. 3
+1. 10
+2. 5
+3. 16
+4. 8
+5. 4
+6. 2
+7. 1
+
+Resulting in 7 steps. So for input n = 3, the return value would be 7.
+
+Starting with n = 12, the steps would be as follows:
+
+0. 12
+1. 6
+2. 3
+3. 10
+4. 5
+5. 16
+6. 8
+7. 4
+8. 2
+9. 1
+
+Resulting in 9 steps. So for input n = 12, the return value would be 9.
+
+### Submitting Exercises
+
+Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
+
+For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
+
+
+For more detailed information about running tests, code style and linting,
+please see the [help page](http://exercism.io/languages/python).
+
+## Source
+
+An unsolved problem in mathematics named after mathematician Lothar Collatz [https://en.wikipedia.org/wiki/3x_%2B_1_problem](https://en.wikipedia.org/wiki/3x_%2B_1_problem)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/exercises/collatz-conjecture/collatz_conjecture.py b/exercises/collatz-conjecture/collatz_conjecture.py
new file mode 100644
index 00000000..5378d4d9
--- /dev/null
+++ b/exercises/collatz-conjecture/collatz_conjecture.py
@@ -0,0 +1,2 @@
+def collatz_steps():
+    pass
diff --git a/exercises/collatz-conjecture/example.py b/exercises/collatz-conjecture/example.py
new file mode 100644
index 00000000..024a5f73
--- /dev/null
+++ b/exercises/collatz-conjecture/example.py
@@ -0,0 +1,17 @@
+def collatz_steps(n):
+    if n <= 0:
+        return
+
+    step_count = 0
+    while n > 1:
+        if is_odd(n):
+            n = n * 3 + 1
+        else:
+            n = n / 2
+        step_count += 1
+
+    return step_count
+
+
+def is_odd(n):
+    return n % 2 == 1
diff --git a/exercises/isogram/isogram.py b/exercises/isogram/isogram.py
index 7b0ccf92..98b31c5b 100644
--- a/exercises/isogram/isogram.py
+++ b/exercises/isogram/isogram.py
@@ -1,2 +1,2 @@
-def is_isogram():
+def is_isogram(string):
     pass
diff --git a/exercises/scale-generator/example.py b/exercises/scale-generator/example.py
index b836b64a..5e39a85e 100644
--- a/exercises/scale-generator/example.py
+++ b/exercises/scale-generator/example.py
@@ -26,6 +26,8 @@ class Scale(object):
         for i, interval in enumerate(self.pattern):
             pitches.append(scale[last_index])
             last_index += self.ASCENDING_INTERVALS.index(interval) + 1
+        if pitches[0] != scale[last_index % len(scale)]:
+            raise ValueError()
         return pitches
 
     def _reorder_chromatic_scale(self):
diff --git a/exercises/two-fer/README.md b/exercises/two-fer/README.md
new file mode 100644
index 00000000..22d56648
--- /dev/null
+++ b/exercises/two-fer/README.md
@@ -0,0 +1,29 @@
+# Two-fer
+
+`Two-fer` or `2-fer` is short for two for one. One for you and one for me.
+
+```
+"One for X, one for me."
+```
+
+When X is a name or "you".
+
+If the given name is "Alice", the result should be "One for Alice, one for me."
+If no name is given, the result should be "One for you, one for me."
+
+### Submitting Exercises
+
+Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
+
+For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
+
+
+For more detailed information about running tests, code style and linting,
+please see the [help page](http://exercism.io/languages/python).
+
+## Source
+
+This is an exercise to introduce users to basic programming constructs, just after hello World. [https://en.wikipedia.org/wiki/Two-fer](https://en.wikipedia.org/wiki/Two-fer)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
\ No newline at end of file
diff --git a/exercises/two-fer/example.py b/exercises/two-fer/example.py
new file mode 100644
index 00000000..be9c6c5c
--- /dev/null
+++ b/exercises/two-fer/example.py
@@ -0,0 +1,5 @@
+def two_fer(name=""):
+    if not name.strip():
+        return "One for you, one for me."
+    else:
+        return "One for %s, one for me." % name
diff --git a/exercises/two-fer/two_fer.py b/exercises/two-fer/two_fer.py
new file mode 100644
index 00000000..e75f4e56
--- /dev/null
+++ b/exercises/two-fer/two_fer.py
@@ -0,0 +1,2 @@
+def two_fer():
+    pass


Test patch:
diff --git a/exercises/change/change_test.py b/exercises/change/change_test.py
index 3f893633..44e4f914 100644
--- a/exercises/change/change_test.py
+++ b/exercises/change/change_test.py
@@ -19,13 +19,18 @@ class ChangeTest(unittest.TestCase):
                          [21, 21, 21])
 
     def test_large_target_values(self):
-        self.assertEqual(find_minimum_coins(21, [2, 5, 10, 20, 50]),
-                         [2, 2, 2, 5, 10])
+        self.assertEqual(find_minimum_coins(999, [1, 2, 5, 10, 20, 50, 100]),
+                         [2, 2, 5, 20, 20, 50, 100, 100, 100,
+                          100, 100, 100, 100, 100, 100])
 
     def test_possible_change_without_unit_coins_available(self):
         self.assertEqual(find_minimum_coins(21, [2, 5, 10, 20, 50]),
                          [2, 2, 2, 5, 10])
 
+    def test_another_possible_change_without_unit_coins_available(self):
+        self.assertEqual(find_minimum_coins(27, [4, 5]),
+                         [4, 4, 4, 5, 5, 5])
+
     def test_no_coins_make_0_change(self):
         self.assertEqual(find_minimum_coins(0, [1, 5, 10, 21, 25]), [])
 
diff --git a/exercises/collatz-conjecture/collatz_conjecture_test.py b/exercises/collatz-conjecture/collatz_conjecture_test.py
new file mode 100644
index 00000000..9e30be11
--- /dev/null
+++ b/exercises/collatz-conjecture/collatz_conjecture_test.py
@@ -0,0 +1,32 @@
+import unittest
+
+from collatz_conjecture import collatz_steps
+
+# Test cases adapted from `x-common//canonical-data.json` @ version: 1.1.1
+
+
+class CollatzConjectureTests(unittest.TestCase):
+
+    def test_zero_steps_for_one(self):
+        self.assertEqual(collatz_steps(1), 0)
+
+    def test_divide_if_even(self):
+        self.assertEqual(collatz_steps(16), 4)
+
+    def test_even_and_odd_steps(self):
+        self.assertEqual(collatz_steps(12), 9)
+
+    def test_large_number_of_even_and_odd_steps(self):
+        self.assertEqual(collatz_steps(1000000), 152)
+
+    def test_zero_is_invalid_input(self):
+        self.assertEqual(collatz_steps(0), None)
+
+    def test_negative_number_is_invalid_input(self):
+        self.assertEqual(collatz_steps(-1), None)
+
+        self.assertEqual(collatz_steps(-15), None)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/exercises/isogram/isogram_test.py b/exercises/isogram/isogram_test.py
index 2459ae09..6573f898 100644
--- a/exercises/isogram/isogram_test.py
+++ b/exercises/isogram/isogram_test.py
@@ -34,6 +34,9 @@ class TestIsogram(unittest.TestCase):
     def test_duplicated_character_in_the_middle(self):
         self.assertFalse(is_isogram("accentor"))
 
+    def test_isogram_with_duplicated_letter_and_nonletter_character(self):
+        self.assertFalse(is_isogram("Aleph Bot Chap"))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/exercises/scale-generator/scale_generator_test.py b/exercises/scale-generator/scale_generator_test.py
index 5031e031..a1825faa 100644
--- a/exercises/scale-generator/scale_generator_test.py
+++ b/exercises/scale-generator/scale_generator_test.py
@@ -115,6 +115,10 @@ class ScaleGeneratorTest(unittest.TestCase):
         actual = enigmatic.pitches
         self.assertEqual(expected, actual)
 
+    def test_brokeninterval(self):
+        with self.assertRaises(ValueError):
+            Scale('G', 'enigmatic', 'mAMMMmM')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/exercises/two-fer/two_fer_test.py b/exercises/two-fer/two_fer_test.py
new file mode 100644
index 00000000..ceef22c2
--- /dev/null
+++ b/exercises/two-fer/two_fer_test.py
@@ -0,0 +1,18 @@
+import unittest
+
+import two_fer
+
+
+class Two_Fer_test(unittest.TestCase):
+    def test_empty(self):
+        self.assertEqual(two_fer.two_fer(), 'One for you, one for me.')
+
+    def test_eve(self):
+        self.assertEqual(two_fer.two_fer("Eve"), "One for Eve, one for me.")
+
+    def test_bob(self):
+        self.assertEqual(two_fer.two_fer("Bob"), "One for Bob, one for me.")
+
+
+if __name__ == '__main__':
+    unittest.main()