diff options
author | eug-vs <eugene@eug-vs.xyz> | 2021-08-18 21:00:28 +0300 |
---|---|---|
committer | eug-vs <eugene@eug-vs.xyz> | 2021-08-18 21:00:40 +0300 |
commit | 37f4e018c239867d70fcdc689200bff2cbb96eed (patch) | |
tree | e52a4a09fa6e57886efe483eae21239c595fe79a /blog/2021-08-18.md | |
parent | 0ab4193d1509dd0fcc75932a03870f6a03e6acca (diff) | |
download | eug-vs-xyz-37f4e018c239867d70fcdc689200bff2cbb96eed.tar.gz |
blog: python in real life
Diffstat (limited to 'blog/2021-08-18.md')
-rw-r--r-- | blog/2021-08-18.md | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/blog/2021-08-18.md b/blog/2021-08-18.md new file mode 100644 index 0000000..6567f35 --- /dev/null +++ b/blog/2021-08-18.md @@ -0,0 +1,83 @@ +# Python :python: in real life +Recently I had a chance to solve an accounting problem. And as always, Python saved me some time and nerve cells. + +## The problem +In accounting you sum things up a lot. And sometimes, you forget what were the terms you used to compute the sum, especially when there are a lot of them. This is exactly the scenario I faced recently: + - There's a price list for certain items + - There's a total sum that was made up from **some** of these prices + - You have to find out what prices were used to calculate total sum + +And this problem appears many times, same price list but different totals. I immediately knew I am not gonna do this manually, so first thing I did was creating a new script and writing my first two lines with the input data: +```python +prices = [561.6, 134.24, 561.6, 345.6, 2292, 22.08, 61.2, 87.41, 37.08, 37.8, 25.44, 246] +totals = [271.01, 1078.61, 3333.44] +``` + +## Thinking +Now, let's analyze the problem a bit. We'll try solving the problem for the total of `3333.44` in this example. + +There's **12** prices in the list. Now, let's suppose our total was made up from 2 prices. It could be any 2 numbers from the list, in fact there are a lot of possible pairs! To be precise, there are **66** of them! Luckily Python has an amazing package `itertools` which allows you to work with combinatorics. We can now list all these pairs: + +```python +from itertools import combinations + + +prices = [561.6, 134.24, 561.6, 345.6, 2292, 22.08, 61.2, 87.41, 37.08, 37.8, 25.44, 246] +pairs = list(combinations(prices, 2)) +print(pairs) +``` + +Output: +```python +[(561.6, 134.24), (561.6, 561.6), (561.6, 345.6), (561.6, 2292), (561.6, 22.08), (561.6, 61.2), (561.6, 87.41), (561.6, 37.08), (561.6, 37.8), (561.6, 25.44), (561.6, 246), (134.24, 561.6), (134.24, 345.6), (134.24, 2292), (134.24, 22.08), (134.24, 61.2), (134.24, 87.41), (134.24, 37.08), (134.24, 37.8), (134.24, 25.44), (134.24, 246), (561.6, 345.6), (561.6, 2292), (561.6, 22.08), (561.6, 61.2), (561.6, 87.41), (561.6, 37.08), (561.6, 37.8), (561.6, 25.44), (561.6, 246), (345.6, 2292), (345.6, 22.08), (345.6, 61.2), (345.6, 87.41), (345.6, 37.08), (345.6, 37.8), (345.6, 25.44), (345.6, 246), (2292, 22.08), (2292, 61.2), (2292, 87.41), (2292, 37.08), (2292, 37.8), (2292, 25.44), (2292, 246), (22.08, 61.2), (22.08, 87.41), (22.08, 37.08), (22.08, 37.8), (22.08, 25.44), (22.08, 246), (61.2, 87.41), (61.2, 37.08), (61.2, 37.8), (61.2, 25.44), (61.2, 246), (87.41, 37.08), (87.41, 37.8), (87.41, 25.44), (87.41, 246), (37.08, 37.8), (37.08, 25.44), (37.08, 246), (37.8, 25.44), (37.8, 246), (25.44, 246)] +``` + +Here we converted the result of `combinations(prices, 2)` to a `list`, but that's actually only to be able to `print` it. In fact, the original result was a [generator](https://wiki.python.org/moin/Generators) - it wasn't storing the whole list in memory, only the instructions how you should iterate over it. And that's exactly what we want to do - iterate over this list, taking the sum and see if it matches the result, something like this: + +```python +for prices_combination in combinations(prices, 2): + if sum(prices_combination) == total: + return prices_combination +``` + +# The solution +Of course, our `total` doesn't have to be a sum of 2 terms, it can be 3, 4, 5 or more. Also, `total` might not exactly equal the sum, there can be a slight difference due to rounding or human error. Let's say this difference is no more than `0.01`. This will be the final solution: +```python +#!/usr/bin/python +from itertools import combinations + + +prices = [561.6, 134.24, 561.6, 345.6, 2292, 22.08, 61.2, 87.41, 37.08, 37.8, 25.44, 246] +totals = [271.01, 1078.61, 3333.44] + +def decompose(total, terms): + for count in range(1, len(terms)): + for combination in combinations(terms, count): + if abs(total - sum(combination)) < 0.01: + return combination + +for total in totals: + combination = decompose(total, prices) + print(f'{total} = sum{combination}') +``` + +Which will produce a nice output in less then **0.03** seconds: +```python +271.01 = sum(22.08, 61.2, 87.41, 37.08, 37.8, 25.44) +1078.61 = sum(561.6, 22.08, 61.2, 87.41, 37.08, 37.8, 25.44, 246) +3333.44 = sum(561.6, 134.24, 345.6, 2292) +``` + +Notice that the only list stored in memory was the original one. Each combination only appeared in memory when it was needed to calculate the sum, and immediately disappeared after that. That's why it's (relatively) fast! + +## Takeaway +Of course this script isn't by any means optimal! Essentially it's a brute force, we are just checking all the possible combinations. But we are doing it in a **clean and efficient way** (credit to generators). + +When you are solving real-life problems, you are not in algorithms class and you don't have to write a hardcore algo, you just have to correctly utilize amazing toolkit that this language has. I've spend no more than 5 minutes writing this and I already have the answer to my problem - that's the beauty of Python :python:! + +## Now imagine writing and compiling a C++ program for that! +I can only describe it with this image: + +![meme](/public/brainlet-dreams-big-brain.png) + +Programming languages are tools, and different situations require different tools. |