Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-yaml
- id: check-added-large-files
Expand All @@ -23,6 +23,6 @@ repos:
pass_filenames: false

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.2
rev: v0.6.0
hooks:
- id: ruff
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### New Features

- Implemented the `plus` function that concatenates iterables and single elements as well as other sequences
- Added `first_or_none`, a function to match `head_or_none`
- Added run_test.sh script
- Added [parametrize](https://pypi.org/project/parametrize/) for parameterized unit tests
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ complete documentation reference
| `tail()` | Returns sequence without the first element | transformation |
| `inits()` | Returns consecutive inits of sequence | transformation |
| `tails()` | Returns consecutive tails of sequence | transformation |
| `plus(other)` | Concatenates this sequence with `other` | transformation |
| `append(other)` | Synonym for `plus()` | transformation |
| `zip(other)` | Zips the sequence with `other` | transformation |
| `zip_with_index(start=0)` | Zips the sequence with the index starting at `start` on the right side | transformation |
| `enumerate(start=0)` | Zips the sequence with the index starting at `start` on the left side | transformation |
Expand Down
30 changes: 30 additions & 0 deletions functional/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,36 @@ def tails(self) -> Sequence[Self]:
"""
return self._transform(transformations.tails_t(_wrap))

def plus(self, other: Sequence[Any] | Iterable[Any] | Any) -> Sequence[Any]:
"""
Concatenates sequence with other.

>>> seq([1, 2, 3]).plus(4)
[1, 2, 3, 4]

>>> seq([1, 2, 3]).plus([4, 5, 6])
[1, 2, 3, 4, 5, 6]

:param other: single element or sequence to concatenate
:return: concatenated sequence with other
"""
return self._transform(transformations.plus_t(other))

def append(self, other: Sequence[Any] | Iterable[Any] | Any) -> Sequence[Any]:
"""
Concatenates sequence with other.

>>> seq([1, 2, 3]).plus(4)
[1, 2, 3, 4]

>>> seq([1, 2, 3]).plus([4, 5, 6])
[1, 2, 3, 4, 5, 6]

:param other: single element or sequence to concatenate
:return: concatenated sequence with other
"""
return self._transform(transformations.plus_t(other, "append"))

@overload
def cartesian(self, /) -> Sequence[tuple[_T_co]]:
...
Expand Down
18 changes: 18 additions & 0 deletions functional/test/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from functional.transformations import name
from functional import seq, pseq

from parametrize import parametrize # type: ignore

Data = namedtuple("Data", "x y")


Expand Down Expand Up @@ -262,6 +264,22 @@ def test_tails(self):
self.assertIteratorEqual(l.tails(), expect)
self.assertIteratorEqual(l.tails().map(lambda s: s.sum()), [6, 5, 3, 0])

@parametrize(
"sequence, other, expected",
[
([1, 2, 3], 4, [1, 2, 3, 4]),
([1, 2], [3, 4], [1, 2, 3, 4]),
([1, 2], seq(3, 4), [1, 2, 3, 4]),
([1, 2], [[3, 4]], [1, 2, [3, 4]]),
([1, 2], [], [1, 2]),
([], [], []),
],
)
def test_plus(self, sequence, other, expected):
result = self.seq(sequence).plus(other)
self.assertIteratorEqual(expected, result)
self.assert_type(result)

def test_drop(self):
s = self.seq([1, 2, 3, 4, 5, 6])
expect = [5, 6]
Expand Down
4 changes: 4 additions & 0 deletions functional/test/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def type_checking() -> None:

t_tails: Sequence[Sequence[int]] = seq([1, 2, 3]).tails()

t_plus: Sequence[int] = seq([1, 2, 3]).plus(seq([4, 5, 6]))

t_append: Sequence[int] = seq([1, 2, 3]).append(seq([4, 5, 6]))

t_cartesian: Sequence[tuple[int, int]] = seq.range(2).cartesian(range(2))

t_drop: Sequence[int] = seq([1, 2, 3, 4, 5]).drop(2)
Expand Down
25 changes: 24 additions & 1 deletion functional/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
import collections
import types
from collections.abc import Callable
from collections.abc import Callable, Iterable

from functional.execution import ExecutionStrategies

Expand Down Expand Up @@ -376,6 +376,29 @@ def tails_t(wrap):
)


def plus_t(other, function_name="plus"):
"""
Transformation for Sequence.plus
:param other: single element or sequence to concatenate
:param function_name: name of pipeline function
:return: transformation
"""

def plus(sequence):
class_name = f"{other.__class__.__module__}.{other.__class__.__name__}"
if class_name == "functional.pipeline.Sequence":
return sequence + other.to_list()
if isinstance(other, Iterable):
return sequence + other
return sequence + [other]

return Transformation(
f"{function_name}({other})",
plus,
None,
)


def union_t(other):
"""
Transformation for Sequence.union
Expand Down
110 changes: 78 additions & 32 deletions run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

# campare_versions(v1, v2)
# Compares two 3-part sematic versions, returning -1 if v1 is less than v2, 1 if v1 is greater than v2 or 0 if v1 and v2 are equal.
compare_versions() {
local v1=(${1//./ })
local v2=(${2//./ })
Expand All @@ -18,47 +20,91 @@ compare_versions() {
echo 0
}

python_version=$(python --version | grep -Eo \[0-9\]\.\[0-9\]+\.\[0-9\]+)
echo "Python version: $python_version"
# get_version_in_pipx(package_name)
# Gets the standard semantic version of a package installed in Pipx if installed.
get_version_in_pipx() {
local package_name=$1
local version
version=$(pipx list | grep -oP "$package_name"\\s+\\K\[0-9\]+\.\[0-9\]+\.\[0-9\]+)
echo "$version"
}

# capitalise(word)
# Capitalizes a word.
capitalize() {
local word=$1
echo "$(tr '[:lower:]' '[:upper:]' <<< ${word:0:1})${word:1}"
}

# print_version(name, version, capitalize, width)
# Prints the version of the software with option to capitalize name and change left-aligned padding.
print_version() {
local name=$1
local version=$2
local capitalize=${3:-true}
local width=${4:-19}
name=$([[ $capitalize == 'true' ]] && capitalize "$name" || echo "$name")
printf "%-${width}s %s\n" "$name version:" "$version"
}

# install_package(package_name)
# Installs specified package with Pipx or displays the its version if it's already installed.
install_package() {
local package_name=$1
local capitalize=${2:-true}

pipx_version=$(pipx --version)
if [[ -z "$pipx_version" ]]; then
echo "Pipx is not installed"
exit 1
else
echo "Pipx version: $pipx_version"
fi
local version
version=$(get_version_in_pipx "$package_name")
if [[ -n $version ]]; then
print_version "$package_name" "$version" "$capitalize"
else
pipx install "$package_name"
pipx ensurepath
fi
}

main() {
python_version=$(python --version | grep -Eo \[0-9\]\.\[0-9\]+\.\[0-9\]+)
print_version "Python" "$python_version"

pipx_version=$(pipx --version)
if [[ -z "$pipx_version" ]]; then
echo "Please install Pipx before running this script."
exit 1
else
print_version "Pipx" "$pipx_version"
fi

poetry_version=$(pipx list | grep -oP poetry\\s+\\K\[0-9\]\.\[0-9\]+\.\[0-9\]+)
if [[ -n $poetry_version ]]; then
echo "Poetry version: $poetry_version"
else
pipx install poetry
fi
install_package "poetry"

echo
install_package "pre-commit" false

if ! poetry install; then
poetry lock
poetry install
fi
echo

echo
if ! poetry install; then
poetry lock
poetry install
fi

if [[ $(compare_versions "$python_version" "3.12.0") -lt 0 ]]; then
poetry run pylint functional
else
poetry run ruff check functional
fi
echo

echo
if [[ $(compare_versions "$python_version" "3.12.0") -lt 0 ]]; then
poetry run pylint functional
else
poetry run ruff check functional
fi

poetry run black --diff --color --check functional
echo

echo
poetry run black --diff --color --check functional

poetry run mypy functional
echo

echo
poetry run mypy functional

echo

poetry run pytest
}

poetry run pytest
main "$@"