Skip to content

Commit 1904077

Browse files
authored
ledger: implement exercise (exercism#1408)
* ledger: update config.json * ledger: add README.md * ledger: add tests * ledger: add solution template * ledger: fix expected description in test_overlong_description * ledger: add example solution * ledger: add inefficient implementation
1 parent a58792f commit 1904077

5 files changed

Lines changed: 602 additions & 0 deletions

File tree

config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,18 @@
13541354
"conditionals"
13551355
]
13561356
},
1357+
{
1358+
"slug": "ledger",
1359+
"uuid": "c2e9d08d-1a58-4d9a-a797-cea265973bd4",
1360+
"core": false,
1361+
"unlocked_by": "markdown",
1362+
"difficulty": 5,
1363+
"topics": [
1364+
"globalization",
1365+
"refactoring",
1366+
"strings"
1367+
]
1368+
},
13571369
{
13581370
"slug": "accumulate",
13591371
"uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",

exercises/ledger/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Ledger
2+
3+
Refactor a ledger printer.
4+
5+
The ledger exercise is a refactoring exercise. There is code that prints a
6+
nicely formatted ledger, given a locale (American or Dutch) and a currency (US
7+
dollar or euro). The code however is rather badly written, though (somewhat
8+
surprisingly) it consistently passes the test suite.
9+
10+
Rewrite this code. Remember that in refactoring the trick is to make small steps
11+
that keep the tests passing. That way you can always quickly go back to a
12+
working version. Version control tools like git can help here as well.
13+
14+
Please keep a log of what changes you've made and make a comment on the exercise
15+
containing that log, this will help reviewers.
16+
17+
## Exception messages
18+
19+
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
20+
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
21+
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
22+
a message.
23+
24+
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
25+
`raise Exception`, you should write:
26+
27+
```python
28+
raise Exception("Meaningful message indicating the source of the error")
29+
```
30+
31+
## Running the tests
32+
33+
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
34+
35+
- Python 2.7: `py.test ledger_test.py`
36+
- Python 3.4+: `pytest ledger_test.py`
37+
38+
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
39+
`python -m pytest ledger_test.py`
40+
41+
### Common `pytest` options
42+
43+
- `-v` : enable verbose output
44+
- `-x` : stop running tests on first failure
45+
- `--ff` : run failures from previous test before running other test cases
46+
47+
For other options, see `python -m pytest -h`
48+
49+
## Submitting Exercises
50+
51+
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/ledger` directory.
52+
53+
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
54+
55+
For more detailed information about running tests, code style and linting,
56+
please see the [help page](http://exercism.io/languages/python).
57+
58+
## Submitting Incomplete Solutions
59+
60+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

exercises/ledger/example.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: utf-8 -*-
2+
from datetime import datetime
3+
4+
ROW_FMT = u'{{:<{1}}} | {{:<{2}}} | {{:{0}{3}}}'
5+
6+
7+
def truncate(s, length=25):
8+
if len(s) <= length:
9+
return s
10+
return s[:length - 3] + '...'
11+
12+
13+
class LCInfo(object):
14+
def __init__(self, locale, currency, columns):
15+
self.columns = columns
16+
if locale == 'en_US':
17+
headers = ['Date', 'Description', 'Change']
18+
self.datefmt = '{0.month:02}/{0.day:02}/{0.year:04}'
19+
self.cur_fmt = u'{}{}{}{}'
20+
self.lead_neg = '('
21+
self.trail_neg = ')'
22+
self.thousands = ','
23+
self.decimal = '.'
24+
elif locale == 'nl_NL':
25+
headers = ['Datum', 'Omschrijving', 'Verandering']
26+
self.datefmt = '{0.day:02}-{0.month:02}-{0.year:04}'
27+
self.cur_fmt = u'{1} {0}{2}{3}'
28+
self.lead_neg = '-'
29+
self.trail_neg = ' '
30+
self.thousands = '.'
31+
self.decimal = ','
32+
fmt = ROW_FMT.format('<', *columns)
33+
self.headers = fmt.format(*headers)
34+
self.cur_symbol = {
35+
'USD': '$',
36+
'EUR': u'€',
37+
}.get(currency)
38+
39+
def number(self, n):
40+
n_int, n_float = divmod(abs(n), 100)
41+
n_int_parts = []
42+
while n_int > 0:
43+
n_int, x = divmod(n_int, 1000)
44+
n_int_parts.insert(0, str(x))
45+
return '{}{}{:02}'.format(
46+
self.thousands.join(n_int_parts) or '0',
47+
self.decimal,
48+
n_float,
49+
)
50+
51+
def currency(self, change):
52+
return self.cur_fmt.format(
53+
self.lead_neg if change < 0 else '',
54+
self.cur_symbol,
55+
self.number(change),
56+
self.trail_neg if change < 0 else ' ',
57+
)
58+
59+
def entry(self, entry):
60+
date, change, desc = entry
61+
fmt = ROW_FMT.format('>', *self.columns)
62+
return fmt.format(
63+
self.datefmt.format(date),
64+
truncate(desc),
65+
self.currency(change),
66+
)
67+
68+
def table(self, entries):
69+
lines = [self.headers]
70+
lines.extend(map(self.entry, sorted(entries)))
71+
return '\n'.join(lines)
72+
73+
74+
def create_entry(date, description, change):
75+
return (
76+
datetime.strptime(date, '%Y-%m-%d'),
77+
change,
78+
description
79+
)
80+
81+
82+
def format_entries(currency, locale, entries):
83+
columns = (10, 25, 13)
84+
lcinfo = LCInfo(locale, currency, columns)
85+
return lcinfo.table(entries)

0 commit comments

Comments
 (0)