Skip to content

Commit

Permalink
IntervalDict.combine
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreDecan committed Sep 25, 2019
1 parent 80c8d65 commit 23efdb6
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 11 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,35 @@ of disjoint intervals, one for each stored value.

```

Two `IntervalDict` instances can be combined together using the `.combine` method.
This method returns a new `IntervalDict` whose keys and values are taken from the two
source `IntervalDict`. Values corresponding to non-intersecting keys are simply copied,
while values corresponding to intersecting keys are combined together using the provided
function, as illustrated hereafter:

```python
>>> d1 = I.IntervalDict({I.closed(0, 2): 'banana'})
>>> d2 = I.IntervalDict({I.closed(1, 3): 'orange'})
>>> concat = lambda x, y: x + '/' + y
>>> d1.combine(d2, how=concat)
{[0,1): 'banana', [1,2]: 'banana/orange', (2,3]: 'orange'}

```

Resulting keys always correspond to an outer join. Other joins can be easily simulated
by querying the resulting `IntervalDict` as follows:

```python
>>> d = d1.combine(d2, how=concat)
>>> d[d1.domain()] # Left join
{[0,1): 'banana', [1,2]: 'banana/orange'}
>>> d[d2.domain()] # Right join
{[1,2]: 'banana/orange', (2,3]: 'orange'}
>>> d[d1.domain() & d2.domain()] # Inner join
{[1,2]: 'banana/orange'}

```

Finally, similarly to a `dict`, an `IntervalDict` also supports `len`, `in` and `del`, and defines
`.clear`, `.copy`, `.update`, `.pop`, `.popitem`, and `.setdefault`.

Expand Down Expand Up @@ -791,6 +820,10 @@ You can cite this library using:

This library adheres to a [semantic versioning](https://semver.org) scheme.

**not yet released**

- `IntervalDict` has a `.combine` method to merge its keys and values with another `IntervalDict`.


**1.9.0** (2019-09-13)

Expand Down
39 changes: 36 additions & 3 deletions intervals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


__package__ = 'python-intervals'
__version__ = '1.9.0'
__version__ = '1.10.0'
__licence__ = 'LGPL3'
__author__ = 'Alexandre Decan'
__url__ = 'https://github.com/AlexandreDecan/python-intervals'
Expand Down Expand Up @@ -1163,9 +1163,9 @@ def find(self, value):

def items(self):
"""
Return the list of (Interval, value) pairs.
Return a sorted list of (Interval, value) pairs.
:return: a list of 2-uples.
:return: a sorted list of 2-uples.
"""
def func(i):
return (i[0].lower, not i[0].left, i[0].upper, i[0].right)
Expand Down Expand Up @@ -1284,6 +1284,39 @@ def __getitem__(self, key):
return v
raise KeyError(key)

def combine(self, other, how):
"""
Return a new IntervalDict that combines the values from current and
provided ones.
If d = d1.combine(d2, f), then d contains (1) all values from d1 whose
keys do not intersect the ones of d2, (2) all values from d2 whose keys
do not intersect the ones of d1, and (3) f(x, y) for x in d1, y in d2 for
intersecting keys.
:param other: another IntervalDict instance.
:param how: a function of two parameters that combines values.
:return: a new IntervalDict instance.
"""
new_items = []

dom1, dom2 = self.domain(), other.domain()

new_items.extend(self[dom1 - dom2].items())
new_items.extend(other[dom2 - dom1].items())

intersection = dom1 & dom2
d1, d2 = self[intersection], other[intersection]

for i1, v1 in d1.items():
for i2, v2 in d2.items():
if i1.overlaps(i2):
i = i1 & i2
v = how(v1, v2)
new_items.append((i, v))

return IntervalDict(new_items)

def __setitem__(self, key, value):
interval = key if isinstance(key, Interval) else singleton(key)

Expand Down
16 changes: 8 additions & 8 deletions test_intervals.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,32 +919,32 @@ def test_intervaldict_combine():
assert I.IntervalDict().combine(I.IntervalDict(), add) == I.IntervalDict()

d = I.IntervalDict([(I.closed(0, 3), 0)])
assert I.IntervalDict().Combine(d, add) == d
assert I.IntervalDict().combine(d, add) == d
assert d.combine(I.IntervalDict(), add) == d

d1 = I.IntervalDict([(I.closed(1, 3) | I.closed(5, 7), 1)])
d2 = I.IntervalDict([(I.closed(2, 4) | I.closed(6, 8), 2)])
assert d1.combine(d2, add) == d2.combine(d1, add)
assert d1.combine(d2, add) == I.IntervalDict([
(I.closedopen(1, 2) | I.closedopen(5, 6), 1),
(I.closedopen(2, 3) | I.closedopen(6, 7), 3),
(I.closed(3, 4) | I.closed(7, 8), 2),
(I.closed(2, 3) | I.closed(6, 7), 3),
(I.openclosed(3, 4) | I.openclosed(7, 8), 2),
])

d1 = I.IntervalDict({
I.closed(0, 1): 2,
I.closed(3, 4): 2
})
d2 = I.IntervalDict({
I.closed(1, 3): 2,
I.closed(1, 3): 3,
I.closed(4, 5): 1
})
assert d1.combine(d2, add) == d2.combine(d1, add)
assert d1.combine(d2, add) == I.IntervalDict({
I.closedopen(0, 1): 1,
I.closed(1, 3): 3,
I.open(2, 3): 2,
I.singleton(3): 4,
I.closedopen(0, 1): 2,
I.singleton(1): 5,
I.open(1, 3): 3,
I.singleton(3): 5,
I.open(3, 4): 2,
I.singleton(4): 3,
I.openclosed(4, 5): 1,
Expand Down

0 comments on commit 23efdb6

Please sign in to comment.