-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathSecondBatch.java
More file actions
312 lines (282 loc) · 12.8 KB
/
SecondBatch.java
File metadata and controls
312 lines (282 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import java.util.Arrays;
import java.util.Random;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
/**
* A second batch of examples demonstrating arrays, loops, and numerical
* algorithms for the course CCPS 109.
* Updated for Java 21+ with modern idioms and language features.
*
* @author Ilkka Kokkarinen
*/
public class SecondBatch {
// -----------------------------------------------------------------------
// Count how many elements are above the array's average.
// Demonstrates: two-pass algorithm, for-each loop over arrays.
// As they say in Lake Wobegon, all students are above average.
// -----------------------------------------------------------------------
/**
* Count how many elements in {@code values} are strictly above the mean.
*
* @param values the array to examine
* @return the number of above-average elements
*/
public static int aboveAverage(double[] values) {
if (values.length == 0) { return 0; }
// First pass: compute the mean.
double sum = 0;
for (double v : values) { sum += v; }
double mean = sum / values.length;
// Second pass: count elements strictly above the mean.
int count = 0;
for (double v : values) {
if (v > mean) { count++; }
}
return count;
}
// -----------------------------------------------------------------------
// Hamming distance between two boolean arrays.
// Demonstrates: parallel iteration with an index-based loop.
// -----------------------------------------------------------------------
/**
* Count the number of positions where {@code first} and {@code second}
* differ. Both arrays must have the same length.
*
* @param first the first boolean array
* @param second the second boolean array
* @return the Hamming distance between the two arrays
*/
public static int hammingDistance(boolean[] first, boolean[] second) {
int differences = 0;
for (int i = 0; i < first.length; i++) {
if (first[i] != second[i]) { differences++; }
}
return differences;
}
// -----------------------------------------------------------------------
// Population variance — an important statistical quantity that is
// surprisingly easy to compute with a single pass.
// Demonstrates: accumulating two running sums simultaneously.
// -----------------------------------------------------------------------
/**
* Compute the population variance of the values in the array using the
* identity Var(X) = E[X²] − (E[X])². The square root of variance
* gives the standard deviation.
*
* @param values the data array
* @return the population variance
*/
public static double variance(double[] values) {
if (values.length == 0) { return 0; }
double sum = 0;
double squareSum = 0;
for (double v : values) {
sum += v;
squareSum += v * v;
}
double mean = sum / values.length;
double meanOfSquares = squareSum / values.length;
// Var(X) = E[X²] − (E[X])²
return meanOfSquares - mean * mean;
}
// -----------------------------------------------------------------------
// Polynomial evaluation — naive approach vs. Horner's rule.
// Demonstrates: accumulator patterns, algorithmic efficiency.
//
// Both methods treat coeff[i] as the coefficient of x^i, so
// coeff = {3, 2, 5} represents the polynomial 3 + 2x + 5x².
// -----------------------------------------------------------------------
/**
* Evaluate a polynomial at {@code x} by computing each term from its
* coefficient and a running power of x. Costs two multiplications and
* one addition per coefficient.
*
* @param coefficients the polynomial coefficients (index = degree)
* @param x the point at which to evaluate
* @return the value of the polynomial at {@code x}
*/
public static double evaluatePolynomial(double[] coefficients, double x) {
double result = 0;
double powerOfX = 1; // x^0 = 1
for (double coefficient : coefficients) {
result += coefficient * powerOfX;
powerOfX *= x;
}
return result;
}
/**
* Evaluate a polynomial at {@code x} using Horner's rule, which
* processes coefficients from highest degree down. This saves one
* multiplication per round and is also more numerically stable
* with floating-point arithmetic.
*
* @param coefficients the polynomial coefficients (index = degree)
* @param x the point at which to evaluate
* @return the value of the polynomial at {@code x}
*/
public static double evaluateHorner(double[] coefficients, double x) {
double result = coefficients[coefficients.length - 1];
for (int i = coefficients.length - 2; i >= 0; i--) {
result = result * x + coefficients[i];
}
return result;
}
// -----------------------------------------------------------------------
// Random sampling without replacement — rejection approach.
// Demonstrates: do-while loop, boolean tracking array.
//
// Simple but can be slow when k is close to a.length, because
// nearly every random index will already be taken.
// -----------------------------------------------------------------------
/**
* Draw {@code sampleSize} elements from {@code population} without
* replacement, using rejection sampling.
*
* @param population the array to sample from
* @param sampleSize how many elements to draw
* @param rng the random number generator to use
* @return an array of {@code sampleSize} sampled elements
*/
public static double[] rejectionSample(double[] population, int sampleSize, Random rng) {
var alreadyTaken = new boolean[population.length];
var sample = new double[sampleSize];
for (int i = 0; i < sampleSize; i++) {
int index;
do {
index = rng.nextInt(population.length);
} while (alreadyTaken[index]);
alreadyTaken[index] = true;
sample[i] = population[index];
}
return sample;
}
// -----------------------------------------------------------------------
// Reservoir sampling — an elegant online algorithm.
// Demonstrates: online/streaming algorithms, probabilistic reasoning.
//
// Each element of the input has exactly k/n probability of appearing
// in the final sample, even though we never know n in advance. This
// is one of those algorithms that seems almost too clever to be correct.
// -----------------------------------------------------------------------
/**
* Draw {@code sampleSize} elements from {@code population} without
* replacement using reservoir sampling. Unlike rejection sampling, this
* works in a single pass and is efficient regardless of the ratio of
* sample size to population size. Assumes {@code sampleSize <= population.length}.
*
* @param population the array to sample from
* @param sampleSize how many elements to draw
* @param rng the random number generator to use
* @return an array of {@code sampleSize} sampled elements
*/
public static double[] reservoirSample(double[] population, int sampleSize, Random rng) {
var reservoir = new double[sampleSize];
// Fill the reservoir with the first sampleSize elements.
System.arraycopy(population, 0, reservoir, 0, sampleSize);
// Each subsequent element may replace a random reservoir entry.
for (int i = sampleSize; i < population.length; i++) {
int slot = rng.nextInt(i + 1);
if (slot < sampleSize) {
reservoir[slot] = population[i];
}
}
return reservoir;
}
// -----------------------------------------------------------------------
// The coupon collector problem — a classic probability puzzle.
// Demonstrates: nested simulation loops, Monte Carlo estimation.
//
// If there are n distinct coupon types and you get one uniformly at
// random each day, on average how many days until you have them all?
// The theoretical answer is n * H(n) where H(n) is the n'th harmonic
// number, approximately n * ln(n) + 0.5772*n.
// -----------------------------------------------------------------------
/**
* Estimate the expected number of draws needed to collect all {@code n}
* distinct coupons, by averaging over many independent trials.
*
* @param couponCount the number of distinct coupon types
* @param trials the number of simulation trials
* @param rng the random number generator to use
* @return the estimated average number of draws to complete the collection
*/
public static double couponCollector(int couponCount, int trials, Random rng) {
long totalDraws = 0;
for (int trial = 0; trial < trials; trial++) {
var collected = new boolean[couponCount];
int remaining = couponCount;
int draws = 0;
while (remaining > 0) {
int coupon = rng.nextInt(couponCount);
draws++;
if (!collected[coupon]) {
collected[coupon] = true;
remaining--;
}
}
totalDraws += draws;
}
return (double) totalDraws / trials;
}
// -----------------------------------------------------------------------
// Theoretical expected value for the coupon collector problem, for
// comparison against our simulation results.
// -----------------------------------------------------------------------
private static double couponCollectorExpected(int n) {
// n * H(n) where H(n) = 1 + 1/2 + 1/3 + ... + 1/n
double harmonicSum = IntStream.rangeClosed(1, n)
.mapToDouble(i -> 1.0 / i)
.sum();
return n * harmonicSum;
}
// -----------------------------------------------------------------------
// Main method — exercise each example and display results.
// -----------------------------------------------------------------------
public static void main(String[] args) {
var rng = new Random(42);
// --- Above average ---
System.out.println("--- aboveAverage ---");
double[] data = {10, 20, 30, 40, 50};
System.out.printf("Data: %s%n", Arrays.toString(data));
System.out.printf("Above average: %d out of %d%n%n",
aboveAverage(data), data.length);
// --- Hamming distance ---
System.out.println("--- hammingDistance ---");
boolean[] bits1 = {true, false, true, true, false};
boolean[] bits2 = {true, true, false, true, false};
System.out.printf("bits1: %s%n", Arrays.toString(bits1));
System.out.printf("bits2: %s%n", Arrays.toString(bits2));
System.out.printf("Hamming distance: %d%n%n", hammingDistance(bits1, bits2));
// --- Variance ---
System.out.println("--- variance ---");
double[] uniform = {1, 2, 3, 4, 5};
System.out.printf("Variance of %s = %.4f%n%n",
Arrays.toString(uniform), variance(uniform));
// --- Polynomial evaluation ---
System.out.println("--- Polynomial evaluation ---");
// Coefficients {3, 2, 5} represent 3 + 2x + 5x².
double[] poly = {3, 2, 5};
double x = 4.0;
System.out.printf("Polynomial: 3 + 2x + 5x²%n");
System.out.printf("Naive eval at x=%.1f: %.4f%n", x, evaluatePolynomial(poly, x));
System.out.printf("Horner eval at x=%.1f: %.4f%n%n", x, evaluateHorner(poly, x));
// --- Sampling ---
System.out.println("--- Rejection sampling vs. reservoir sampling ---");
double[] population = DoubleStream.iterate(1, v -> v + 1)
.limit(20)
.toArray();
System.out.printf("Population: %s%n", Arrays.toString(population));
System.out.printf("Rejection sample (5): %s%n",
Arrays.toString(rejectionSample(population, 5, rng)));
System.out.printf("Reservoir sample (5): %s%n%n",
Arrays.toString(reservoirSample(population, 5, rng)));
// --- Coupon collector ---
System.out.println("--- Coupon collector problem ---");
for (int n : new int[]{5, 10, 20, 50}) {
double simulated = couponCollector(n, 100_000, rng);
double expected = couponCollectorExpected(n);
System.out.printf("n=%2d: simulated=%.2f, theoretical=%.2f%n",
n, simulated, expected);
}
}
}