All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.deecaad.core.utils.ProbabilityMap Maven / Gradle / Ivy

package me.deecaad.core.utils;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * This class outlines a mapping of elements to a weight. This data structure allows real time
 * getting of random elements with weight.
 *
 * @param  The type of the element to store.
 */
public class ProbabilityMap implements Iterable> {

    private final Node dummy;
    private final NavigableSet> set;
    private double totalProbability;

    /**
     * Default constructor.
     */
    public ProbabilityMap() {
        this.dummy = new Node<>();
        this.set = new TreeSet<>(Comparator.comparingDouble(Node::getOffset));
    }

    /**
     * Adds an element with the given weight to the map.
     *
     * @param element The element to add.
     * @param chance The non-negative weight to map to the element.
     * @return true if the element was successfully added.
     */
    public boolean add(E element, double chance) {
        if (chance <= 0.0)
            throw new IllegalArgumentException("chance <= 0.0");

        Node node = new Node<>(element, chance, totalProbability);
        if (set.add(node)) {
            totalProbability += chance;
            return true;
        } else {
            return false;
        }
    }

    /**
     * Removes the given element, if it is present in the map. This method has an O notation of O(n) in
     * both best and worst case scenarios.
     *
     * @param element The element to remove.
     * @return true if the element was removed.
     */
    public boolean remove(E element) {
        Node removedElement = null;
        Iterator> iterator = iterator();

        // Iterate through every element
        while (iterator.hasNext()) {
            Node node = iterator.next();

            // If the element has already been removed, then we
            // need to shift the rest of the elements back by the
            // removed element's chance
            if (removedElement != null) {
                node.offset -= removedElement.chance;
            } else if (Objects.equals(element, node.value)) {
                iterator.remove();
                totalProbability -= node.chance;
                removedElement = node;
            }
        }

        return removedElement != null;
    }

    /**
     * Returns a random element based on each element's weight. If there are no elements in the set,
     * then this method will return null.
     *
     * @return The randomized element.
     */
    @Nullable public E get() {
        dummy.offset = ThreadLocalRandom.current().nextDouble(totalProbability);
        Node temp = set.floor(dummy);
        return temp == null ? null : temp.value;
    }

    /**
     * Returns true if there are no elements added to the map.
     *
     * @return true if the backing map is empty.
     */
    public boolean isEmpty() {
        return set.isEmpty();
    }

    /**
     * Returns the number of elements in the map.
     *
     * @return The amount of elements in the map.
     */
    public int size() {
        return set.size();
    }

    @NotNull @Override
    public Iterator> iterator() {
        return set.iterator();
    }

    public static class Node {

        private final E value;
        private final double chance;
        private double offset;

        Node() {
            value = null;
            chance = 0.0;
        }

        Node(E value, double chance, double offset) {
            this.chance = chance;
            this.value = value;
            this.offset = offset;
        }

        public E getValue() {
            return value;
        }

        public double getChance() {
            return chance;
        }

        public double getOffset() {
            return offset;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy