This commit is contained in:
Daniel Bulant 2025-11-16 13:56:58 +01:00
parent 0690e7c705
commit 125e24b3b8
11 changed files with 304 additions and 94 deletions

81
common/Stopwatch.java Normal file
View file

@ -0,0 +1,81 @@
/******************************************************************************
* Compilation: javac Stopwatch.java
* Execution: java Stopwatch n
* Dependencies: none
*
* A utility class to measure the running time (wall clock) of a program.
*
* % java8 Stopwatch 100000000
* 6.666667e+11 0.5820 seconds
* 6.666667e+11 8.4530 seconds
*
******************************************************************************/
package common;
/**
* The {@code Stopwatch} data type is for measuring
* the time that elapses between the start and end of a
* programming task (wall-clock time).
*
* See {@link StopwatchCPU} for a version that measures CPU time.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public class Stopwatch {
private final long start;
/**
* Initializes a new stopwatch.
*/
public Stopwatch() {
start = System.currentTimeMillis();
}
/**
* Returns the elapsed CPU time (in seconds) since the stopwatch was created.
*
* @return elapsed CPU time (in seconds) since the stopwatch was created
*/
public double elapsedTime() {
long now = System.currentTimeMillis();
return (now - start) / 1000.0;
}
/**
* Unit tests the {@code Stopwatch} data type.
* Takes a command-line argument {@code n} and computes the
* sum of the square roots of the first {@code n} positive integers,
* first using {@code Math.sqrt()}, then using {@code Math.pow()}.
* It prints to standard output the sum and the amount of time to
* compute the sum. Note that the discrete sum can be approximated by
* an integral - the sum should be approximately 2/3 * (n^(3/2) - 1).
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
// sum of square roots of integers from 1 to n using Math.sqrt(x).
Stopwatch timer1 = new Stopwatch();
double sum1 = 0.0;
for (int i = 1; i <= n; i++) {
sum1 += Math.sqrt(i);
}
double time1 = timer1.elapsedTime();
StdOut.printf("%e (%.2f seconds)\n", sum1, time1);
// sum of square roots of integers from 1 to n using Math.pow(x, 0.5).
Stopwatch timer2 = new Stopwatch();
double sum2 = 0.0;
for (int i = 1; i <= n; i++) {
sum2 += Math.pow(i, 0.5);
}
double time2 = timer2.elapsedTime();
StdOut.printf("%e (%.2f seconds)\n", sum2, time2);
}
}

View file

@ -15,7 +15,7 @@ Collection of solutions to programming exercises as part of Introduction to Prog
) )
#{ #{
let count = 11; let count = 12;
for week in range(1, count + 1).filter(it => it != 8) { for week in range(1, count + 1).filter(it => it != 8) {
let a = "./week" + str(week) + "/doc.typ" let a = "./week" + str(week) + "/doc.typ"
include a include a

38
week11/Clock.java Normal file
View file

@ -0,0 +1,38 @@
import common.StdDraw;
public class Clock {
public static void main(String[] args) {
StdDraw.setScale(-1, 1);
StdDraw.enableDoubleBuffering();
while(true) {
var time = System.currentTimeMillis();
var ms = (int) (time % 1000);
// this could be changed to doubles to have smooth movement
var seconds = (int) (time / 1000 % 60);
var minutes = (int) (time / 60_000 % 60);
var hours = (int) (time / 3_600_000 % 12);
var secondsAngle = (double) seconds / 60 * 2*Math.PI;
var minutesAngle = (double) minutes / 60 * 2*Math.PI;
var hoursAngle = (double) hours / 12 * 2*Math.PI;
StdDraw.clear();
StdDraw.setPenColor(StdDraw.BLACK);
StdDraw.setPenRadius(0.004);
StdDraw.line(0, 0, Math.sin(minutesAngle) * 0.8, Math.cos(minutesAngle) * 0.8);
StdDraw.line(0, 0, Math.sin(hoursAngle), Math.cos(hoursAngle));
StdDraw.setPenColor(StdDraw.RED);
StdDraw.setPenRadius(0.002);
StdDraw.line(0, 0, Math.sin(secondsAngle), Math.cos(secondsAngle));
StdDraw.setPenRadius(0.004);
StdDraw.setPenColor(StdDraw.BLACK);
StdDraw.circle(0, 0, 1);
StdDraw.show();
StdDraw.pause(1000 - ms);
}
}
}

View file

@ -0,0 +1,26 @@
import common.StdRandom;
public class RandomSequenceChange {
public static void main(String[] args) {
var arrayLength = 5;
var rounds = 100;
var data = new int[arrayLength];
for(var i = 0; i < data.length; i++)
data[i] = i;
// number of times any repetition was found after a random shuffle
var total = 0;
for(var i = 0; i < rounds; i++) {
StdRandom.shuffle(data);
total += (countSequences(data) > 0) ? 1 : 0;
}
var chance = 1 - (total / (double) rounds);
System.out.printf("The chance of no repetition is %s%%\n", chance * 100);
}
static int countSequences(int[] data) {
var output = 0;
for(var i = 1; i < data.length; i++)
if(data[i] == data[i - 1] + 1) output++;
return output;
}
}

View file

@ -2,7 +2,7 @@
#show: template #show: template
= Week 10 = Week 11
== Exercise 1.3.45 == Exercise 1.3.45
@ -15,7 +15,7 @@ with a small population—say, $x = 0.01$—and study the result of iterating th
$x = 1 - 1/r$ ? Can you say anything about the population when $r$ is $3.5$? $3.8$? $5$? $x = 1 - 1/r$ ? Can you say anything about the population when $r$ is $3.5$? $3.8$? $5$?
#table( #table(
columns: 3, columns: 4,
$3.5$, $3.5$,
$3.8$, $3.8$,
$5$, $5$,
@ -26,15 +26,21 @@ $x = 1 - 1/r$ ? Can you say anything about the population when $r$ is $3.5$? $3.
[Sustained $1-1/r$] [Sustained $1-1/r$]
) )
#embedClass(name: "Chaos")
== Exercise 1.4.26 == Exercise 1.4.26
_Music shuffling_. You set your music player to shuffle mode. It plays each of _Music shuffling_. You set your music player to shuffle mode. It plays each of
the n songs before repeating any. Write a program to estimate the likelihood that the $n$ songs before repeating any. Write a program to estimate the likelihood that
you will not hear any sequential pair of songs (that is, song 3 does not follow song you will not hear any sequential pair of songs (that is, song 3 does not follow song
2, song 10 does not follow song 9, and so on). 2, song 10 does not follow song 9, and so on).
#embedClass(name: "RandomSequenceChange")
== Exercise 1.5.32 == Exercise 1.5.32
_Clock_. Write a program that displays an animation of the second, minute, _Clock_. Write a program that displays an animation of the second, minute,
and hour hands of an analog clock. Use the method `StdDraw.pause(1000)` to and hour hands of an analog clock. Use the method `StdDraw.pause(1000)` to
update the display roughly once per second. update the display roughly once per second.
#embedClass(name: "Clock")

View file

@ -1,102 +1,47 @@
package week12; package week12;
public class Dict<K, V> { import java.util.Arrays;
public class Dict<K extends Comparable<K>, V> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Pair<K, V>[] data = new Pair[4]; private Pair<K, V>[] data = new Pair[4];
private int ptr = 0;
public static void main(String[] args) {
System.out.printf("%s %s %s\n", 1, 4, getIndex(1, 4));
System.out.printf("%s %s %s\n", 1, 8, getIndex(1, 8));
System.out.printf("%s %s %s\n", 1073741824, 4, getIndex(1073741824, 4));
System.out.printf("%s %s %s\n", 1073741824, 8, getIndex(1073741824, 8));
System.out.printf("%s %s %s\n", Integer.MIN_VALUE, 4, getIndex(Integer.MIN_VALUE, 4));
System.out.printf("%s %s %s\n", Integer.MIN_VALUE, 8, getIndex(Integer.MIN_VALUE, 8));
System.out.printf("%s %s %s\n", Integer.MAX_VALUE, 4, getIndex(Integer.MAX_VALUE, 4));
System.out.printf("%s %s %s\n", Integer.MAX_VALUE, 8, getIndex(Integer.MAX_VALUE, 8));
}
public V get(K key) { public V get(K key) {
var hashCode = key.hashCode(); var index = binarySearch(key);
var hash = hashCode; if(index >= 0) return data[index].second;
// try getting a pair as long as there's a hash collision return null;
while(true) { }
var index = getIndex(hash);
var pair = data[index]; // adapted from Arrays.binarySearch
if(pair == null) return null; // edited as Arrays.binarySearch doesn't accept a mapping function
if(pair.first.equals(key)) return pair.second; private int binarySearch(K key) {
var secondHash = pair.first.hashCode(); int low = 0;
if(hashCode != secondHash) return null; int high = data.length - 1;
// this can overflow but should always be in range of the array when used with getIndex
hash += 1; while (low <= high) {
int mid = (low + high) >>> 1;
var midVal = data[mid];
int cmp = PairComparator.compare(midVal == null ? null : midVal.first, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid;
} }
} return -(low + 1); // key not found.
private static int getIndex(int hashCode, int length) {
// using this instead of modulo allows us to simply insert nulls on every odd number when doubling array instead of having to rehash
// this changes the range of number from <MIN_VALUE, MAX_VALUE> to <0, data.length>
long hash = hashCode;
hash -= Integer.MIN_VALUE; // make the number positive, i.e. from 0 to 2*MAX_VALUE+1 (MIN_VALUE = -MAX_VALUE - 1)
hash *= length; // this may cause overflow in int
hash /= Integer.MAX_VALUE; // this will always put the number back into int range, as array length is at most MAX_VALUE
hash /= 2;
return (int)hash;
}
private int getIndex(int hashCode) {
return getIndex(hashCode, data.length);
}
private void increaseBackingSize() {
@SuppressWarnings("unchecked")
Pair<K, V>[] newData = new Pair[data.length * 2];
for(var i = 0; i < data.length; i++) {
newData[i * 2] = data[i];
}
data = newData;
} }
public void put(K key, V value) { private void increaseBackingSize() {
var hashCode = key.hashCode(); data = Arrays.copyOf(data, data.length * 2);
var hash = hashCode; }
var index = getIndex(hash);
var existingPair = data[index];
var newPair = new Pair<>(key, value);
if(existingPair != null && existingPair.first.equals(key)) {
data[index] = existingPair.withSnd(value);
return; // some of these returns aren't needed but directly show exit points of the function at a glance
} else if(existingPair != null) {
var originalHash = existingPair.first.hashCode();
if(hash == originalHash) {
// full collision of hash, cannot solve so add +1 to hash and try to insert it again
// a different possible solution is to use 2d arrays, with the topmost being a mapping of hashcodes to arrays of pairs
// insert the pair into first empty space after hash collisions. If there's something else though, increase backing size and try again, it should fit right after then. public void put(K key, V value) {
// Simply trying to put again recomputes stuff needlessly but simplifies code. if(data.length >= ptr) increaseBackingSize();
while(true) { data[ptr] = new Pair<>(key, value);
hash += 1; ptr += 1;
index = getIndex(hash); // O(n) when almost sorted, which would be the case here. Allows use of binarySearch
existingPair = data[index]; Arrays.sort(data, new PairComparator<>());
if(existingPair == null) {
data[index] = newPair;
return;
}
originalHash = existingPair.first.hashCode();
if(hash == originalHash) {
continue;
} else {
increaseBackingSize();
put(key, value);
return;
}
}
} else {
// a better algorithm would be to calculate the required length for the collision to disappear, but this should also work eventually and is simpler
increaseBackingSize();
put(key, value);
return;
}
} else {
data[index] = newPair;
}
} }
} }

27
week12/Multiset.java Normal file
View file

@ -0,0 +1,27 @@
package week12;
import java.util.HashMap;
import java.util.Map;
public class Multiset<E> {
Map<E, Integer> data = new HashMap<>();
public int count(E e) {
var entry = data.get(e);
return entry == null ? 0 : entry.intValue();
}
public void add(E e) {
data.compute(e, (key, num) -> num == null ? 1 : num+1);
}
public void remove(E e) {
var entry = data.get(e);
if(entry == null || entry <= 1) data.remove(e);
else data.put(e, entry - 1);
}
public int size() {
return data.size();
}
}

32
week12/Pair.java Normal file
View file

@ -0,0 +1,32 @@
package week12;
class Pair<A, B> {
final A first;
final B second;
Pair(A first, B second) {
this.first = first;
this.second = second;
}
// these getters doesn't seem to be needed, stdlib doesn't usually use getters for final values and instead just makes them public to be used directly
public A getFirst() {
return first;
}
public B getSecond() {
return second;
}
public Pair<B, A> swap() {
return new Pair<B, A>(second, first);
}
public<C> Pair<C, B> withFst(C first) {
return new Pair<C, B>(first, second);
}
public<C> Pair<A, C> withSnd(C second) {
return new Pair<A, C>(first, second);
}
}

View file

@ -0,0 +1,17 @@
package week12;
import java.util.Comparator;
public class PairComparator<A extends Comparable<A>, B> implements Comparator<Pair<A, B>> {
@Override
public int compare(Pair<A, B> o1, Pair<A, B> o2) {
return compare(o1 == null ? null : o1.first, o2 == null ? null : o2.first);
}
static <K extends Comparable<K>> int compare(K firstKey, K secondKey) {
if(firstKey == secondKey) return 0;
if(firstKey == null) return 1;
if(secondKey == null) return -1;
return firstKey.compareTo(secondKey);
}
}

1
week12/common Symbolic link
View file

@ -0,0 +1 @@
../common

37
week12/doc.typ Normal file
View file

@ -0,0 +1,37 @@
#import "./common/common.typ" : *
#show: template
= Week 12
== Exercise 12.07
Write a class `Pair` with two type parameters `A` and `B` to represent an immutable pair of values (i.e. the class should have two `final` fields of type `A` and `B`).
- Add an appropriate constructor and getter methods.
- Do not add any setters, as the class should be immutable.
- Add a method `swap` to the `Pair` class. The `swap` method should return a *new* pair where the first component becomes the second component and vice versa. For example, for the pair `(true, 42)` the method should return `(42, true)`.
- Add methods `withFst` and `withSnd` to the Pair class. Each method should take a type parameter `C` and return a new pair where the appropriate component has been updated. For example, calling `withFst` with the integer `42` on the pair `(true, "Hello World")` should return `(42, "Hello World")`.
== Exercise 12.08
Write a class `Dict` that takes two type parameters `K` and `V` to represent a dictionary, i.e. a mapping from items of type `K` to items of type `V`. Internally, the dictionary should maintain a single array of pairs of type `Pair<K, V>`. Add the following methods:
- `V get(K key)` returns the value associated with the given key, or null if the key is not found.
- `void put(K key, V value)` updates the dictionary with a mapping from the key to the value. If the key already exists, its value is updated. Otherwise, a new pair is added.
You may assume the `Dict` can contain at most 100 entries.
== Exercise 12.16
Write a class, which takes one type parameter `E`, to represent a multiset. A multiset is a set that counts how many times it contains each of its elements. Add the following methods:
- `int count(E e)` returns the number of times the element `e` occurs in the multiset.
- `void add(E e)` adds the element `e` to the multiset. (Adding increments its count.)
- `void remove(E e)` removes the element `e` from the multiset. (Removing decrements its count.)
- `int size()` returns the number of different elements in the set (non-duplicate count).
An element can never occur a negative number of times in a multiset.
_Hint: Use an internal map of type `Map<E, Integer>`._