189 lines
7.0 KiB
Markdown
189 lines
7.0 KiB
Markdown
# EzSequence
|
|
A quick and dirty sequence generator for Python.
|
|
|
|
## About
|
|
- EzSequence uses a fluent interface to strive for a self-documenting code approach. It works like the _Builder_ pattern.
|
|
- EzSequence produces iterators that you can use in your FOR loops, your list comprehensions or to be converted to a list all at once.
|
|
- EzSequence supports infinite sequences but has safety built in.
|
|
|
|
## Basic Usage
|
|
```
|
|
from ezsequence import *
|
|
|
|
seq = sequence_from(1).to(5)
|
|
print(seq)
|
|
```
|
|
|
|
## Full Usage
|
|
|
|
### Static Methods
|
|
|
|
There is only one!
|
|
|
|
`sequence_from()`
|
|
|
|
This function starts your sequence with an initial value and some default behavior. From there, you can customize it with any of the fluent methods listed below.
|
|
|
|
### Fluent Methods
|
|
|Method| Description| Example| Output|
|
|
|---- |---- |---- |---- |
|
|
|.by(increment: int) |Optional. Specifies a gap between values. Default is 1. Cannot be used with `.with_increment()`.|`sequence_from(1) .to(5) .by(2)`|[1, 3, 5]|
|
|
|.with_increment(increment_func: Callable[[int, int], int])| Optional. Specifies the translation of the value on each iteration. By default, value is incremented by 1. |`sequence .from(1) .to(5) .with_increment(lambda index, value: value + 2)` | [1, 3, 5] |
|
|
|.until(condition: Callable[[int, int], int]) |Optional. Specifies a terminating condition in the sequence. Value is inclusive. In other words, once the condition is met, the next iteration will end the sequence. Multiple .until() calls are permitted. Otherwise, it is the same as `.to()`. |`sequence_from(1) .until(lambda index, value: value == 5)`| [1, 2, 3, 4, 5]|
|
|
|.as_long_as(condition: Callable[[int, int], int]) |Optional. Specifies a terminating condition in the sequence. Value is exclusive. In other words, once the condition is violated, the current iteration will end the sequence. Multiple .as_long_as() calls are permitted. |`sequence_from(1) .as_long_as(lambda index, value: value <= 5)`| [1, 2, 3, 4, 5]|
|
|
|.when(condition: Callable[[int, int], int]) |Optional. Specifies a condition for including this value in the sequence output. By default, all values are included. Multiple .when() calls are supported. Evluations do not terminate the sequence. |`sequence_from(1) .to(5) .when(lambda index, value: value % 2 == 1)`| [1, 3, 5]|
|
|
|.not_when(condition: Callable[[int, int], int]) |Optional. Specifies a condition for excluding values, overriding any .when() condition that included them. By default, there are no exclusions. Multiple .not_when() calls are supported. Evaluations do not terminate the sequence.|`sequence_from(1) .to(5) .not_when(lambda index, value: value % 2 == 0)`| [1, 3, 5]
|
|
|.safely(max_attempts: int)|Optional. Prevents unintended infinite loops caused by .when() and .not_when() filters which will hang in the _while_ loop that searches for the next unfiltered vlaue in the sequence. Takes an optional parameter for the max number of attempts to find an unfiltered value. Default max attempts is 10000. |`sequence_from(1) .to(5) .when(lambda index, value: value < 0) .safely(100)`|Exception: Safe iteration limit exceeded. 100 attempts exceeded.|
|
|
|.unsafely()|Optional. Allows unintended infinite loops caused by `.when()` and `.not_when()` filters which may hang in the _while_loop that searchs for the next unfiltered value in the sequence. |`sequence_from(1) .with_increment(lambda index, value: random.Random() .randint(1, 1000)) .as_long_as(lambda index, value: value != 50) .unsafely()` |(No exception but may hang indefinitely)|
|
|
|.translate_as(projection: Callable[[int, int], any]) |Optional. Specifies a function to convert each element that is returned by the iterator. | `sequence_from(5) .to(9) .translate_as(lambda index, value: value - 4)`| [1, 2, 3, 4, 5]|
|
|
|.reset() |Rewinds the sequence to the beginning so it can be reused. |`seq.reset()` | N/A|
|
|
|
|
|
|
## More Examples
|
|
|
|
Odds
|
|
```
|
|
seq = sequence_from(1).to(5) \
|
|
.when(lambda index, value: value % 2 == 1)
|
|
```
|
|
|
|
Evens
|
|
```
|
|
seq = sequence_from(1).to(5) \
|
|
.when(lambda index, value: value % 2 == 0)
|
|
```
|
|
|
|
Backward
|
|
```
|
|
seq = sequence_from(1) \
|
|
.by(-1) \
|
|
.until(lambda index, value: value == -5)
|
|
```
|
|
|
|
Random Bytes
|
|
```
|
|
import random
|
|
rnd = random.Random()
|
|
seq = sequence_from(1).to(8) \
|
|
.translate_as(lambda index, value: rnd.randint(0, 255))
|
|
data = list(seq)
|
|
```
|
|
|
|
Random AlphaNumeric Text
|
|
```
|
|
import random
|
|
rnd = random.Random()
|
|
seq = sequence_from(1).to(8) \
|
|
.translate_as(lambda index, value: chr(rnd.randint(1, 26) + 64))
|
|
data = "".join(seq)
|
|
```
|
|
|
|
Reset Example
|
|
```
|
|
import random
|
|
rnd = random.Random()
|
|
seq = sequence_from(1).to(8) \
|
|
.translate_as(lambda index, value: chr(rnd.randint(1, 26) + 64))
|
|
|
|
data1 = "".join(seq)
|
|
seq.reset()
|
|
data2 = "".join(seq)
|
|
```
|
|
|
|
|
|
Random CSV values
|
|
```
|
|
import random
|
|
rnd = random.Random()
|
|
|
|
field_count = 5
|
|
row_count = 3
|
|
|
|
def create_field_data(data_seq):
|
|
data_seq.reset()
|
|
return "".join(data_seq)
|
|
|
|
def create_row(field_seq):
|
|
field_seq.reset()
|
|
return ",".join(field_seq)
|
|
|
|
data_seq = sequence_from(1).to(8) \
|
|
.translate_as(lambda index, value: chr(rnd.randint(1, 26) + 64))
|
|
|
|
field_seq = sequence_from(1).to(field_count) \
|
|
.translate_as(lambda index, value: "\"" + create_field_data(data_seq) + "\"")
|
|
|
|
row_seq = sequence_from(1).to(row_count)
|
|
|
|
for row in row_seq:
|
|
print(create_row(field_seq))
|
|
```
|
|
|
|
Guid
|
|
```
|
|
import random
|
|
rnd = random.Random()
|
|
section_generators = \
|
|
[
|
|
sequence_from(1).to(8).translate_as(lambda index, value: hex(rnd.randint(0, 15))[2:3]),
|
|
sequence_from(1).to(4).translate_as(lambda index, value: hex(rnd.randint(0, 15))[2:3]),
|
|
sequence_from(1).to(4).translate_as(lambda index, value: hex(rnd.randint(0, 15))[2:3]),
|
|
sequence_from(1).to(4).translate_as(lambda index, value: hex(rnd.randint(0, 15))[2:3]),
|
|
sequence_from(1).to(12).translate_as(lambda index, value: hex(rnd.randint(0, 15))[2:3])
|
|
]
|
|
|
|
sections = ["".join(generator) for generator in section_generators]
|
|
guid = "-".join(sections)
|
|
print(guid)
|
|
```
|
|
|
|
Factorials
|
|
```
|
|
from functools import reduce
|
|
|
|
factorial_of = 5
|
|
factors = sequence_from(factorial_of) \
|
|
.with_increment(lambda index, value: value - 1) \
|
|
.until(lambda index, value: value == 1)
|
|
factorial = reduce(lambda running, value: running * value, factors)
|
|
|
|
print(list(factors.reset()))
|
|
print(factorial)
|
|
```
|
|
|
|
Fibonacci
|
|
```
|
|
import itertools
|
|
|
|
class Fibonacci:
|
|
def __init__(self):
|
|
self.previous2 = 0
|
|
self.previous1 = 1
|
|
def calculate_fibonacci(self, value):
|
|
next_value = self.previous1 + self.previous2
|
|
self.previous2 = self.previous1
|
|
self.previous1 = next_value
|
|
return next_value
|
|
def sequence(self):
|
|
seq = sequence_from(1).to(20) \
|
|
.translate_as(lambda index, value: self.calculate_fibonacci(value)) \
|
|
.as_long_as(lambda index, value: value < 500000000)
|
|
return itertools.chain([1], seq)
|
|
|
|
fib = list(Fibonacci().sequence())
|
|
print(fib)
|
|
```
|
|
|
|
Multiple _when_, _not_when_, _until_ and _as_long_as_
|
|
```
|
|
seq = sequence_from(1).to(50) \
|
|
.by(3) \
|
|
.when(lambda index, value: value >= 10) \
|
|
.when(lambda index, value: value % 2 == 1) \
|
|
.when_not(lambda index, value: value == 25) \
|
|
.as_long_as(lambda index, value: value <= 40) \
|
|
.as_long_as(lambda index, value: value != 38) \
|
|
.safely(10)
|
|
|
|
print(list(seq))
|
|
``` |