ProbabilitySetElement   A
last analyzed

Complexity

Total Complexity 5

Size/Duplication

Total Lines 37
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 16
c 3
b 0
f 0
dl 0
loc 37
rs 10
wmc 5

5 Methods

Rating   Name   Duplication   Size   Complexity  
A ProbabilitySetElement(T,int) 0 3 1
A getProbability() 0 2 1
A getIndex() 0 2 1
A getObject() 0 2 1
A setIndex(int) 0 3 1
1
/*
2
* Copyright (c) 2020 Lewys Davies
3
* 
4
* Permission is hereby granted, free of charge, to any person obtaining a copy
5
* of this software and associated documentation files (the "Software"), to deal
6
* in the Software without restriction, including without limitation the rights
7
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
* copies of the Software, and to permit persons to whom the Software is
9
* furnished to do so, subject to the following conditions:
10
*
11
* The above copyright notice and this permission notice shall be included in all
12
* copies or substantial portions of the Software.
13
* 
14
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
* SOFTWARE.
21
*/
22
package com.lewdev.probabilitylib;
23
24
import java.util.Comparator;
25
import java.util.Iterator;
26
import java.util.NavigableSet;
27
import java.util.Objects;
28
import java.util.SplittableRandom;
29
import java.util.TreeSet;
30
31
/**
32
 * ProbabilityCollection for retrieving random elements based on probability.
33
 * <br>
34
 * <br>
35
 * <b>Selection Algorithm Implementation</b>:
36
 * <p>
37
 * <ul>
38
 * <li>Elements have a "block" of space, sized based on their probability share
39
 * <li>"Blocks" start from index 1 and end at the total probability of all
40
 * elements
41
 * <li>A random number is selected between 1 and the total probability
42
 * <li>Which "block" the random number falls in is the element that is selected
43
 * <li>Therefore "block"s with larger probability have a greater chance of being
44
 * selected than those with smaller probability.
45
 * </p>
46
 * </ul>
47
 * 
48
 * @author Lewys Davies
49
 * @version 0.8
50
 *
51
 * @param <E> Type of elements
52
 */
53
public final class ProbabilityCollection<E> {
54
55
    private final NavigableSet<ProbabilitySetElement<E>> collection;
56
    private final SplittableRandom random = new SplittableRandom();
57
58
    private int totalProbability;
59
60
    /**
61
     * Construct a new Probability Collection
62
     */
63
    public ProbabilityCollection() {
64
        this.collection = new TreeSet<>(Comparator.comparingInt(ProbabilitySetElement::getIndex));
65
        this.totalProbability = 0;
66
    }
67
68
    /**
69
     * @return Number of objects inside the collection
70
     */
71
    public int size() {
72
        return this.collection.size();
73
    }
74
75
    /**
76
     * @return True if collection contains no elements, else False
77
     */
78
    public boolean isEmpty() {
79
        return this.collection.isEmpty();
80
    }
81
82
    /**
83
     * @param <E> object
84
     * @return True if collection contains the object, else False
85
     * @throws IllegalArgumentException if object is null
86
     */
87
    public boolean contains(E object) {
88
        if (object == null) {
89
            throw new IllegalArgumentException("Cannot check if null object is contained in this collection");
90
        }
91
92
        return this.collection.stream().anyMatch(entry -> entry.getObject().equals(object));
93
    }
94
95
    /**
96
     * @return Iterator over this collection
97
     */
98
    public Iterator<ProbabilitySetElement<E>> iterator() {
99
        return this.collection.iterator();
100
    }
101
102
    /**
103
     * Add an object to this collection
104
     * 
105
     * @param <E>         object. Not null.
106
     * @param probability share. Must be greater than 0.
107
     * 
108
     * @throws IllegalArgumentException if object is null
109
     * @throws IllegalArgumentException if probability <= 0
110
     */
111
    public void add(E object, int probability) {
112
        if (object == null) {
113
            throw new IllegalArgumentException("Cannot add null object");
114
        }
115
116
        if (probability <= 0) {
117
            throw new IllegalArgumentException("Probability must be greater than 0");
118
        }
119
120
        ProbabilitySetElement<E> entry = new ProbabilitySetElement<E>(object, probability);
121
        entry.setIndex(this.totalProbability + 1);
122
123
        this.collection.add(entry);
124
        this.totalProbability += probability;
125
    }
126
127
    /**
128
     * Remove a object from this collection
129
     * 
130
     * @param <E> object
131
     * @return True if object was removed, else False.
132
     * 
133
     * @throws IllegalArgumentException if object is null
134
     */
135
    public boolean remove(E object) {
136
        if (object == null) {
137
            throw new IllegalArgumentException("Cannot remove null object");
138
        }
139
140
        Iterator<ProbabilitySetElement<E>> it = this.iterator();
141
        boolean removed = false;
142
143
        // Remove all instances of the object
144
        while (it.hasNext()) {
145
            ProbabilitySetElement<E> entry = it.next();
146
            if (entry.getObject().equals(object)) {
147
                this.totalProbability -= entry.getProbability();
148
                it.remove();
149
                removed = true;
150
            }
151
        }
152
153
        // Recalculate remaining elements "block" of space: i.e 1-5, 6-10, 11-14
154
        if (removed) {
155
            int previousIndex = 0;
156
            for (ProbabilitySetElement<E> entry : this.collection) {
157
                previousIndex = entry.setIndex(previousIndex + 1) + (entry.getProbability() - 1);
158
            }
159
        }
160
161
        return removed;
162
    }
163
164
    /**
165
     * Remove all objects from this collection
166
     */
167
    public void clear() {
168
        this.collection.clear();
169
        this.totalProbability = 0;
170
    }
171
172
    /**
173
     * Get a random object from this collection, based on probability.
174
     * 
175
     * @return <E> Random object
176
     * 
177
     * @throws IllegalStateException if this collection is empty
178
     */
179
    public E get() {
180
        if (this.isEmpty()) {
181
            throw new IllegalStateException("Cannot get an object out of a empty collection");
182
        }
183
184
        ProbabilitySetElement<E> toFind = new ProbabilitySetElement<>(null, 0);
185
        toFind.setIndex(this.random.nextInt(1, this.totalProbability + 1));
186
187
        return Objects.requireNonNull(this.collection.floor(toFind).getObject());
188
    }
189
190
    /**
191
     * @return Sum of all element's probability
192
     */
193
    public int getTotalProbability() {
194
        return this.totalProbability;
195
    }
196
197
    /**
198
     * Used internally to store information about a object's state in a collection.
199
     * Specifically, the probability and index within the collection.
200
     * 
201
     * Indexes refer to the start position of this element's "block" of space. The
202
     * space between element "block"s represents their probability of being selected
203
     * 
204
     * @author Lewys Davies
205
     *
206
     * @param <T> Type of element
207
     */
208
    public final static class ProbabilitySetElement<T> {
209
        private final T object;
210
        private final int probability;
211
        private int index;
212
        
213
        /**
214
         * @param <T>         object
215
         * @param probability share within the collection
216
         */
217
        protected ProbabilitySetElement(T object, int probability) {
218
            this.object = object;
219
            this.probability = probability;
220
        }
221
222
        /**
223
         * @return <T> The actual object
224
         */
225
        public T getObject() {
226
            return this.object;
227
        }
228
229
        /**
230
         * @return Probability share in this collection
231
         */
232
        public int getProbability() {
233
            return this.probability;
234
        }
235
236
        // Used internally, see this class's documentation
237
        private int getIndex() {
238
            return this.index;
239
        }
240
241
        // Used Internally, see this class's documentation
242
        private int setIndex(int index) {
243
            this.index = index;
244
            return this.index;
245
        }
246
    }
247
}
248