Passed
Branch master (3b4079)
by Lewys
03:21
created

size()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
dl 0
loc 2
rs 10
1
package com.lewdev.probabilitylib;
2
3
import java.util.Comparator;
4
import java.util.Iterator;
5
import java.util.TreeSet;
6
import java.util.concurrent.ThreadLocalRandom;
7
8
/**
9
 * ProbabilityCollection for retrieving random elements based on probability.
10
 * <br>
11
 * <br>
12
 * <b>Selection Algorithm Implementation</b>:
13
 * <p>
14
 * <ul>
15
 * <li>Elements have a "block" of space, sized based on their probability share
16
 * <li>"Blocks" start from index 1 and end at the total probability of all elements
17
 * <li>A random number is selected between 1 and the total probability
18
 * <li>Which "block" the random number falls in is the element that is selected
19
 * <li>Therefore "block"s with larger probability have a greater chance of being
20
 * selected than those with smaller probability.
21
 * </p>
22
 * </ul>
23
 * 
24
 * @author Lewys Davies
25
 *
26
 * @param <E> Type of elements
27
 */
28
public class ProbabilityCollection<E> {
29
30
	protected final Comparator<ProbabilitySetElement<E>> comparator = 
31
			(o1, o2)-> Integer.compare(o1.getIndex(), o2.getIndex());
32
	
33
	private final TreeSet<ProbabilitySetElement<E>> collection;
34
35
	private int totalProbability;
36
	
37
	/**
38
	 * Construct a new Probability Collection
39
	 */
40
	public ProbabilityCollection() {
41
		this.collection = new TreeSet<>(this.comparator);
42
		this.totalProbability = 0;
43
	}
44
45
	/**
46
	 * @return Number of objects inside the collection
47
	 */
48
	public int size() {
49
		return this.collection.size();
50
	}
51
	
52
	/**
53
	 * @return Collection contains no elements
54
	 */
55
	public boolean isEmpty() {
56
		return this.collection.isEmpty();
57
	}
58
	
59
	/**
60
	 * @param object
61
	 * @return True if the collection contains the object, else False
62
	 */
63
	public boolean contains(E object) {
64
		return this.collection.stream()
65
			.filter(entry -> entry.getObject().equals(object))
0 ignored issues
show
introduced by
Replace this "filter().findFirst().isPresent()" chain with "anyMatch()"
Loading history...
66
			.findFirst()
67
			.isPresent();
68
	}
69
70
	/**
71
	 * @return Iterator over collection
72
	 */
73
	public Iterator<ProbabilitySetElement<E>> iterator() {
74
		return this.collection.iterator();
75
	}
76
	
77
	/**
78
	 * Add an object to this collection
79
	 * 
80
	 * @param object
81
	 * @param probability share
82
	 */
83
	public void add(E object, int probability) {
84
		this.collection.add(new ProbabilitySetElement<E>(object, probability));
85
		this.totalProbability += probability;
86
		this.updateIndexes();
87
	}
88
89
	/**
90
	 * Remove a object from this collection
91
	 * 
92
	 * @param object
93
	 * @return True if object was removed, else False.
94
	 */
95
	public boolean remove(E object) {
96
		Iterator<ProbabilitySetElement<E>> it = this.iterator();
97
		boolean removed = false;
98
		
99
		while(it.hasNext()) {
100
			ProbabilitySetElement<E> element = it.next();
101
			if(element.getObject().equals(object)) {
102
				removed = true;
103
				this.totalProbability -= element.getProbability();
104
				it.remove();
105
			}
106
		}
107
		
108
		this.updateIndexes();
109
		return removed;
110
	}
111
	
112
	/**
113
	 * @return Random object based on probability
114
	 */
115
	public E get() {
116
		ProbabilitySetElement<E> toFind = new ProbabilitySetElement<>(null, 0);
117
		toFind.setIndex(ThreadLocalRandom.current().nextInt(1, this.totalProbability + 1));
118
		
119
		return this.collection.floor(toFind).getObject();
120
	}
121
	
122
	/**
123
	 * @return Sum of all element's probability
124
	 */
125
	public final int getTotalProbability() {
126
		return this.totalProbability;
127
	}
128
	
129
	/*
130
	 * Calculate the size of all element's "block" of space: 
131
	 * i.e 1-5, 6-10, 11-14, 15, 16
132
	 * 
133
	 * We then only need to store the start index of each element
134
	 */
135
	private void updateIndexes() {
136
		int previousIndex = 0;
137
		
138
		for(ProbabilitySetElement<E> entry : this.collection) {
139
			previousIndex = entry.setIndex(previousIndex + 1) + (entry.getProbability() - 1);
140
		}
141
	}
142
	
143
	/**
144
	 * Used internally to store information about a object's
145
	 * state in a collection. Specifically, the probability 
146
	 * and index within the collection.
147
	 * 
148
	 * Indexes refer to the start position of this element's "block" of space.
149
	 * The space between element "block"s represents their probability of being selected
150
	 * 
151
	 * @author Lewys Davies
152
	 *
153
	 * @param <T> Type of element
154
	 */
155
	final static class ProbabilitySetElement<T> {
156
		private final T object;
157
		private final int probability;
158
		private int index;
159
		
160
		/**
161
		 * @param object
162
		 * @param probability
163
		 */
164
		protected ProbabilitySetElement(T object, int probability) {
165
			this.object = object;
166
			this.probability = probability;
167
		}
168
169
		/**
170
		 * @return The actual object
171
		 */
172
		public final T getObject() {
173
			return this.object;
174
		}
175
176
		/**
177
		 * @return Probability share in this collection
178
		 */
179
		public final int getProbability() {
180
			return this.probability;
181
		}
182
		
183
		// Used internally, see this class's documentation
184
		protected final int getIndex() {
185
			return this.index;
186
		}
187
		
188
		// Used Internally, see this class's documentation
189
		protected final int setIndex(int index) {
190
			this.index = index;
191
			return this.index;
192
		}
193
	}
194
}
195