Add a new item to the Reservoir with the given tag.

    If the reservoir has not yet reached full size, the new item is guaranteed
    to be added. If the reservoir is full, then behavior depends on the
    always_keep_last boolean.

    If always_keep_last was set to true, the new item is guaranteed to be added
    to the reservoir, and either the previous last item will be replaced, or
    (with low probability) an older item will be replaced.

    If always_keep_last was set to false, then the new item will replace an
    old item with low probability.

    If f is provided, it will be applied to transform item (lazily, iff item is
      going to be included in the reservoir).

    Args:
      key: The key to store the item under.
      item: The item to add to the reservoir.
      f: An optional function to transform the item prior to addition.

def AddItem(self, key, item, f=lambda x: x):
    """Add a new item to the Reservoir with the given tag.

    If the reservoir has not yet reached full size, the new item is guaranteed
    to be added. If the reservoir is full, then behavior depends on the
    always_keep_last boolean.

    If always_keep_last was set to true, the new item is guaranteed to be added
    to the reservoir, and either the previous last item will be replaced, or
    (with low probability) an older item will be replaced.

    If always_keep_last was set to false, then the new item will replace an
    old item with low probability.

    If f is provided, it will be applied to transform item (lazily, iff item is
      going to be included in the reservoir).

    Args:
      key: The key to store the item under.
      item: The item to add to the reservoir.
      f: An optional function to transform the item prior to addition.
    """
    with self._mutex:
      bucket = self._buckets[key]
    bucket.AddItem(item, f)