def __and__(self, other): """ >>> a1 = ArrayInterval([True, True, False, False]) >>> a2 = ArrayInterval([True, False, True, False]) >>> print(a1 & a2, (a1 & a2)[:]) ArrayInterval("0:1", shape=(4,)) [ True False False False] >>> a1 = ArrayInterval([True, True, False, False], inverse_mode=True) >>> a2 = ArrayInterval([True, False, True, False], inverse_mode=True) >>> print(a1 & a2, (a1 & a2)[:]) ArrayInterval("1:4", shape=(4,), inverse_mode=True) [ True False False False] >>> np.logical_and(a1, a2) array([ True, False, False, False]) """ if not isinstance(other, ArrayInterval): return NotImplemented elif self.inverse_mode is True and other.inverse_mode is True: # short circuit return ~((~self) | (~other)) elif self.inverse_mode is False and other.inverse_mode is False: if other.shape != self.shape: raise ValueError( f'Cannot broadcast together ArrayIntervals with shapes ' f'{self.shape} {other.shape}') ai = zeros(shape=self.shape) normalized_intervals = self.normalized_intervals intervals = [] for (start, stop) in other.normalized_intervals: intervals.extend( cy_intersection((start, stop), normalized_intervals)) ai.intervals = intervals return ai else: raise NotImplementedError(self.inverse_mode, other.inverse_mode)
def test_cy_intersection(): assert cy_intersection((0, 3), ((1, 2), )) == ((1, 2), ) assert cy_intersection((1, 4), ((0, 2), (3, 5))) == ((1, 2), (3, 4)) assert cy_intersection((1, 2), ((0, 3), )) == ((1, 2), ) assert cy_intersection((4, 5), ((0, 3), )) == ()
def __getitem__(self, item): """ >>> ai = zeros(50) >>> ai[19:26] array([False, False, False, False, False, False, False]) >>> ai[10:20] = 1 >>> ai[25:30] = 1 >>> ai ArrayInterval("10:20, 25:30", shape=(50,)) >>> ai[19:26] array([ True, False, False, False, False, False, True]) >>> ai[19] True >>> ai[5] False >>> ai[49] False >>> ai[29] True >>> ai[-25:-20] array([ True, True, True, True, True]) Get a similar behavior to numpy when indexing outside of shape: >>> ai[-1:1] array([], dtype=bool) >>> ai[-10:] array([False, False, False, False, False, False, False, False, False, False]) >>> ai[45:100] array([False, False, False, False, False]) """ if isinstance(item, (int, np.integer)): index = item if index < 0: if self.shape is None: raise ValueError( f'Negative indices can only be used on ArrayIntervals ' f'with a shape! index={index}') index = index + self.shape[-1] if index < 0 or self.shape is not None and index > self.shape[-1]: raise IndexError( f'Index {item} is out of bounds for ArrayInterval with ' f'shape {self.shape}') # Could be optimized for s, e in self.normalized_intervals: if e > index: return (index >= s) ^ self.inverse_mode return self.inverse_mode start, stop = cy_parse_item(item, self.shape) # This is numpy behavior if stop <= start: return np.zeros(0, dtype=np.bool) intervals = cy_intersection((start, stop), self.normalized_intervals) if self.inverse_mode: arr = np.ones(stop - start, dtype=np.bool) for i_start, i_end in intervals: arr[i_start - start:i_end - start] = False else: arr = np.zeros(stop - start, dtype=np.bool) for i_start, i_end in intervals: arr[i_start - start:i_end - start] = True return arr