import java.util.*; import java.io.File; import java.io.FileNotFoundException; /** * @author Eric Durant * Created on October 31, 2016 * Last updated November 14, 2016 * Java sample solutions for Op 2016 */ public class Op2016 { /** * Console menu allowing selection of any of the 9 demo solutions * @param args (unused) */ public static void main(String[] args) { boolean done = false; while(!done) { System.out.println("Op 2016 Demo Program"); System.out.println("1. Palindrome"); System.out.println("2. Law of sines"); System.out.println("3. Lensmaker’s equation"); System.out.println("4. Euler’s totient function"); System.out.println("5. Hangman"); System.out.println("6. Recursive difference equation"); System.out.println("7. Skyscrapers"); System.out.println("8. Text expander"); System.out.println("9. Rotation code"); System.out.println("0. Exit"); int choice = -1; // invalid while (choice > 9 || choice < 0) { if (sc.hasNextInt()) { choice = sc.nextInt(); sc.nextLine(); // dump CR from stream } else sc.next(); // discard non-int token } try { switch(choice) { case 1: prob1(); break; case 2: prob2(); break; case 3: prob3(); break; case 4: prob4(); break; case 5: prob5(); break; case 6: prob6(); break; case 7: prob7(); break; case 8: prob8(); break; case 9: prob9(); break; case 0: done = true; break; } } catch (FileNotFoundException e) { System.err.println("Op Demo returned to main menu due to FileNotFound exception: " + e.getMessage()); } } } /** * Problem 1. Palindrome (10 Points) */ private static void prob1() { /* * Ask the user to enter a lowercase word and then tell the user whether the word was a palindrome. Palindromes read the same forward and backward. For example, “abba” and “deified” are both palindromes. */ final String word = inputLine("Lowercase word? "); boolean palindrome = true; for (int idx = 0; palindrome && idx < word.length()/2; ++idx) palindrome = word.charAt(idx) == word.charAt(word.length()-(idx+1)); System.out.printf("That word is %sa palindrome.\n", palindrome?"":"not "); } /** * Problem 2. Law of sines (10 Points) */ private static void prob2() { /* * Ask the user for the length of sides a and c of the triangle along with the degree measure of ∠A. Calculate ∠C according to the law of sines: (sin A)/a=(sin C)/c Note that there generally 2 (positive) solutions for ∠C, one of which is found by solving for the angle using the arcsine function. The other, larger value is given by 180°-∠C. You should report this 2nd value of the angle only if it is physically possible (∠A+∠C < 180°). (When sin C = 1 and ∠C = 90°, there is only one solution, but you will not be penalized if you report it twice.) Report all angles in degrees. The second figure illustrates how there might be 2 valid solutions for ∠C. */ final double a = inputDouble("Side a? "); final double c = inputDouble("Side c? "); final double alpha = inputDouble("Angle a (degrees)? "); final double gamma = Math.toDegrees(Math.asin(c/a*Math.sin(Math.toRadians(alpha)))); // Okay for competitors to assume asin argument <= 1 final double gamma2 = 180-gamma; if (alpha+gamma2 < 180) { // 2nd, larger gamma is physically possible System.out.printf("There are 2 possible values for ∠C: %g & %g.\n", gamma, gamma2); } else { System.out.printf("There is 1 possible value for ∠C: %g.\n", gamma); } } /** * Problem 3. Lensmaker’s equation (10 Points) */ private static void prob3() { /* * Calculate f, the focal length of a lens, after inputting all the needed quantities from the user. The lens will be biconvex (both surfaces are spherical and protrude from the lens). The quantities are related by the lensmaker’s equation, where all variables are real numbers: 1/f=(n-1)[1/R_1 -1/R_2 +(n-1)d/(nR_1 R_2 )] As shown in the diagram, the Rs are the radii of the spherical lens surfaces and d is the thickness of the lens. Note that, for a biconvex lens (which is illustrated), R1>0 and R2<0; you may assume the user obeys these constraints. n is the refractive index of the lens material and n ≥ 1 for normal materials. You may assume that the user enters an n of at least 1. Assume that all length values are in the same units (centimeters, feet, etc.). It is not important what the specific units are. */ final double n = inputDouble("Refractive index (n)? "); final double R1 = inputDouble("Radius 1 (R1), >0? "); final double R2 = inputDouble("Radius 2 (R2), <0? "); final double d = inputDouble("Thickness (d)? "); final double f = 1. / ((n-1)*(1./R1-1./R2+((n-1)*d)/(n*R1*R2))); System.out.printf("The focal length is %g.\n", f); } private static SortedSet readDictionary(String filename) throws FileNotFoundException { Scanner sc = new Scanner(new File(filename)); TreeSet rv = new TreeSet<>(); while (sc.hasNextLine()) { rv.add(sc.nextLine()); } return rv; } /** * Problem 4. Euler’s totient function (20 Points) */ private static String genList(double[] list, int begin, int end) { // generate string representation on [begin,end) StringBuilder sb = new StringBuilder(); for(int idx = begin; idx < end; ++idx) { sb.append(list[idx]); sb.append(' '); } return sb.toString(); } private static void prob4() { /* Write a function that, given a positive integer between 1 and 10,000, calculates φ(n), which is called Euler’s totient function. Euler’s totient function has many applications in mathematics including cryptography, which allows computers to privately exchange messages even when using a public network where everyone can see the data being transmitted. Euler’s totient function indicates how many integers between 1 and n, inclusive, are relatively prime to n. Two numbers are relatively prime if they share no prime factors. So, the following are all pairs of relatively prime numbers: (3,7), (4,5), (12,35). Consider the number 10. The following numbers are relatively prime to 10: 1, 3, 7, 9. All the other numbers in range (2, 4, 5, 6, 8, 10) are not relatively prime since they have a factor of 2 and/or 5, which are the factors of 10. So, φ(10) = 4. The formula for calculating the totient is based on the prime factorization of n. For each prime factor, p, it is important how many times, k, that factor is repeated. Each prime factor contributes to a product that forms the totient. For pk, the contribution is pk-1(p-1). For example, if the prime factor 3 is repeated 4 times (thus 34 = 81 is a factor of n, but 35 = 243 is not), the contribution is 33(2) = 54. The contributions of each prime factor are multiplied together to arrive at the totient. For example, φ(1500) = φ(2 × 2 × 3 × 5 × 5 × 5) = φ(22 × 3 × 53) = φ(22) φ(3) φ(53) = (211)(302)(524) = 2×2×100 = 400. */ final int N = inputUnsignedInt("N? "); int quotient = N; int factor = 2; int totient = 1; while (quotient > 1) { if (quotient % factor == 0) { // implicit sieve, being prime is a necessary condition for being true int multiplicity = 0; do { ++multiplicity; quotient /= factor; } while (quotient % factor == 0); totient *= factor - 1; while (multiplicity-- > 1) { totient *= factor; } } ++factor; } System.out.printf("The totient of %d is %d.\n", N, totient); } /** * Problem 5. Hangman (20 Points) */ private static void prob5() throws FileNotFoundException { /* * Using the given “dictionary” file, generate a list of the letters ‘a’ through ‘z’. Along with each letter, indicate its probability of appearing (e.g., 27.3% or 0.273) in a word. This list is helpful in implementing a reasonable good “0th order” (not dependent on context) strategy for guessing in the game of Hangman. (If you are not familiar with the game, please see https://en.wikipedia.org/wiki/Hangman_(game).) Note that letters that appear multiple times in a word do not affect the probability of that letter appearing differently than letters that appear once; it is only relevant whether (not how many times) a letter appears in a word. The judges will test your program with different dictionary files, but they will be formatted the same as the given file (all lowercase, unique words in alphabetical order on separate lines). */ final String fn = inputLine("Dictionary file? "); final SortedSet dictionary = readDictionary(fn); final int counts[] = new int['z'-'a'+1]; // letters in alphabet for (String word: dictionary) { boolean found[] = new boolean[counts.length]; // shift into single int is likely more efficient, but not as clear for(char c: word.toCharArray()) { found[Character.toLowerCase(c)-'a'] = true; } for (int idx = 0; idx < found.length; ++idx) { if (found[idx]) { ++counts[idx]; } } } for (int idx = 0; idx < counts.length; ++idx) { System.out.printf("%c: %.4f\n", 'a'+idx, ((float)counts[idx])/dictionary.size()); } } /** * Problem 6. Recursive difference equation (20 Points) */ private static void prob6() { /* * Given an input sequence of numbers, x(n) (e.g., 0, 1, 2, 3, 4, 5, …) a 2nd order recursive difference equation defines an output sequence of numbers, y(n): y(n) = b(0)x(n) + b(1)x(n-1) + b(2)x(n-2) – a(1)y(n-1) – a(2)y(n-2). It is helpful to think of n as a time index, such as how many milliseconds have elapsed. n is an integer and starts at 0. When the equation calls for values of x or y for an argument less than 0, use the value 0 (that is, the sequences x and y are taken to be 0 before n = 0). You will prompt the user to enter the 3 b coefficients (be clear whether you are asking for b(0) or b(2) first) and the 2 a coefficients. You will then display in 2 columns (separate with tabs) the sequences y that result for 2 different functions x. Show your outputs for 0 ≤ n ≤ 50. In the first column, you will show the value of y when x has the value 0 everywhere except at n = 0, where it has the value 1. This first column y is called the “unit impulse response” of the system defined by the equation. In the second column, you will show the value of y when x has the value of 1 everywhere beginning at n = 0 (but remember that x is always taken to be 0 at n = -1 and n = -2). This second column is called the “unit step response” of the system. Sample output for a(1) = -0.8, a(2) = 0.6, b(0) = 0.7, b(1) = 0.3, and b(2) = -0.6: 0.7000 0.7000 0.8600 1.5600 -0.3320 1.2280 -0.7816 0.4464 -0.4261 0.0203 0.1281 0.1484 0.3581 0.5065 0.2096 0.7162 -0.0472 0.6690 -0.1635 0.5055 */ final double a[] = new double[3]; // a[0] taken as 1, but unused; here for notational convenience final double b[] = new double[3]; for (int idx = 0; idx < b.length; ++idx) { b[idx] = inputDouble("b["+idx+"]? "); } for (int idx = 1; idx < a.length; ++idx) { a[idx] = inputDouble("a["+idx+"]? "); } final double yd[] = new double[3]; // index is time in past, keep extra for notational convenience final double yu[] = new double[3]; for (int n = 0; n <= 50; ++n) { yd[0] = b[0]*(n==0?1:0) + b[1]*(n==1?1:0) + b[2]*(n==2?1:0) - a[1]*yd[1] - a[2]*yd[2]; // e.g., n==2 => n-2 == 0 == delta(n-2) = x(n-2) yu[0] = b[0]*(n>=0?1:0) + b[1]*(n>=1?1:0) + b[2]*(n>=2?1:0) - a[1]*yu[1] - a[2]*yu[2]; System.out.printf("%f\t%f\n",yd[0],yu[0]); yd[2] = yd[1]; yd[1] = yd[0]; // shift back, prepare for next iteration; circular buffer would eliminate moves at cost of mod yu[2] = yu[1]; yu[1] = yu[0]; } } /** * Problem 7. Skyscrapers (40 Points) */ private static void prob7() throws FileNotFoundException { /* * In this problem, you will write both a main() program and a helper method (or function). The helper method will return a positive integer indicating which “obvious error” exists in a player’s attempt to complete a “Skyscrapers” puzzle or 0 if there is no obvious error. These puzzles come in various sizes, typically between 3 and 6, were designed by Wei-Hwa Huang, and ran in the New York Times in summer, 2016. Here is the description of a size 6 puzzle: “Fill the grid with numbers from 1 to 6. The numbers represent the respective heights of skyscrapers — 1 being the shortest, 6 being the tallest. The digits at the side of the grid indicate the number of skyscrapers that can be seen from that vantage point, with shorter buildings sometimes being hidden by taller ones. As in sudoku, no number is repeated in any row or column.” To clarify, a shorter building is blocked whenever a taller building is in front of it. First, your main program will ask the user for the name of a text file describing a puzzle. This file will contain • First line: number indicating N, the size of the puzzle, which will be between 3 and 6 • Second line: number indicating C, the number of clues given on the outside of the puzzle • Each subsequent line contains 3 numbers separated by a space and describing how many towers are visible from a particular vantage point o The first number is 0, 1, 2, or 3, representing the view from the north, west, south, or east, respectively. o The second number is between 0 and N-1, inclusive, and indicates the 0-based row or column, starting from the left or top, of the vantage point. o The third number is between 1 and N, inclusive, and indicates the exact number of buildings that will be visible in a correct solution. The contest website contains sample files defining each of the illustrated puzzles. Second, your main program will ask the user for the name of a text file describing the user’s attempted partial (or full) solution to a puzzle. Each of the N lines of the file will contain N numbers between 0 and N, inclusive, separated by a space. 0 represents a blank cell, so there will be no 0s when the user has specified their complete solution. The file does not explicitly specify what N is, but you may assume the file is correctly formatted and matched to the previous file. This means, among other things, you may assume it only contains numbers between 0 and N; you may not assume, however, that numbers between 1 and N do not appear multiple times on a line (or in a column) (i.e., the user may have made an obvious mistake). The contest website contains the complete solution to the size 4 puzzle shown. For testing, you will want to replace some of the numbers with 0s and also enter some invalid solutions (e.g., two buildings of height 3 in the same row). Third, your main program will call your helper method (you can choose which arguments to pass), which will check if there are any “obvious errors” in the solution and return an integer indicating the result. Below is the complete list of “obvious errors.” If multiple obvious errors occur, you should just detect and return the one with the lowest ID number. If there are no obvious errors, your method will return 0. • Error 1: The same number between 1 and N appears more than once in any row or column. • Error 2: There is an empty cell that cannot be filled with a number since all the numbers 1 through N already appear in the 2N-2 cells in the same row and column. • Error 3: There is a row or column that is completely filled that does not present the number of specified faces. Fourth and finally, your main program will output a descriptive message based on the integer returned describing the error (you may use the text above) or lack of any obvious error. */ final String puzzleFile = inputLine("Puzzle file? "); final Scanner puzzleScanner = new Scanner(new File(puzzleFile)); final int N = puzzleScanner.nextInt(); final int C = puzzleScanner.nextInt(); final int[][] clues = new int[C][3]; for(int i = 0; i 0 && sawRow[solution[i][j]]) || (solution[j][i] > 0 && sawCol[solution[j][i]]) ) { errorLevel = 1; break; } sawRow[solution[i][j]] = true; sawCol[solution[j][i]] = true; } } if (errorLevel > 0) return errorLevel; // Error 2: There is an empty cell that cannot be filled with a number since all the numbers 1 through N already appear in the 2N-2 cells in the same row and column. for (int r = 0; r 0) return errorLevel; // Error 3: There is a row or column that is completely filled that does not present the number of specified faces. // Outer iteration over the clues makes the most sense for (int clue = 0; clue < C; ++clue) { // The first number is 0, 1, 2, or 3, representing the view from the north, west, south, or east, respectively. // The second number is between 0 and N-1, inclusive, and indicates the 0-based row or column, starting from the left or top, of the vantage point. // The third number is between 1 and N, inclusive, and indicates the exact number of buildings that will be visible in a correct solution. int stride = (clues[clue][0] <= 1) ? +1 : -1; // NW has positive stride, SE has negative stride boolean completelyFilled = true; int facesSeen = 0, tallestSoFar = 0; for (int off = 0; off < N; ++off) { // offset from edge int idx = ((stride>0) ? 0 : (N-1)) + stride*off; int current; if (clues[clue][0] % 2 == 0) { // NS = vertical pass current = solution[idx][clues[clue][1]]; } else { // WE = horizontal pass current = solution[clues[clue][1]][idx]; } if (current == 0) { completelyFilled = false; break; } else if (current > tallestSoFar) { ++facesSeen; tallestSoFar = current; // could early break when too tall first discovered } } if (completelyFilled && facesSeen != clues[clue][2]) { errorLevel = 3; break; } } return errorLevel; // will either be 0 or maximum at this point } /** * Problem 8. Text expander (40 Points) */ private static void prob8() throws FileNotFoundException { /* * Write a “text expander” that will prompt the user for the characters at the beginning of a word until enough information is given to determine which word the user is attempting to enter. The words that the user wants are in the given “dictionary” file containing an alphabetized list of lowercase words. After the user enters a character, the program will decide that one of the following is true and take the indicated action: * • 0 words are possible, so report this to the user and exit. * • Only 1 dictionary word is possible, so output that word and exit. (This is not the same as saying the user has entered a valid word. For example, a user who has entered “acid” might be aiming for “acidify.”) * • Two or more dictionary words are possible, so show all options up to a maximum of 10 to the user indicating a single-digit number for each, beginning with 0. Then, wait for the user to enter a response. * When a response is needed, the user may enter a letter or a single-digit number. The program should ignore numbers when they’re not expected (this includes entering a number that is too large). */ final SortedSet dictionary = readDictionary("dictionary.txt"); final int MAXLEN = 30; final char[] entered = new char[MAXLEN]; Integer off = 0; // total number of letters entered by user boolean done = false; SortedSet matches = new TreeSet<>(); while (!done) { final char c = inputLine("").charAt(0); // okay to assume exactly 1 character entered if (Character.isLetter(c)) { entered[off++] = c; String prefix = new String(entered, 0, off); matches = dictionary.subSet(prefix,prefix+"zzzz"); // assume zzzz can't appear in a valid word; simplifies exclusive upper bound if (matches.size() == 0) { System.out.println("No possible words"); done = true; } else if (matches.size() == 1) { System.out.printf("Word selected: [%s]\n", matches.first()); done = true; } else { // 2 or more options, so print up to 10 int idx = 0; for (String s : matches) { System.out.printf("%d. %s\n", idx++, s); if (idx==10) break; // only "room to display" up to 10 options } } } else if (Character.isDigit(c)) { // if we get here, we know that matches.size() >= 2 final int n = Character.getNumericValue(c); if (n < matches.size()) { Object[] matchesA = matches.toArray(); // might be terribly inefficient when matches.size() is large System.out.printf("Word selected: [%s]\n", (String)matchesA[n]); done = true; } } } } /** * Problem 9. Rotation code (40 Points) */ private static void prob9() { /* Write a program that generates and displays a “rotation code” based on an initial value given by the user. Each item in the code has the same number of characters, and each character is either a 0 or a 1. The program must handle items up to 9 characters long. A new item is generated from the previous item by moving each character one position to the left. The original leftmost item “falls off the left edge”, changes its value, and appears as the new item on the right. New items are generated until (but not including) the item the user originally entered is reached. Be careful that you don’t ignore any leading 0s in the user’s input. For example, if the user enters “10”, the “0” moves to the left place while the leftmost digit (here a “1”) falls off the left, changes into a “0” and becomes the rightmost digit, yielding “00”. Following these rules the next item is “01”, followed by “11”. This is the final item since the next item would be “10,” but that would repeat the initial value given by the user. */ // Although a boolean array should be the most efficient way to handle this, the upper limit of length 9 was chosen so that the values can be kept in a (base 10) integer if desired. That approach is taken here: final String start = inputLine("Starting value? "); // assume it is properly formatted final int N = start.length(); // Cannot ignore leading 0s! final String fmt = String.format("%%0%dd\n",N); // Set width, display leading 0s; e.g., 4 -> "%04d\n" final int startV = Integer.parseInt(start); int div = 1; for (int i = 1; i < N; ++i) div *= 10; // divisor to separate MSD int nextV = startV; do { int MSD = nextV / div; nextV = (nextV - MSD*div) * 10 + (1-MSD); // rotate left with bit flip if (nextV == startV) break; System.out.printf(fmt,nextV); } while (true); } private static void dumpBooleanArray(boolean[][] b) { for(int r = 0; r < b.length; ++r) { for(int c = 0; c < b[r].length; ++c) { System.out.print(b[r][c]?'1':'0'); System.out.print(' '); } System.out.println(); } } /**Read a NxN array from a file. * Assumes properly formatted file. * @param sc the Scanner to read from * @param N side length of array in file * @return the NxN array */ private static int[][] readIntArray(Scanner sc, int N) { int[][] result = null; assert N >= 0; if (sc != null) { result = new int[N][N]; boolean success = true; for(int row = 0; row < N && success; ++row) { for(int col = 0; col < N; ++col) { if (sc.hasNextInt()) { result[row][col] = sc.nextInt(); } else { success = false; break; } } } if(!success) { System.out.printf("Fewer than the expected number of elements found.\n"); } sc.close(); } return result; } /**Read a NxN array from a file. * Assumes properly formatted file. * @param sc the Scanner to read from * @param N side length of array in file * @return the NxN array */ private static double[][] readDoubleArray(Scanner sc, int N) { double[][] result = null; assert N >= 0; if (sc != null) { result = new double[N][N]; boolean success = true; for(int row = 0; row < N && success; ++row) { for(int col = 0; col < N; ++col) { if (sc.hasNextDouble()) { result[row][col] = sc.nextDouble(); } else { success = false; break; } } } if(!success) { System.out.printf("Fewer than the expected number of elements found.\n"); } sc.close(); } return result; } /** * prompt for int, skipping any invalid preamble * @param prompt prompt to display * @return the entered int */ private static int inputInt(String prompt) { System.out.print(prompt); while (!sc.hasNextInt()) sc.next(); // discard non-int token int retVal = sc.nextInt(); sc.nextLine(); // dump CR from stream return retVal; } /** * prompt for unsigned int, skipping any invalid preamble * @param prompt prompt to display * @return the entered int */ private static int inputUnsignedInt(String prompt) { System.out.print(prompt); int result = -1; while (result < 0) { while (!sc.hasNextInt()) sc.next(); // discard non-int token result = sc.nextInt(); } sc.nextLine(); // dump CR from stream return result; } /** * prompt for a float, skipping any invalid preamble * @param prompt prompt to display * @return the entered float */ private static float inputFloat(String prompt) { System.out.print(prompt); while (!sc.hasNextFloat()) sc.next(); // discard non-matching token float retVal = sc.nextFloat(); sc.nextLine(); // dump CR from stream return retVal; } /** * prompt for a double, skipping any invalid preamble * @param prompt prompt to display * @return the entered double */ private static double inputDouble(String prompt) { System.out.print(prompt); while (!sc.hasNextDouble()) sc.next(); // discard non-matching token double retVal = sc.nextDouble(); sc.nextLine(); // dump CR from stream return retVal; } private static String inputLine(String prompt) // Simple prompt for a line of text { System.out.print(prompt); String result; // Key here is to discard empty lines (including \n's left in the buffer), but we make it a bit more robust to whitespace... do { result = sc.nextLine(); result = result.trim(); } while (result.length() == 0); return result; } private final static Scanner sc = new Scanner(System.in); }