Functional programming extension of Melodie
Project description
MelodieFunctFlow
Description
Functional programming extension of Melodie. Using this tool, you can make full use of the advantages of functional programming, making coding more convenient and debugging easier.
Getting Started
Dependencies
- Python 3.8+
- No compulsory dependency. But to use
show_progressto show the progress,tqdmis required.
Installing
pip install MelodieFuncFlow
Basic Tutorial
To take advantage of functional programming in
interactive mode, we strongly suggest to use
IPython/Jupyter instead of barely Python.
Also, for script-based programming, MelodieFuncFlow has optimized type hints for the autocompletion of many common editors. But for convenience, this documentation only used interactive environment for demonstration.
Creating Generator
The core class is MelodieGenerator.
By the code below, we imported MelodieGenerator,
and created one instance g.
In [1]: from MelodieFuncFlow import MelodieGenerator
In [2]: g = MelodieGenerator([1, 2, 3, 4, 5])
Iterate Through The MelodieGenerator
This generator is also an Iterable:
In [3]: [item for item in MelodieGenerator([1, 2, 3, 4, 5])]
Out[3]: [1, 2, 3, 4, 5]
Basic Conversions
MelodieGenerator could be converted to list or set.
to_listput elements to a new list in the order theMelodieGeneratorgeneratesto_setwill put elements to a set, eliminating repetitions of elements.
In [4]: MelodieGenerator([1, 2, 3, 1, 2]).to_list()
Out[4]: [1, 2, 3, 1, 2]
In [5]: MelodieGenerator([1, 2, 3, 1, 2]).to_set()
Out[5]: {1, 2, 3}
Of course, if some elements in generator are not
hash-able, to_set method will raise an TypeError:
In [6]: MelodieGenerator([{"a": 123}, {"a": 456}, {"a": 123}]).to_set()
↓↓↓↓↓↓ raised an error ↓↓↓↓↓↓
TypeError: unhashable type: 'dict'
Accessing the Elements of the Generator
We can access the first element by head or element subscription:
In [2]: g = MelodieGenerator([1, 2, 3, 4, 5])
In [3]: g.head()
Out[3]: 1
In [4]: g.head()
Out[4]: 2
In [5]: g.head()
Out[5]: 3
In [6]: MelodieGenerator([1, 2, 3, 4, 5])[3]
Out[6]: 4
Also, we can slice this generator, and this method
returns a new MelodieGenerator.
The slicing rules are:
-
If two arguments
bound, rboundpassed in:boundmust satisfybound >= 0- If
rbound >= 0, the interval is[bound, rbound) - If
rbound == -1, the interval is:[bound, +∞). (<0values except-1had not been supported yet)
-
If only one argument
boundpassed in, the interval is[0, bound) -
slicehas equivalent subscriptions, for example:g.slice(1, 3)<=>g[1:3]g.slice(3)<=>g[:3]g.slice(1, -1)<=>g[1:]- Note that the returning value of integer subscriptions like
g[3]will not return theMelodieGeneratorbut the 4th element.
The slicing mechanism is demonstrated below:
In [2]: MelodieGenerator([1, 2, 3, 4, 5]).slice(1, 3).to_list()
Out[2]: [2, 3]
In [2]: MelodieGenerator([1, 2, 3, 4, 5])[1:3].to_list()
Out[2]: [2, 3]
In [3]: MelodieGenerator([1, 2, 3, 4, 5]).slice(1, -1).to_list()
Out[3]: [2, 3, 4, 5]
In [4]: MelodieGenerator([1, 2, 3, 4, 5]).slice(2).to_list()
Out[4]: [3, 4, 5]
Mapping
This generator supports mapping, which is a common method used in functional programming.
Mapping on the MelodieGenerator will not change
the original generator, but create a new
generator containing the return values
of the operator function in order.
In [7]: MelodieGenerator([1, 2, 3, 4, 5]).map(lambda x: x * x)
Out[7]: <MelodieFuncFlow.functional.MelodieGenerator at 0x28a5a641640>
In [8]: MelodieGenerator([1, 2, 3, 4, 5]).map(lambda x: x * x).to_list()
Out[8]: [1, 4, 9, 16, 25]
Note: The
to_listmethod called above is intended to let the generator go on. As the values in theMelodieGeneratoris evaluated lazily, only callingmapmethod will just create a new generator, which will not execute automatically (demonstrated inOut[7]).
If the elements inside MelodieGenerator are tuples
of same size, star_map method can be used for better
organizing the operator function. Here is a demo
to convert students' grades from 100 to 5:
In [9]: MelodieGenerator([("Alice", 98),
("Bob", 95),
("Carol", 92)]) \
.star_map(lambda name, grade: grade*5/100) \
.to_list()
Out[9]: [4.9, 4.75, 4.6]
Code In [9] has the same effect of the code below.
As we can see, coding with star_map is easier for
understanding the operator.
MelodieGenerator([("Alice", 98),
("Bob", 95),
("Carol", 92)]) \
.map(lambda student: student[1]*5/100) \
.to_list()
Filtering
Filtering is another common operation in functional
programming. The filter method is used to
extract elements from the iterator that meet
specific conditions.
The filter method does not change the original
generator, but returns a new generator with all
the elements that satisfy the condition.
In [10]: MelodieGenerator([1, 2, 3, 4, 5]).filter(lambda x: x % 2 == 0).to_list()
Out[10]: [2, 4]
Also, MelodieGenerator supports star_filter, unpacking
the tupled elements to match the arguments of condition function.
In [11]: MelodieGenerator([(1, 0), (0, -5), (0, -3)]).star_filter(lambda x, y: y < 0).to_list()
Out[11]: [(0, -5), (0, -3)]
Reducing
Brief Introduction to Reducing
In functional programming, reducing refers to the process of applying a binary operator function (a function that takes two arguments) to all elements of an iterable (such as a list or array), in a cumulative way, so as to reduce the iterable to a single output value.
The binary function is applied to the first two elements of the iterable, then to the result and the next element, and so on, until only one element remains. This final element is the output value of the reduction.
Here is an example written with standard libraries.
suppose we have a list of numbers and we want to find
their product.
the reduce
is performed on the list numbers.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce((lambda x, y: x * y), numbers)
print(product) # Output: 120
The calculation steps are listed below. (
lambda x, y: x * y is written as product(x, y)):
- product(1, 2) = 2, remaining values were [3, 4, 5]
- product(2, 3) = 6, remaining values were [4, 5]
- product(6, 4) = 24, remaining values were [5]
- product(24, 5) = 120, remaining values were []
So finally we got the value 120.
Example for MelodieGenerator
In MelodieGenerator, there are two types of reduction method that we can use, the comparison is shown in the following table.
Note that in the following table:
-
Element type of
MelodieGeneratorare marked asT. -
(A, B) => Rindicates a python function with two argument having typesAandBaccordingly, and returning typeR.
| Characteristics \ Reduction Type | reduce |
fold_left |
|---|---|---|
| Reducer Function Type | (T, T) => T |
(A, T) => A |
| Recommended Reducer Kind | Pure and simple, without any side effect | Complex and might have side effect |
An reducing example for MelodieGenerator is as follows.
>>> MelodieGenerator([-1, 1, 3, 5]).reduce(lambda x, y: x + y)
8
>>> MelodieGenerator([[-1], [1, 3, 5]]).reduce(lambda x, y: x + y)
[-1, 1, 3, 5]
def add_to_dict(dic, elem):
k, v = elem
dic[k] = v
return dic
>>> MelodieGenerator([("Alice", 98), ("Bob", 76)]).fold_left(add_to_dict, {})
{'Alice': 98, 'Bob': 76}
Extra Jobs
Extra jobs by extra_job in MelodieGenerator is a kind of
operation that will not interfere the computation flow.
For example, in code In [13] and In [14], a print
was inserted into the computation flow in two different
positions, and middle results
were printed.
However, the result Out[13] and Out[14] are also the
same as the original result Out[12]:
In [12]: MelodieGenerator([-1, 1, 3, 5]).map(lambda value: value * 2).to_list()
Out[12]: [-2, 2, 6, 10]
In [13]: MelodieGenerator([-1, 1, 3, 5]).extra_job(print).map(lambda value: value * 2).to_list()
-1
1
3
5
Out[13]: [-2, 2, 6, 10]
In [14]: MelodieGenerator([-1, 1, 3, 5]).map(lambda value: value * 2).extra_job(print).to_list()
-2
2
6
10
Out[14]: [-2, 2, 6, 10]
Indexed Filtering, Mapping and Extra Job
Sometimes it is important for the mapping/filtering/extra-job operator
to get the index of current element.
To do this, we implemented indexed_filter, indexed_map and indexed_extra_job,
providing an index together with each element.
In [15]: MelodieGenerator([-1, 1, 3, 5]).indexed_map(lambda index, value: (index, value)).to_list()
Out[15]: [(0, -1), (1, 1), (2, 3), (3, 5)]
In [16]: MelodieGenerator([1, 2, 3, 4]).indexed_filter(lambda index, value: index > 1 and value % 2 == 0).to_list()
Out[16]: [4]
In [17]: MelodieGenerator([-1, 1, 3, 5]).indexed_extra_job(print).to_list()
0 -1
1 1
2 3
3 5
Out[17]: [-1, 1, 3, 5]
Note: Currently, indexed map/filter with tuple unpacks have not been implemented.
Freeze the Generator
You might have noticed that in the examples above,
MelodieGenerator was evaluated right after the creation.
That is because this generator is irreversible, we cannot
evaluate it for the second time
(e.g., by head, to_list, to_set or slice) :
>>> g = MelodieGenerator([-1, 1, 3, 5])
>>> g.to_list()
[-1, 1, 3, 5]
>>> g.to_list()
[]
If you want to evaluate it repeatedly, you could freeze it.
freeze will generate a MelodieFrozenGenerator object,
which is a subclass of MelodieGenerator.
Most of the method implementations in MelodieFrozenGenerator
are identical to MelodieGenerator, and it can be evaluated
multiple times.
>>> g = MelodieGenerator([-1, 1, 3, 5]).freeze()
>>> g.to_list()
[-1, 1, 3, 5]
>>> g.to_list()
[-1, 1, 3, 5]
Sorting the Frozen Generator
Besides, frozen generator can be sorted.
Method sort will return a new
MelodieFrozenGenerator with elements sorted.
>>> g = MelodieGenerator([1, 3, 4, 2, 5]).freeze()
>>> g.sort(lambda x: x).to_list() # ascending by default
[1, 2, 3, 4, 5]
>>> g.sort(lambda x: x, reverse=True).to_list() # descending
[5, 4, 3, 2, 1]
To sort by a custom comparator function:
def cmp(a, b):
if a == b:
return 0
elif a > b:
return 1
else:
return -1
>>> g = MelodieGenerator([1, 3, 4, 2, 5]).freeze()
>>> g.relsort(cmp).to_list() # ascending by default
[1, 2, 3, 4, 5]
>>> g.relsort(cmp, reverse=True).to_list() # descending
[5, 4, 3, 2, 1]
Notes to MelodieFrozenGenerator
Note 1:
headmethod on frozen generator will always return the first value
>>> g = MelodieGenerator([1,2,3,4,5]).freeze()
>>> g.head()
1
>>> g.head()
1
Note 2: Operations like
filterandmaponMelodieFrozenGeneratorwill return a generalMelodieGenerator. To freeze the filtered/mapped result, please use.freeze()/.fmethod on the new generator.
In [1]: from MelodieFuncFlow import *
In [2]: g_freeze = MelodieGenerator([1, 2, 3, 4, 5]).f
In [3]: g_mapped = g_freeze.map(lambda x: x*x)
In [4]: g_mapped.head()
Out[4]: 1
In [5]: g_mapped.head()
Out[5]: 4
In [6]: g_mapped.head()
Out[6]: 9
In [7]: g_mapped_frozen = g_freeze.map(lambda x: x*x).f
In [8]: g_mapped_frozen.head()
Out[8]: 1
In [9]: g_mapped_frozen.head()
Out[9]: 1
Showing Progress
Note:
tqdmmust be installed. If you are usingIPython,ipywidgetsis required as well. Just execute:
pip install tqdm
pip install ipywidgets # Used when using IPython/Jupyter.
To show progress, just use show_progress method.
In [19]: import time
In [20]: MelodieGenerator([-1, 1, 3, 5]).extra_job(lambda x: time.sleep(1)).show_progress().to_list()
4it [00:04, 1.01s/it]
Out[20]: [-1, 1, 3, 5]
For frozen generator, as the total value is known,
show_progress will show the progress bar.
>>> MelodieGenerator([-1, 1, 3, 5]).freeze().show_progress().extra_job(lambda x: time.sleep(1)).to_list()
100% ██████████████████████████████████████████████| 4/4 [00:04<00:00, 1.01s/it]
Version History
-
0.2.0
- Distinguished
reduceandfold_leftmethods. - Added slicing methods for element accessing.
- Distinguished
-
0.1.0
- Initial Release
License
This project is licensed under the MIT License - See the LICENSE.md file for details
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file MelodieFuncFlow-0.3.0-py3-none-any.whl.
File metadata
- Download URL: MelodieFuncFlow-0.3.0-py3-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ba91be2f6ca87e865ee99cb7bd8ad263f77f26ffcba37d1e50eeda4de3d55de
|
|
| MD5 |
f0c8e62f885796c7f116470dee2ed371
|
|
| BLAKE2b-256 |
7e0e0d6f2bd7c6e2539ece72fcee64c52f4730e9e5d264d47db1db423ae52b53
|