diff --git a/common/Stopwatch.java b/common/Stopwatch.java new file mode 100644 index 0000000..69da970 --- /dev/null +++ b/common/Stopwatch.java @@ -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); + } +} diff --git a/main.typ b/main.typ index 9f3ac0b..ae999d6 100644 --- a/main.typ +++ b/main.typ @@ -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) { let a = "./week" + str(week) + "/doc.typ" include a diff --git a/week11/Clock.java b/week11/Clock.java new file mode 100644 index 0000000..33f72ff --- /dev/null +++ b/week11/Clock.java @@ -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); + } + + } +} diff --git a/week11/RandomSequenceChange.java b/week11/RandomSequenceChange.java new file mode 100644 index 0000000..ddf1861 --- /dev/null +++ b/week11/RandomSequenceChange.java @@ -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; + } +} diff --git a/week11/doc.typ b/week11/doc.typ index 272e4be..36a33d2 100644 --- a/week11/doc.typ +++ b/week11/doc.typ @@ -2,7 +2,7 @@ #show: template -= Week 10 += Week 11 == 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$? #table( - columns: 3, + columns: 4, $3.5$, $3.8$, $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$] ) +#embedClass(name: "Chaos") + == Exercise 1.4.26 _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 2, song 10 does not follow song 9, and so on). +#embedClass(name: "RandomSequenceChange") + == Exercise 1.5.32 _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 update the display roughly once per second. + +#embedClass(name: "Clock") \ No newline at end of file diff --git a/week12/Dict.java b/week12/Dict.java index 6de926e..afe96fd 100644 --- a/week12/Dict.java +++ b/week12/Dict.java @@ -1,102 +1,47 @@ package week12; -public class Dict { +import java.util.Arrays; + +public class Dict, V> { @SuppressWarnings("unchecked") private Pair[] data = new Pair[4]; - - 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)); - } + private int ptr = 0; public V get(K key) { - var hashCode = key.hashCode(); - var hash = hashCode; - // try getting a pair as long as there's a hash collision - while(true) { - var index = getIndex(hash); - var pair = data[index]; - if(pair == null) return null; - if(pair.first.equals(key)) return pair.second; - var secondHash = pair.first.hashCode(); - if(hashCode != secondHash) return null; - // this can overflow but should always be in range of the array when used with getIndex - hash += 1; + var index = binarySearch(key); + if(index >= 0) return data[index].second; + return null; + } + + // adapted from Arrays.binarySearch + // edited as Arrays.binarySearch doesn't accept a mapping function + private int binarySearch(K key) { + int low = 0; + int high = data.length - 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; } - } - - 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 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[] newData = new Pair[data.length * 2]; - for(var i = 0; i < data.length; i++) { - newData[i * 2] = data[i]; - } - data = newData; + return -(low + 1); // key not found. } - public void put(K key, V value) { - var hashCode = key.hashCode(); - 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 + private void increaseBackingSize() { + data = Arrays.copyOf(data, data.length * 2); + } - // 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. - // Simply trying to put again recomputes stuff needlessly but simplifies code. - while(true) { - hash += 1; - index = getIndex(hash); - existingPair = data[index]; - 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; - } + public void put(K key, V value) { + if(data.length >= ptr) increaseBackingSize(); + data[ptr] = new Pair<>(key, value); + ptr += 1; + // O(n) when almost sorted, which would be the case here. Allows use of binarySearch + Arrays.sort(data, new PairComparator<>()); } } diff --git a/week12/Multiset.java b/week12/Multiset.java new file mode 100644 index 0000000..4debed8 --- /dev/null +++ b/week12/Multiset.java @@ -0,0 +1,27 @@ +package week12; + +import java.util.HashMap; +import java.util.Map; + +public class Multiset { + Map 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(); + } +} diff --git a/week12/Pair.java b/week12/Pair.java new file mode 100644 index 0000000..1a964f3 --- /dev/null +++ b/week12/Pair.java @@ -0,0 +1,32 @@ +package week12; + +class Pair { + 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 swap() { + return new Pair(second, first); + } + + public Pair withFst(C first) { + return new Pair(first, second); + } + + public Pair withSnd(C second) { + return new Pair(first, second); + } +} \ No newline at end of file diff --git a/week12/PairComparator.java b/week12/PairComparator.java new file mode 100644 index 0000000..5b76bf3 --- /dev/null +++ b/week12/PairComparator.java @@ -0,0 +1,17 @@ +package week12; + +import java.util.Comparator; + +public class PairComparator, B> implements Comparator> { + @Override + public int compare(Pair o1, Pair o2) { + return compare(o1 == null ? null : o1.first, o2 == null ? null : o2.first); + } + + static > 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); + } +} diff --git a/week12/common b/week12/common new file mode 120000 index 0000000..60d3b0a --- /dev/null +++ b/week12/common @@ -0,0 +1 @@ +../common \ No newline at end of file diff --git a/week12/doc.typ b/week12/doc.typ new file mode 100644 index 0000000..168c573 --- /dev/null +++ b/week12/doc.typ @@ -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`. 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`._ +