# structa: an application for analyzing repetitive data structures
#
# Copyright (c) 2020-2021 Dave Jones <dave@waveform.org.uk>
#
# SPDX-License-Identifier: GPL-2.0-or-later
from collections import Counter
from collections.abc import Mapping
[docs]class FrozenCounter(Mapping):
"""
An immutable variant of the :class:`collections.Counter` class from the
Python standard library.
This implements all readable properties and behaviours of the
:class:`collections.Counter` class, but excludes all methods and behaviours
which permit modification of the counter. The resulting instances are
hashable and can be used as keys in mappings.
"""
def __init__(self, it):
self._counter = Counter(it)
self._hash = None
[docs] @classmethod
def from_counter(cls, counter):
"""
Construct a :class:`FrozenCounter` from a :class:`collections.Counter`
instance. This is generally much faster than attempting to construct
from the elements of an existing counter.
The *counter* parameter must either be a :class:`collections.Counter`
instance, or a :class:`FrozenCounter` instance (in which case it is
returned verbatim).
"""
if isinstance(counter, Counter):
self = cls(())
self._counter = counter.copy()
return self
elif isinstance(counter, FrozenCounter):
# It's frozen; no need to go recreating stuff
return counter
else:
assert False
[docs] def most_common(self, n=None):
"""
See :meth:`collections.Counter.most_common`.
"""
return self._counter.most_common(n)
[docs] def elements(self):
"""
See :meth:`collections.Counter.elements`.
"""
return self._counter.elements()
def __iter__(self):
return iter(self._counter)
def __len__(self):
return len(self._counter)
def __getitem__(self, key):
return self._counter[key]
def __repr__(self):
return "{self.__class__.__name__}({self._counter})".format(self=self)
def __hash__(self):
if self._hash is None:
self._hash = hash((frozenset(self), frozenset(self.values())))
return self._hash
def __eq__(self, other):
if isinstance(other, FrozenCounter):
return self._counter == other._counter
elif isinstance(other, Counter):
return self._counter == other
return NotImplemented
def __ne__(self, other):
if isinstance(other, FrozenCounter):
return self._counter != other._counter
elif isinstance(other, Counter):
return self._counter != other
return NotImplemented
def __add__(self, other):
if isinstance(other, FrozenCounter):
return FrozenCounter.from_counter(self._counter + other._counter)
elif isinstance(other, Counter):
return FrozenCounter.from_counter(self._counter + other)
return NotImplemented
def __sub__(self, other):
if isinstance(other, FrozenCounter):
return FrozenCounter.from_counter(self._counter - other._counter)
elif isinstance(other, Counter):
return FrozenCounter.from_counter(self._counter - other)
return NotImplemented