Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into master
Browse files Browse the repository at this point in the history
hoanganhngo610 authored Sep 3, 2024
2 parents 2a719d0 + 3f14064 commit 3ee2c42
Showing 32 changed files with 81 additions and 88 deletions.
2 changes: 1 addition & 1 deletion docs/introduction/why-use-river.md
Original file line number Diff line number Diff line change
@@ -14,4 +14,4 @@ River supports different machine learning tasks, including regression, classific

## User experience

River is not the only library allowing you to do online machine learning. But it might just the simplest one to use in the Python ecosystem. River plays nicely with Python dictionaries, therefore making it easy to use in the context of web applications where JSON payloads are aplenty.
River is not the only library allowing you to do online machine learning. But it might just be the simplest one to use in the Python ecosystem. River plays nicely with Python dictionaries, therefore making it easy to use in the context of web applications where JSON payloads are aplenty.
1 change: 1 addition & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

- The units used in River have been corrected to be based on powers of 2 (KiB, MiB). This only changes the display, the behaviour is unchanged.
- The methods `learn_one`, `learn_many`, `update`, `revert`, and `append` now return `None`.

## cluster

16 changes: 9 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions river/anomaly/base.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ def _supervised(self):
return False

@abc.abstractmethod
def learn_one(self, x: dict):
def learn_one(self, x: dict) -> None:
"""Update the model.
Parameters
@@ -48,7 +48,7 @@ class SupervisedAnomalyDetector(base.Estimator):
"""A supervised anomaly detector."""

@abc.abstractmethod
def learn_one(self, x: dict, y: base.typing.Target):
def learn_one(self, x: dict, y: base.typing.Target) -> None:
"""Update the model.
Parameters
@@ -137,7 +137,7 @@ def score_one(self, *args, **kwargs):
"""
return self.anomaly_detector.score_one(*args, **kwargs)

def learn_one(self, *args, **learn_kwargs):
def learn_one(self, *args, **learn_kwargs) -> None:
"""Update the anomaly filter and the underlying anomaly detector.
Parameters
3 changes: 1 addition & 2 deletions river/anomaly/pad.py
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ class PredictiveAnomalyDetection(anomaly.base.SupervisedAnomalyDetector):
>>> for t, (x, y) in enumerate(datasets.AirlinePassengers()):
... score = PAD.score_one(None, y)
... PAD = PAD.learn_one(None, y)
... PAD.learn_one(None, y)
... scores.append(score)
>>> print(scores[-1])
@@ -131,7 +131,6 @@ def learn_one(self, x: dict | None, y: base.typing.Target | float):
self.predictive_model.learn_one(y=y, x=x)
else:
self.predictive_model.learn_one(x=x, y=y)
return self

def score_one(self, x: dict, y: base.typing.Target):
# Return the predicted value of x from the predictive model, first by checking whether
4 changes: 2 additions & 2 deletions river/base/classifier.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class Classifier(estimator.Estimator):
"""A classifier."""

@abc.abstractmethod
def learn_one(self, x: dict, y: base.typing.ClfTarget) -> Classifier:
def learn_one(self, x: dict, y: base.typing.ClfTarget) -> None:
"""Update the model with a set of features `x` and a label `y`.
Parameters
@@ -81,7 +81,7 @@ class MiniBatchClassifier(Classifier):
"""A classifier that can operate on mini-batches."""

@abc.abstractmethod
def learn_many(self, X: pd.DataFrame, y: pd.Series):
def learn_many(self, X: pd.DataFrame, y: pd.Series) -> None:
"""Update the model with a mini-batch of features `X` and boolean targets `y`.
Parameters
2 changes: 1 addition & 1 deletion river/base/clusterer.py
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ def _supervised(self):
return False

@abc.abstractmethod
def learn_one(self, x: dict) -> Clusterer:
def learn_one(self, x: dict) -> None:
"""Update the model with a set of features `x`.
Parameters
2 changes: 1 addition & 1 deletion river/base/drift_detector.py
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ class BinaryDriftDetector(_BaseDriftDetector):
"""A drift detector for binary data."""

@abc.abstractmethod
def update(self, x: bool) -> BinaryDriftDetector:
def update(self, x: bool) -> None:
"""Update the detector with a single boolean input.
Parameters
4 changes: 2 additions & 2 deletions river/base/multi_output.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ class MultiLabelClassifier(Estimator, abc.ABC):
"""Multi-label classifier."""

@abc.abstractmethod
def learn_one(self, x: dict, y: dict[FeatureName, bool]):
def learn_one(self, x: dict, y: dict[FeatureName, bool]) -> None:
"""Update the model with a set of features `x` and the labels `y`.
Parameters
@@ -68,7 +68,7 @@ class MultiTargetRegressor(Estimator, abc.ABC):
"""Multi-target regressor."""

@abc.abstractmethod
def learn_one(self, x: dict, y: dict[FeatureName, RegTarget], **kwargs):
def learn_one(self, x: dict, y: dict[FeatureName, RegTarget], **kwargs) -> None:
"""Fits to a set of features `x` and a real-valued target `y`.
Parameters
4 changes: 2 additions & 2 deletions river/base/regressor.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class Regressor(estimator.Estimator):
"""A regressor."""

@abc.abstractmethod
def learn_one(self, x: dict, y: base.typing.RegTarget):
def learn_one(self, x: dict, y: base.typing.RegTarget) -> None:
"""Fits to a set of features `x` and a real-valued target `y`.
Parameters
@@ -47,7 +47,7 @@ class MiniBatchRegressor(Regressor):
"""A regressor that can operate on mini-batches."""

@abc.abstractmethod
def learn_many(self, X: pd.DataFrame, y: pd.Series):
def learn_many(self, X: pd.DataFrame, y: pd.Series) -> None:
"""Update the model with a mini-batch of features `X` and real-valued targets `y`.
Parameters
8 changes: 4 additions & 4 deletions river/base/transformer.py
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ class Transformer(base.Estimator, BaseTransformer):
def _supervised(self):
return False

def learn_one(self, x: dict):
def learn_one(self, x: dict) -> None:
"""Update with a set of features `x`.
A lot of transformers don't actually have to do anything during the `learn_one` step
@@ -81,7 +81,7 @@ class SupervisedTransformer(base.Estimator, BaseTransformer):
def _supervised(self):
return True

def learn_one(self, x: dict, y: base.typing.Target):
def learn_one(self, x: dict, y: base.typing.Target) -> None:
"""Update with a set of features `x` and a target `y`.
Parameters
@@ -113,7 +113,7 @@ def transform_many(self, X: pd.DataFrame) -> pd.DataFrame:
"""

def learn_many(self, X: pd.DataFrame):
def learn_many(self, X: pd.DataFrame) -> None:
"""Update with a mini-batch of features.
A lot of transformers don't actually have to do anything during the `learn_many` step
@@ -138,7 +138,7 @@ def _supervised(self):
return True

@abc.abstractmethod
def learn_many(self, X: pd.DataFrame, y: pd.Series):
def learn_many(self, X: pd.DataFrame, y: pd.Series) -> None:
"""Update the model with a mini-batch of features `X` and targets `y`.
Parameters
1 change: 0 additions & 1 deletion river/cluster/textclust.py
Original file line number Diff line number Diff line change
@@ -213,7 +213,6 @@ def learn_one(self, x, t=None, w=None):

## increment observation counter
self.n += 1
return clusterId

## predicts the cluster number. The type specifies whether this should happen on micro-cluster
## or macro-cluster level
2 changes: 1 addition & 1 deletion river/compat/sklearn_to_river.py
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ def convert_sklearn_to_river(estimator: sklearn_base.BaseEstimator, classes: lis
(sklearn_base.RegressorMixin, SKL2RiverRegressor),
(
sklearn_base.ClassifierMixin,
functools.partial(SKL2RiverClassifier, classes=classes),
functools.partial(SKL2RiverClassifier, classes=classes), # type:ignore[arg-type]
),
]

2 changes: 0 additions & 2 deletions river/covariance/emp.py
Original file line number Diff line number Diff line change
@@ -379,5 +379,3 @@ def update_many(self, X: pd.DataFrame):
row = self._w[fi] * inv_cov[i]
for j, fj in enumerate(X):
self._inv_cov[min((fi, fj), (fj, fi))] = row[j]

return self
1 change: 0 additions & 1 deletion river/ensemble/ewa.py
Original file line number Diff line number Diff line change
@@ -122,7 +122,6 @@ def learn_predict_one(self, x, y):

def learn_one(self, x, y):
self.learn_predict_one(x, y)
return self

def predict_one(self, x):
return sum(model.predict_one(x) * weight for model, weight in zip(self, self.weights))
2 changes: 1 addition & 1 deletion river/feature_extraction/vectorize.py
Original file line number Diff line number Diff line change
@@ -203,7 +203,7 @@ def __init__(
# Stop word removal
if self.stop_words:
self.processing_steps.append(
functools.partial(remove_stop_words, stop_words=stop_words)
functools.partial(remove_stop_words, stop_words=self.stop_words)
)

# n-grams
6 changes: 2 additions & 4 deletions river/linear_model/base.py
Original file line number Diff line number Diff line change
@@ -122,8 +122,6 @@ def _fit(self, x, y, w, get_grad):

self._update_weights(x)

return self

def _update_weights(self, x):
# L1 cumulative penalty helper

@@ -161,7 +159,7 @@ def _eval_gradient_one(self, x: dict, y: float, w: float) -> tuple[dict, float]:

return (loss_gradient * utils.VectorDict(x), loss_gradient)

def learn_one(self, x, y, w=1.0):
def learn_one(self, x, y, w=1.0) -> None:
with self._learn_mode(x):
self._fit(x, y, w, get_grad=self._eval_gradient_one)

@@ -188,7 +186,7 @@ def _eval_gradient_many(

return dict(zip(X.columns, gradient)), loss_gradient.mean()

def learn_many(self, X: pd.DataFrame, y: pd.Series, w: float | pd.Series = 1):
def learn_many(self, X: pd.DataFrame, y: pd.Series, w: float | pd.Series = 1) -> None:
self._y_name = y.name
with self._learn_mode(set(X)):
self._fit(X, y, w, get_grad=self._eval_gradient_many)
28 changes: 14 additions & 14 deletions river/metrics/base.py
Original file line number Diff line number Diff line change
@@ -22,11 +22,11 @@ class Metric(base.Base, abc.ABC):
"""Mother class for all metrics."""

@abc.abstractmethod
def update(self, y_true, y_pred) -> Metric:
def update(self, y_true, y_pred) -> None:
"""Update the metric."""

@abc.abstractmethod
def revert(self, y_true, y_pred) -> Metric:
def revert(self, y_true, y_pred) -> None:
"""Revert the metric."""

@abc.abstractmethod
@@ -89,14 +89,14 @@ def __init__(self, cm=None):
cm = metrics.ConfusionMatrix()
self.cm = cm

def update(self, y_true, y_pred, w=1.0):
def update(self, y_true, y_pred, w=1.0) -> None:
self.cm.update(
y_true,
y_pred,
w=w,
)

def revert(self, y_true, y_pred, w=1.0):
def revert(self, y_true, y_pred, w=1.0) -> None:
self.cm.revert(
y_true,
y_pred,
@@ -152,7 +152,7 @@ def update(
y_true: bool,
y_pred: bool | float | dict[bool, float],
w=1.0,
) -> BinaryMetric:
) -> None:
if self.requires_labels:
y_pred = y_pred == self.pos_val
return super().update(y_true == self.pos_val, y_pred, w)
@@ -162,7 +162,7 @@ def revert(
y_true: bool,
y_pred: bool | float | dict[bool, float],
w=1.0,
) -> BinaryMetric:
) -> None:
if self.requires_labels:
y_pred = y_pred == self.pos_val
return super().revert(y_true == self.pos_val, y_pred, w)
@@ -190,11 +190,11 @@ class RegressionMetric(Metric):
_fmt = ",.6f" # use commas to separate big numbers and show 6 decimals

@abc.abstractmethod
def update(self, y_true: numbers.Number, y_pred: numbers.Number) -> RegressionMetric:
def update(self, y_true: numbers.Number, y_pred: numbers.Number) -> None:
"""Update the metric."""

@abc.abstractmethod
def revert(self, y_true: numbers.Number, y_pred: numbers.Number) -> RegressionMetric:
def revert(self, y_true: numbers.Number, y_pred: numbers.Number) -> None:
"""Revert the metric."""

@property
@@ -227,7 +227,7 @@ def __init__(self, metrics, str_sep="\n"):
super().__init__(metrics)
self.str_sep = str_sep

def update(self, y_true, y_pred, w=1.0):
def update(self, y_true, y_pred, w=1.0) -> None:
# If the metrics are classification metrics, then we have to handle the case where some
# of the metrics require labels, whilst others need to be fed probabilities
if hasattr(self, "requires_labels") and not self.requires_labels:
@@ -241,7 +241,7 @@ def update(self, y_true, y_pred, w=1.0):
for m in self:
m.update(y_true, y_pred)

def revert(self, y_true, y_pred, w=1.0):
def revert(self, y_true, y_pred, w=1.0) -> None:
# If the metrics are classification metrics, then we have to handle the case where some
# of the metrics require labels, whilst others need to be fed probabilities
if hasattr(self, "requires_labels") and not self.requires_labels:
@@ -334,10 +334,10 @@ def __init__(self):
def _eval(self, y_true, y_pred):
pass

def update(self, y_true, y_pred, w=1.0):
def update(self, y_true, y_pred, w=1.0) -> None:
self._mean.update(x=self._eval(y_true, y_pred), w=w)

def revert(self, y_true, y_pred, w=1.0):
def revert(self, y_true, y_pred, w=1.0) -> None:
self._mean.revert(x=self._eval(y_true, y_pred), w=w)

def get(self):
@@ -353,11 +353,11 @@ class ClusteringMetric(base.Base, abc.ABC):
_fmt = ",.6f" # Use commas to separate big numbers and show 6 decimals

@abc.abstractmethod
def update(self, x, y_pred, centers, w=1.0) -> ClusteringMetric:
def update(self, x, y_pred, centers, w=1.0) -> None:
"""Update the metric."""

@abc.abstractmethod
def revert(self, x, y_pred, centers, w=1.0) -> ClusteringMetric:
def revert(self, x, y_pred, centers, w=1.0) -> None:
"""Revert the metric."""

@abc.abstractmethod
8 changes: 4 additions & 4 deletions river/metrics/multioutput/base.py
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ def update(
y_pred: dict[str | int, base.typing.ClfTarget]
| dict[str | int, dict[base.typing.ClfTarget, float]],
w=1.0,
):
) -> None:
"""Update the metric."""
self.cm.update(y_true, y_pred, w)

@@ -48,7 +48,7 @@ def revert(
y_pred: dict[str | int, base.typing.ClfTarget]
| dict[str | int, dict[base.typing.ClfTarget, float]],
w=1.0,
):
) -> None:
"""Revert the metric."""
self.cm.revert(y_true, y_pred, w)

@@ -72,15 +72,15 @@ def update(
self,
y_true: dict[str | int, float | int],
y_pred: dict[str | int, float | int],
):
) -> None:
"""Update the metric."""

@abc.abstractmethod
def revert(
self,
y_true: dict[str | int, float | int],
y_pred: dict[str | int, float | int],
):
) -> None:
"""Revert the metric."""

def works_with(self, model) -> bool:
2 changes: 1 addition & 1 deletion river/neighbors/base.py
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ def __init__(self, dist_func: DistanceFunc | FunctionWrapper):
self.dist_func = dist_func

@abc.abstractmethod
def append(self, item: typing.Any, **kwargs):
def append(self, item: typing.Any, **kwargs) -> None:
pass

@abc.abstractmethod
2 changes: 0 additions & 2 deletions river/neighbors/knn_classifier.py
Original file line number Diff line number Diff line change
@@ -143,8 +143,6 @@ def _run_class_cleanup(self):
self.clean_up_classes()
self._cleanup_counter = self.cleanup_every

return self

def predict_proba_one(self, x, **kwargs):
nearest = self._nn.search((x, None), n_neighbors=self.n_neighbors, **kwargs)

16 changes: 8 additions & 8 deletions river/proba/base.py
Original file line number Diff line number Diff line change
@@ -56,11 +56,11 @@ class DiscreteDistribution(Distribution):
"""

@abc.abstractmethod
def update(self, x: typing.Hashable):
def update(self, x: typing.Hashable) -> None:
"""Updates the parameters of the distribution given a new observation."""

@abc.abstractmethod
def revert(self, x: typing.Hashable):
def revert(self, x: typing.Hashable) -> None:
"""Reverts the parameters of the distribution for a given observation."""


@@ -75,11 +75,11 @@ class BinaryDistribution(Distribution):
"""

@abc.abstractmethod
def update(self, x: bool):
def update(self, x: bool) -> None:
"""Updates the parameters of the distribution given a new observation."""

@abc.abstractmethod
def revert(self, x: bool):
def revert(self, x: bool) -> None:
"""Reverts the parameters of the distribution for a given observation."""


@@ -94,11 +94,11 @@ class ContinuousDistribution(Distribution):
"""

@abc.abstractmethod
def update(self, x: float):
def update(self, x: float) -> None:
"""Updates the parameters of the distribution given a new observation."""

@abc.abstractmethod
def revert(self, x: float):
def revert(self, x: float) -> None:
"""Reverts the parameters of the distribution for a given observation."""

@abc.abstractmethod
@@ -117,11 +117,11 @@ class MultivariateContinuousDistribution(Distribution):
"""

@abc.abstractmethod
def update(self, x: dict[str, float]):
def update(self, x: dict[str, float]) -> None:
"""Updates the parameters of the distribution given a new observation."""

@abc.abstractmethod
def revert(self, x: dict[str, float]):
def revert(self, x: dict[str, float]) -> None:
"""Reverts the parameters of the distribution for a given observation."""

@abc.abstractmethod
2 changes: 1 addition & 1 deletion river/reco/base.py
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ def is_contextual(self):
return False

@abc.abstractmethod
def learn_one(self, user: ID, item: ID, y: Reward, x: dict | None = None):
def learn_one(self, user: ID, item: ID, y: Reward, x: dict | None = None) -> None:
"""Fits a `user`-`item` pair and a real-valued target `y`.
Parameters
8 changes: 4 additions & 4 deletions river/stats/base.py
Original file line number Diff line number Diff line change
@@ -37,8 +37,8 @@ class Univariate(Statistic):
"""A univariate statistic measures a property of a variable."""

@abc.abstractmethod
def update(self, x: numbers.Number):
"""Update and return the called instance."""
def update(self, x: numbers.Number) -> None:
"""Update the called instance."""
raise NotImplementedError

@property
@@ -68,5 +68,5 @@ class Bivariate(Statistic):
"""A bivariate statistic measures a relationship between two variables."""

@abc.abstractmethod
def update(self, x, y):
"""Update and return the called instance."""
def update(self, x, y) -> None:
"""Update the called instance."""
4 changes: 4 additions & 0 deletions river/stats/iqr.py
Original file line number Diff line number Diff line change
@@ -61,6 +61,10 @@ def update(self, x):
self._is_updated = True

def get(self):
# HACK: Avoid crash if get is called before update
# panicked at 'index out of bounds: the len is 0 but the index is 0'
if not self._is_updated:
return None
return self._iqr.get()

def __repr__(self):
2 changes: 1 addition & 1 deletion river/time_series/base.py
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ def _supervised(self):
return True

@abc.abstractmethod
def learn_one(self, y: float, x: dict | None = None) -> Forecaster:
def learn_one(self, y: float, x: dict | None = None) -> None:
"""Updates the model.
Parameters
4 changes: 1 addition & 3 deletions river/time_series/metrics.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

class ForecastingMetric(base.Base, abc.ABC):
@abc.abstractmethod
def update(self, y_true: list[Number], y_pred: list[Number]) -> ForecastingMetric:
def update(self, y_true: list[Number], y_pred: list[Number]) -> None:
"""Update the metric at each step along the horizon.
Parameters
@@ -83,8 +83,6 @@ def update(self, y_true, y_pred):

metric.update(yt, yp)

return self

def get(self):
return [metric.get() for metric in self.metrics]

4 changes: 2 additions & 2 deletions river/tree/isoup_tree_regressor.py
Original file line number Diff line number Diff line change
@@ -208,7 +208,7 @@ def _new_leaf(self, initial_stats=None, parent=None):

return new_adaptive

def learn_one(self, x, y, *, w: float = 1.0) -> iSOUPTreeRegressor: # type: ignore
def learn_one(self, x, y, *, w: float = 1.0, **kwargs) -> None:
"""Incrementally train the model with one sample.
Training tasks:
@@ -232,7 +232,7 @@ def learn_one(self, x, y, *, w: float = 1.0) -> iSOUPTreeRegressor: # type: ign
# Update target set
self.targets.update(y.keys())

super().learn_one(x, y, w=w) # type: ignore
super().learn_one(x, y, w=w)

def predict_one(self, x):
pred = {}
4 changes: 2 additions & 2 deletions river/tree/splitter/base.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ class Splitter(base.Estimator, abc.ABC):
"""

@abc.abstractmethod
def update(self, att_val, target_val: base.typing.Target, w: float):
def update(self, att_val, target_val: base.typing.Target, w: float) -> None:
"""Update statistics of this observer given an attribute value, its target value
and the weight of the instance observed.
@@ -109,7 +109,7 @@ def __len__(self):
pass

@abc.abstractmethod
def update(self, x_val, gh: GradHess, w: float):
def update(self, x_val, gh: GradHess, w: float) -> None:
pass

@abc.abstractmethod
8 changes: 3 additions & 5 deletions river/tree/splitter/random_splitter.py
Original file line number Diff line number Diff line change
@@ -36,11 +36,11 @@ def cond_proba(self, att_val, class_val) -> float:
"""This attribute observer does not support probability density estimation."""
raise NotImplementedError

def update(self, att_val, target_val, w) -> Splitter:
def update(self, att_val, target_val, w) -> None:
if self.threshold is None:
if len(self._buffer) < self.buffer_size:
self._buffer.append((att_val, target_val, w))
return self
return

mn = min(self._buffer, key=lambda t: t[0])[0]
mx = max(self._buffer, key=lambda t: t[0])[0]
@@ -51,12 +51,10 @@ def update(self, att_val, target_val, w) -> Splitter:
self._update_stats(0 if a <= self.threshold else 1, t, w)
self._buffer = None

return self
return

self._update_stats(0 if att_val <= self.threshold else 1, target_val, w)

return self

def best_evaluated_split_suggestion(self, criterion, pre_split_dist, att_idx, binary_only):
post_split_dist = [self.stats[0], self.stats[1]]
merit = criterion.merit_of_split(pre_split_dist, post_split_dist)
4 changes: 2 additions & 2 deletions river/utils/rolling.py
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@

@typing.runtime_checkable
class Rollable(typing.Protocol):
def update(self, *args, **kwargs): ...
def update(self, *args, **kwargs) -> None: ...

def revert(self, *args, **kwargs): ...
def revert(self, *args, **kwargs) -> None: ...


class BaseRolling:
7 changes: 3 additions & 4 deletions river/utils/sorted_window.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,8 @@ class SortedWindow(collections.UserList):
>>> window = utils.SortedWindow(size=3)
>>> for i in reversed(range(9)):
... print(window.append(i))
... window.append(i)
... print(window)
[8]
[7, 8]
[6, 7, 8]
@@ -45,13 +46,11 @@ def __init__(self, size: int):
def size(self):
return self.unsorted_window.maxlen

def append(self, x):
def append(self, x) -> None:
if len(self) >= self.size:
# The window is sorted, and a binary search is more optimized than linear search
start_deque = bisect.bisect_left(self, self.unsorted_window[0])
del self[start_deque]

bisect.insort_left(self, x)
self.unsorted_window.append(x)

return self

0 comments on commit 3ee2c42

Please sign in to comment.