Skip to content

Commit

Permalink
Added in a lot of code from samwell.
Browse files Browse the repository at this point in the history
  • Loading branch information
tfenne committed Nov 19, 2022
1 parent d722b42 commit 314fe06
Show file tree
Hide file tree
Showing 18 changed files with 2,722 additions and 388 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,7 @@ dmypy.json

# Pyre type checker
.pyre/

# Random things
.DS_Store
.idea/
15 changes: 14 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
The MIT License

Copyright (c) 2020 Fulcrum Genomics LLC
Portions of the following files are Copyright (c) 2020 Myriad Genetics, Inc.
and were extracted from https://github.com/myriad-opensource/samwell as permitted
by the MIT License of that software:
fgpyo/collections/__init__.py
fgpyo/collections/test/test_peekable_iterator.py
fgpyo/sam/__init__.pyt
fgpyo/sam/builder.py
fgpyo/sam/clipping.py
fgpyo/sam/tests/test_builder.py
fgpyo/sam/tests/test_clipping.py
fgpyo/sam/tests/test_sam.py
fgpyo/sam/tests/test_supplementary_ailgnments.py

All other content and files Copyright (c) 2020-2022 Fulcrum Genomics LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,6 @@ conda activate fgpyo
poetry install
```

If, during `poetry install` on Mac OS X errors are encountered running gcc/clang to build `pybedtools` or other packages with native code, try setting the following and re-running `poetry install`:
```bash
export CFLAGS="-stdlib=libc++"
```


[rtd-link]: http://fgpyo.readthedocs.org/en/stable
[poetry-link]: https://github.com/python-poetry/poetry
[conda-link]: https://docs.conda.io/en/latest/miniconda.html
145 changes: 145 additions & 0 deletions fgpyo/collections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
Functions for Working with Collections
--------------------------------------
This module contains classes and functions for working with collections and iterators.
Examples of a "Peekable" Iterator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"Peekable" iterators are useful to "peek" at the next item in an iterator without consuming it.
For example, this is useful when consuming items in iterator while a predicate is true, and not
consuming the first element where the element is not true. See the
:func:`~fgpyo.collections.PeekableIterator.takewhile` and
:func:`~fgpyo.collections.PeekableIterator.dropwhile` methods.
An empty peekable iterator throws StopIteration:
.. code-block:: python
>>> from fgpyo.collections import PeekableIterator
>>> piter = PeekableIterator(iter([]))
>>> piter.peek()
StopIteration
A peekable iterator will return the next item before consuming it.
.. code-block:: python
>>> piter = PeekableIterator([1, 2, 3])
>>> piter.peek()
1
>>> next(piter)
1
>>> [j for j in piter]
[2, 3]
The `can_peek()` function can be used to determine if the iterator can be peeked without
StopIteration being thrown:
>>> piter = PeekableIterator([1])
>>> piter.peek() if piter.can_peek() else -1
1
>>> next(piter)
1
>>> piter.peek() if piter.can_peek() else -1
-1
>>> next(piter)
StopIteration
`PeekableIterator`'s constructor supports creation from iterable objects as well as iterators.
Module Contents
~~~~~~~~~~~~~~~
The module contains the following public classes:
- :class:`~fgpyo.collections.PeekableIterator` -- Iterator that allows you to peek at the
next value before calling next
"""

from typing import Any
from typing import Callable
from typing import Generic
from typing import Iterable
from typing import Iterator
from typing import List
from typing import TypeVar
from typing import Union


IterType = TypeVar("IterType")


class PeekableIterator(Generic[IterType], Iterator[IterType]):
"""A peekable iterator wrapping an iterator or iterable.
This allows returning the next item without consuming it.
Args:
source: an iterator over the objects
"""

def __init__(self, source: Union[Iterator[IterType], Iterable[IterType]]) -> None:
self._iter: Iterator[IterType] = iter(source)
self._sentinel: Any = object()
self.__update_peek()

def __iter__(self) -> Iterator[IterType]:
return self

def __next__(self) -> IterType:
to_return = self.peek()
self.__update_peek()
return to_return

def __update_peek(self) -> None:
self._peek = next(self._iter, self._sentinel)

def can_peek(self) -> bool:
"""Returns true if there is a value that can be peeked at, false otherwise."""
return self._peek is not self._sentinel

def peek(self) -> IterType:
"""Returns the next element without consuming it, or StopIteration otherwise."""
if self.can_peek():
return self._peek
else:
raise StopIteration

def takewhile(self, pred: Callable[[IterType], bool]) -> List[IterType]:
"""Consumes from the iterator while pred is true, and returns the result as a List.
The iterator is left pointing at the first non-matching item, or if all items match
then the iterator will be exhausted.
Args:
pred: a function that takes the next value from the iterator and returns
true or false.
Returns:
List[V]: A list of the values from the iterator, in order, up until and excluding
the first value that does not match the predicate.
"""
xs: List[IterType] = []
while self.can_peek() and pred(self._peek):
xs.append(next(self))
return xs

def dropwhile(self, pred: Callable[[IterType], bool]) -> "PeekableIterator[IterType]":
"""Drops elements from the iterator while the predicate is true.
Updates the iterator to point at the first non-matching element, or exhausts the
iterator if all elements match the predicate.
Args:
pred (Callable[[V], bool]): a function that takes a value from the iterator
and returns true or false.
Returns:
PeekableIterator[V]: a reference to this iterator, so calls can be chained
"""
while self.can_peek() and pred(self._peek):
self.__update_peek()
return self
Empty file.
53 changes: 53 additions & 0 deletions fgpyo/collections/tests/test_peekable_iterator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Tests for :py:mod:`~fgpyo.collections.PeekableIterator`"""

import pytest

from fgpyo.collections import PeekableIterator


def test_peekable_iterator_empty() -> None:
empty_iter: PeekableIterator[None] = PeekableIterator([])
assert not empty_iter.can_peek()
with pytest.raises(StopIteration):
empty_iter.peek()
with pytest.raises(StopIteration):
next(empty_iter)


def test_peekable_iterator_nonempty() -> None:
nonempty_iter = PeekableIterator(range(10))
for i in range(10):
assert nonempty_iter.can_peek()
assert nonempty_iter.peek() == i
assert next(nonempty_iter) == i

with pytest.raises(StopIteration):
nonempty_iter.peek()
with pytest.raises(StopIteration):
next(nonempty_iter)


def test_peekable_with_nones() -> None:
xs = [1, 2, None, 4, None, 6]
iterator = PeekableIterator(xs)

for i in range(len(xs)):
assert iterator.peek() is xs[i]
assert next(iterator) is xs[i]


def test_takewhile() -> None:
xs = [2, 4, 6, 8, 11, 13, 15, 17, 19, 20, 22, 24]
iterator = PeekableIterator(xs)
assert iterator.takewhile(lambda x: x % 2 == 0) == [2, 4, 6, 8]
assert iterator.takewhile(lambda x: x % 2 == 1) == [11, 13, 15, 17, 19]
assert iterator.takewhile(lambda x: x % 2 == 1) == []
assert iterator.takewhile(lambda x: x % 2 == 0) == [20, 22, 24]


def test_dropwhile() -> None:
xs = [2, 4, 6, 8, 11, 13, 15, 17, 19, 20, 22, 24]
iterator = PeekableIterator(xs)
iterator.dropwhile(lambda x: x % 2 == 0)
iterator.dropwhile(lambda x: x <= 20)
assert list(iterator) == [22, 24]
Loading

0 comments on commit 314fe06

Please sign in to comment.