Repository: pberkes/big_O (language: python)

Issue:
Show a nice report
Having to type the following: 

```python
best, others = big_o.big_o(heapify, data_generator_heapify, max_n=10**7)
print(best)
print()
for other in others:
    print(other)
print()
```

Is quite a drag. Also there needs to be a clear separation between `best` and `others`

Would be nice if there was a `print_report` function that prints it all for you, with a clear indication of which solution is likely best. This library looks like it's mostly for diagnostic usage anyway.


Discussion / hints:
Can I be assigned to this ?
Sure! Would you mind suggesting what the report could look like before implementing? I think it would save some time rather than discuss after the fact
Sure! The best algo could just be printed as it is. As of the others, we could print a table which has columns [Ranking, Complexities, Time, Residuals] where the rows are sorted on ascending based on their time.
Also since this print_report function can only be called for the results of big_o function, it would be better to create this function within the big_o() function only, With an introduction of a new default parameter print_report. 

By default we could initialise it to False, thus it will not break any current code. Users who needs to get the report can send a parameter with print_report = True to get the reports printed. 

Just an idea, does that sound good to you. Or do you have any other ways to achieve this in mind ?
I'm not fond of that idea, because it leads to an explosion of keyword arguments in the long run. The general principle is to separate calculations and reporting/plotting.

I think the idea would be to write something like 
```
best, others = big_o.big_o(heapify, data_generator_heapify, max_n=10**7)
print(big_o_report(best, others))
```
and big_o_report returns a nicely formatted string.

`big_o_report` could live in a new module `reports`
Part of this task would also be to add the usage as an example in the documentaion
Got it! That can be done. Will start working on it. Can I go ahead with the proposed table design for report ?

Fix patch:
diff --git a/README.rst b/README.rst
index daff24b..28c8a01 100755
--- a/README.rst
+++ b/README.rst
@@ -72,7 +72,6 @@ Submodules
   fit to the execution times. Unless you want to define new classes, you don't
   need to worry about it.
 
-
 Standard library examples
 -------------------------
 
@@ -151,6 +150,25 @@ dynamic programming and is linear O(n):
     >>> print(big_o.big_o(fib_dp, big_o.datagen.n_, n_repeats=100, min_n=200, max_n=1000)[0])
     Linear: time = -1.8E-06 + 7.3E-06*n (sec)
 
+Report Generation
+-------
+
+This feature allows users to generate a report based on the outputs received from
+calling the :code:`big-o` function.
+The report defines the best time complexity along with the the others
+estimates and returns them as a string.
+
+    >>> best, others = big_o.big_o(heapify, data_generator_heapify, max_n=10**7)
+    >>> print(big_o.reports.big_o_report(best, others))
+    Best : Polynomial: time = 3.5E-06 * x^0.97 (sec)
+    Constant: time = 0.13 (sec)                                     (res: 0.067)
+    Linear: time = 0.0068 + 2.5E-06*n (sec)                         (res: 0.003)
+    Quadratic: time = 0.053 + 2.2E-11*n^2 (sec)                     (res: 0.012)
+    Cubic: time = 0.074 + 2.1E-16*n^3 (sec)                         (res: 0.02)
+    Polynomial: time = 3.5E-06 * x^0.97 (sec)                       (res: 0.003)
+    Logarithmic: time = -0.2 + 0.033*log(n) (sec)                   (res: 0.027)
+    Linearithmic: time = 0.013 + 2.2E-07*n*log(n) (sec)             (res: 0.0035)
+    Exponential: time = 0.007 * 1^n (sec)                           (res: 0.22)
 
 License
 -------
diff --git a/big_o/__init__.py b/big_o/__init__.py
index d4da61e..9d23f71 100755
--- a/big_o/__init__.py
+++ b/big_o/__init__.py
@@ -5,5 +5,6 @@
     infer_big_o_class,
     measure_execution_time,
 )
+from big_o import reports  # noqa
 from big_o import complexities  # noqa
 from big_o import datagen  # noqa
diff --git a/big_o/reports.py b/big_o/reports.py
new file mode 100644
index 0000000..e90af9e
--- /dev/null
+++ b/big_o/reports.py
@@ -0,0 +1,24 @@
+from big_o.complexities import ComplexityClass
+
+def big_o_report(best, others):
+    """ Creates a human-readable report of the output of the big_o function.
+
+    Input:
+    ------
+
+    best -- Object representing the complexity class that best fits
+            the measured execution times.
+    others -- A dictionary of fitted complexity classes to the residuals
+
+    Output:
+    -------
+
+    report -- A string describing the report of the big_o function
+
+    """
+    report = ""
+    report += 'Best : {!s:<60s} \n'.format(best)
+    for class_, residuals in others.items():
+        if isinstance(class_, ComplexityClass):
+            report += '{!s:<60s}    (res: {:.2G})\n'.format(class_, residuals)
+    return report


Test patch:
diff --git a/big_o/test/test_reports.py b/big_o/test/test_reports.py
new file mode 100644
index 0000000..e99666b
--- /dev/null
+++ b/big_o/test/test_reports.py
@@ -0,0 +1,18 @@
+import unittest
+import big_o
+from big_o import reports
+
+class TestReport(unittest.TestCase):
+
+    def test_report_return(self):
+        best, others = big_o.big_o(sorted, lambda n: big_o.datagen.integers(n, 100, 500))
+
+        assert isinstance(reports.big_o_report(best, others), str)
+
+    def test_report_return_raw_data_true(self):
+        best, others = big_o.big_o(sorted, lambda n: big_o.datagen.integers(n, 100, 500), return_raw_data=True)
+
+        report = reports.big_o_report(best, others)
+        assert 'measures' not in report
+        assert 'times' not in report
+