package solution; import java.util.Arrays; import java.util.Collections; import java.util.Scanner; import java.util.ArrayList; import java.util.List; import java.io.File; import java.io.FileNotFoundException; import javax.swing.JFileChooser; import javax.swing.filechooser.FileNameExtensionFilter; /** * @author Eric Durant * Created on November 14, 2013 * Last updated November 19, 2013 * Java sample solutions for Op 2013 */ public class Op2013 { /** * Console menu allowing selection of any of the 9 demo solutions * @param args (unused) * @throws FileNotFoundException */ public static void main(String[] args) { boolean done = false; while(!done) { System.out.println("Op 2013 Demo Program"); System.out.println("1. Parallel resistance"); System.out.println("2. Scrabble score"); System.out.println("3. Bertrand's ballot theorem"); System.out.println("4. Anagrams"); System.out.println("5. Angle between clock hands"); System.out.println("6. Text histogram"); System.out.println("7. Bertrand's ballot theorem simulation"); System.out.println("8. Spin ice"); System.out.println("9. Dispatching cabs"); 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. Parallel resistance (10 Points) */ public static void prob1() { /* * Calculate the “parallel resistance” of three loads (1st 3 inputs from user) placed across a “voltage source” (4th and final input from user). * For example, the voltage source might be a battery and the loads might be an LED (light source), computer processor, and camera. * Ohm’s law tells us that the electrical current (proportional to electrons per second) flowing through a load is equal to the voltage divided * by the resistance, or I = V/R. These quantities are given in units of amperes (A), volts (V), and ohms ($\Omega$), respectively. * For example, if we have a 5 V battery and an LED with resistance of 1000 $\Omega$, the resulting current through the LED is 5/1000 V/ $\Omega$ = 0.005 A. * * In our example, all 3 devices are placed with their ends touching the ends of the voltage source since they each require the voltage it provides. * In this case, the voltage source is providing the calculated current to each of the 3 devices, * thus we can add the 3 device currents to get the current out of the voltage source. * Now that we have the total current, we can solve for the parallel resistance again using Ohm’s law: Itotal = V/Rparallel. * * For example, if the user tells us the resistances are 500, 1000, and 2500 \Omega, and the voltage is 5 V, the currents would be * 0.01, 0.005, and 0.002 A. Adding the currents together, we get a total of 0.017 A. * Then, we can solve for the resistance as seen from the voltage source by solving the equation for R: R = V / I = 5 V / 0.017 A = 294.12 \Omega. * * Note: There is a shortcut to this problem that allows you to ignore the input voltage. You may use it, but are not required to do so. */ final double R1 = inputDouble("R1? "); final double R2 = inputDouble("R2? "); final double R3 = inputDouble("R3? "); final double V = inputDouble("V ? "); final double[] I = { V/R1, V/R2, V/R3 }; double I_tot = 0; for (double i : I) I_tot += i; System.out.printf("The parallel resistance is %g.\n", V/I_tot); } /** * Problem 2. Scrabble score (10 Points) */ public static void prob2() { /* * Ask the user to input a word and calculate its Scrabble score. * The user may use any mix of uppercase and lowercase, but case is not significant in your calculation. * You may assume that the user enters only letters. The values of the letters are given below: * • 1 point: A, E, I, L, N, O, R, S, T, U * • 2 points: D, G * • 3 points: B, C, M, P * • 4 points: F, H, V, W, Y * • 5 points: K * • 8 points: J, X * • 10 points: Q, Z */ // A B C D E F G H I J K L M N O P Q R S T U V W X Y Z final int[] points = {1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10}; final String word = inputLine("Enter a word: "); int score = 0; for (char c: word.toCharArray()) score += points[Character.toUpperCase(c)-'A']; System.out.printf("The word [%s] is worth %d points.\n", word, score); } /** * Problem 3. Bertrand's ballot theorem (10 Points) */ public static void prob3() { /* * Bertrand’s ballot theorem calculates the probability that the ultimate winner of a 2-candidate election is ahead after each individual vote is cast * (so, they must receive the first vote; we are ignoring ties). * Given that candidate A is the ultimate winner and receives p votes and candidate B receives q votes, the probability is: * (p-q)/(p+q) * Ask the user to input p and q and calculate the probability. Assume the user enters positive integers. * Your answer should take the form of “Candidate B, who received 13 out of 20 votes, had a 0.3 chance of being ahead throughout the election.” * This example is for p = 7 and q = 13. Your sentence should begin with which ever candidate received more votes. */ final int p = inputUnsignedInt("Candidate A votes? "); final int q = inputUnsignedInt("Candidate B votes? "); final int s = p+q; final double prob = (p-q)/(double)s; // It is given that there is a first vote, therefore we ignore s==0 case final boolean aWon = prob >= 0; // ignoring ties per spec. System.out.printf("Candidate %c, who received %d out of %d votes, had a %g chance of being ahead throughout the election.\n", aWon?'A':'B', aWon?p:q, s, aWon?prob:-prob); } /** * Problem 4. Anagrams (20 Points) */ public static void prob4() { /* * Ask the user to input 2 words. Ignore case. Assume the user enters only letters. * Report whether the words are anagrams of each other, that is, whether they contain the identical number of each letter. * Note that the words must be of exactly the same length in order to possibly be anagrams. */ final String word1 = inputLine("Enter a first word: "); final String word2 = inputLine("Enter another word: "); if (word1.length() != word2.length()) System.out.println("The words are of different lengths, so they cannot be anagrams."); // not necessary to point this out as long as program reports not anagrams else { final int[] bucket = new int['Z'-'A'+1]; for (char c: word1.toCharArray()) ++bucket[Character.toUpperCase(c)-'A']; // Specification REQUIRES ignoring case for (char c: word2.toCharArray()) --bucket[Character.toUpperCase(c)-'A']; // buckets will all be at 0 if they're anagrams boolean anagram = true; for (int b: bucket) { if (b != 0) { anagram = false; break; } } System.out.println("The truth value of the words being anagrams is: " + Boolean.toString(anagram)); } } /** * Problem 5. Angle between clock hands (20 Points) */ public static void prob5() { /* Ask the user for the hour and minute and display the resulting angle between the hands of a clock. * Assume that the hour is correctly entered as an integer between 1 and 12 and that * the minute is correctly entered as a real number between 0 and 60. * The angle must be presented as a value between 0 and 180 degrees inclusive. */ final int h = inputUnsignedInt("Hour? "); final double m = inputDouble("Minute? "); double degrees = Math.abs(60*h-11*m)/2; // 0-360 if (degrees > 180) degrees = 360-degrees; // ensure on 0-180 System.out.println("The angle is " + degrees + " degrees."); } /** * Problem 6. Text histogram (20 Points) */ public static void prob6() { /* * A histogram is a graph that shows the number of values in each of a series of bins. * It is a useful tool for showing where values are most popular. * A histogram is computed by dividing the number line up into a series of bins of the same size and counting the number of values in each bin’s range. * For the sequence of data: 3 6 1 1 2 3 4 5 1 3 6 2 9 4 2 3 4 6 6 7 and bins starting at 0 with a width of 2, the following histogram would result: * 0 - 1 *** * 2 - 3 ******* * 4 - 5 **** * 6 - 7 ***** * 8 - 9 * * * This result is correct because there are 3 values in the range 0 to 1, 7 in the range 2 to 3, …, and 1 in the range 8-9. * * Write a program that inputs from the user: * • a starting bin location (for the example above this is 0), * • the bin width (for the example above this is 2), * • and all the values on a single line, separated by spaces. * Compute and plot the histogram in a format similar to the example above. You may assume that all the data are integers. * If the starting location is greater than the minimum of the data, respond as if the user entered the minimum for the starting location. */ int left = inputInt("Starting bin location: "); // below code assumes this is not greater than the minimum final int width = inputInt("Bin width: "); final Scanner data = new Scanner(inputLine("All the values on a single line, separated by spaces: ")); final List li = new ArrayList(); while (data.hasNextInt()) li.add(data.nextInt()); data.close(); final int min = Collections.min(li), max = Collections.max(li); if (left > min) left = min; final int totalWidth = max-left+1; final int binCount = totalWidth / width + ((totalWidth % width > 0) ? 1 : 0); final int[] bin = new int[binCount]; for(int i : li) ++bin[(i-left) / width]; for(int i = 0; i < bin.length; ++i) { char[] stars = new char[bin[i]]; Arrays.fill(stars , '*'); // There are more efficient ways, but probably no simpler ways because of limitations on int, etc. System.out.printf("%d - %d %s\n", left+(i*width), left+(i+1)*width-1, new String(stars)); } } /** * Problem 7. Bertrand’s ballot theorem simulation (40 Points) */ public static void prob7() { /* * Write a program to simulate a large number of elections to confirm whether Bertrand’s ballot theorem (see problem 3) is approximately correct. * You will ask the user for 3 inputs, and you may assume they are all valid: * • The number of voters (an integer from 1 to 1000) * • The probability that each voter will vote for candidate A (between 0 and 1) * • The number of elections to simulate (an integer from 1 to 1000) * * For each election, you must determine whether the ultimate winner was always ahead, starting with the first vote. * Thus, if A receives the 1st vote, but falls behind or reaches parity at any point as votes are tallied (regardless of whether A wins), * the election counts in the “not always ahead” category. * Note that it is possible and okay that A and B each win some elections where they are always ahead. * * After running all the elections (at least far enough to categorize them), * you will divide the “always ahead” election count by the total election count and summarize the relevant facts for the user similarly to the following: * “In 1000 simulated elections each with 100 voters, where candidate A received each vote with probability 0.65, the winner was always ahead 0.287 of the time.” * * Note: This is solving a similar, but different problem, than Bertrand’s theorem. * Results may differ greatly for small electorates. * While Bertrand’s theorem applies to exact numbers of votes, this problem concerns the probability of receiving each vote. * * Note: Your program will need random numbers to solve this. See the end of this document for information on how to generate these. */ final int numberVoters = inputUnsignedInt("Number of voters (integer from 1 to 1000): "); final double probabilityA = inputDouble("Probability that each voter will vote for candidate A (between 0 and 1): "); final int numberElections = inputUnsignedInt("Number of elections to simulate (integer from 1 to 1000): "); final java.util.Random myGenerator = new java.util.Random(); int countAlwaysAhead = 0; for (int election = 0; election < numberElections; ++election) { final int[] votes = new int[2]; // votes for A and B // first voter is special case final boolean aAlwaysLeading = myGenerator.nextDouble() < probabilityA; // abort election if this would be about to change int vote = aAlwaysLeading ? 0 : 1; ++votes[vote]; boolean alwaysLeading = true; for(int voter = 1; voter < numberVoters; ++voter) { vote = myGenerator.nextDouble() < probabilityA ? 0 : 1; ++votes[vote]; if (aAlwaysLeading != votes[0] > votes[1] || votes[0] == votes[1]) { // if the wrong candidate just pulled ahead or into a tie; handling of terminal ties unimportant per problem spec. alwaysLeading = false; break; // end this election } } if (alwaysLeading) ++countAlwaysAhead; } System.out.printf("In %d simulated elections each with %d voters, where candidate A received each vote with probability %g, the winner was always ahead %g of the time.\n", numberElections, numberVoters, probabilityA, (double)countAlwaysAhead/numberElections); } /** * Problem 8. Spin ice (40 Points) * @throws FileNotFoundException */ public static void prob8() throws FileNotFoundException { /* * A “spin ice” is a substance similar to a crystalline solid in that its atoms are locked in fixed, regular positions. * Each vertex (intersection of a horizontal line with a vertical line) in the diagram represents an atom. * Each atom can have one of two spin states (it is convenient to think of it as spinning either up or down, * but spin is actually a quantum mechanical property that doesn’t have such a physical interpretation). * Usually the spins are balanced around alternating spaces between the atoms (indicated by gray squares; * note they always occur where the sum of the row and column numbers is odd). * Balanced means that there equal numbers of inward pointing spins (spins on the bottom pointing up and spins on the top pointing down) * as outward pointing spins. Note also that spins never point at any of the white squares (gaps between atoms). * Sometimes spins are reversed from a balanced pattern, however, such as the 4 spins marked by dark gray triangles. * Note that the space at r3c4 (row 3, column 4), marked N, is out of balance since 3 spins are pointing in and 1 spin is pointing out. * Similarly, the space at r5c6, marked S, is out of balance with 1 spin pointing in and 3 pointing out. * The N and S represent the north and south poles of a microscopic magnet. There is also a north pole at r1c2. * Note that there are more north poles than south poles! * This actually happens in certain spin ices, although it is not something that normally makes sense * when we think of normal magnets where north and south poles always come in pairs. * * The spins in the diagram can be represented by the following array. * The gray spins in the diagram are marked by bold-italic text and break the checkerboard pattern of 0s and 1s. * Note that there is one more row and one more column in the atom spin array than there is in the array of spaces between atoms; * atom [0,0] with its spin is at the upper left side of space [0,0], atom [8,8] with its spin is at the lower right of space [7,7]. * * You must write a program that inputs an array of 1s and 0s as shown from a user-specified file. * You may assume it is always a 9×9 array as illustrated and that there are 9 lines each containing 9 characters; * there are no row or column labels in the file. Your program will do the following: * * c012345678 * r0: 101000101 * r1: 010001010 * r2: 101010101 * r3: 010101010 * r4: 101011101 * r5: 010101110 * r6: 101010101 * r7: 010101010 * r8: 101010101 * * • Find the locations of all the contained N and S poles and report them. In this case, you would report, “N at [1,2][3,4]. S at [5,6].” * The precise formatting isn’t critical as long as the meaning is clear. * • Report whether the net polarity is N or S and how strong it is. In the example, there are 2 Ns and 1 S, so the net polarity is 1 N. * So, in this case you would report “Net polarity: 1 N”, but in some situations you might report “Net polarity: 2 S”, “Net polarity: 0”, etc. * * Note: you may assume that no spaces have 4 arrows pointing to or away from them. * That represents a spin configuration that is physically impossible. */ final int spins[][] = readSpins(); /* * Note: Competitors do NOT need to make this check. This section checks that an input file represents a physically realizable configuration, * that is, one with no spaces that have 4 arrows pointing to or away from them. If this section identifies an invalid file, than that file * should NOT be used to determine whether student programs are correct. */ boolean success = true; // if we fail at any point, we report the error and break out for (int row = 0; row < spins.length-1 && success; ++row) { // iterating over spaces, so one less than spins/atoms for (int col = 1-row%2; col < spins[0].length-1; col+=2) { // row+col must be odd final int T = spins[row ][col]+spins[row ][col+1], // # up from top B = spins[row+1][col]+spins[row+1][col+1]; // # up from bottom final int inward = B - T; if (inward == 2 || inward == -2) { System.out.printf("Invalid spin state found at space [%d,%d].", row, col); success = false; break; } } } /* * • Find the locations of all the contained N and S poles and report them. In this case, you would report, “N at [1,2][3,4]. S at [5,6].” * The precise formatting isn’t critical as long as the meaning is clear. */ if (success) { for (int row = 0; row < spins.length-1; ++row) { // iterating over spaces, so one less than spins/atoms for (int col = 1-row%2; col < spins[0].length-1; col+=2) { // row+col must be odd final int T = spins[row ][col]+spins[row ][col+1], // # up from top B = spins[row+1][col]+spins[row+1][col+1]; // # up from bottom final int inward = B - T; if (inward != 0) { System.out.print(pole(inward)); System.out.println(" at [" + row + ',' + col + ']'); } } } } /* * • Report whether the net polarity is N or S and how strong it is. * In the example, there are 2 Ns and 1 S, so the net polarity is 1 N. * So, in this case you would report “Net polarity: 1 N”, but in some situations you might report “Net polarity: 2 S”, “Net polarity: 0”, etc. */ if (success) { /* I expect student solutions to take the easy route and simply sum the results they found in the previous section. * However, I implement a different approach here, partially as a cross-check on the validity of the algorithm. * I use just the border values to calculate the net polarity; internal disturbances don't affect the net polarity * Note that in the example data there are 2 more spins pointing in than out (the difference will always be an even number). * Note that the spins at [0,0] and [8,8] are ignored since they do not cross the boundary of any of the gray squares. */ int inward = 0; // net inward arrows for (int col = 0; col < spins[0].length-1; ++col) { // indexes across bottom (including LL); +1 to get top (including UR) inward += (spins[spins.length-1][col] == 1 ? 1 : -1) // bottoms pointing up are in + (spins[0][col+1] == 0 ? 1 : -1); // tops pointing down are in } for (int row = 1; row < spins.length-1; ++row) { // indexes down sides, neglecting top & bottom rows, which are already taken care of inward += ((spins[row][0 ] == row %2) ? -1 : 1) // lefts up are out (S, neg) on odd rows + ((spins[row][spins[0].length-1] == ((row+spins[0].length)-1)%2) ? 1 : -1); // rights down are out when row+col is even } assert inward % 2 == 0 : inward; // there is a logic error if inward isn't even System.out.print("Net polarity: " + Integer.toString(Math.abs(inward)/2) + ' '); // abs first to ensure rounding towards 0 if (inward != 0) System.out.print(pole(inward)); System.out.println(); } } private static char pole(int inward) { // assumes inward != 0 return inward>0 ? 'N' : 'S'; } private static int SIZE = 9; // specified as constant for problem /** * Ask user to select file (beware: dialog is sometimes raised in background). * This function is given to the competitors, but they may modify it if they desire a different representation. * @return 2-D array of spins of size SIZE x SIZE * @throws FileNotFoundException if the picked file is not found when we attempt to open it */ private static int[][] readSpins() throws FileNotFoundException { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter(new FileNameExtensionFilter("Text files", "txt")); if(chooser.showOpenDialog(null) != JFileChooser.APPROVE_OPTION) { throw new IllegalArgumentException("User did not select a file"); } // Open the specified file final Scanner fsc = new Scanner(chooser.getSelectedFile()); // throws FileNotFoundException // Read the file, assuming it is properly formatted; may throw runtime exceptions for formatting errors final int[][] spins = new int[SIZE][SIZE]; for (int row = 0; row < spins.length; ++row) { final String line = fsc.nextLine(); assert line.length() == spins[row].length; for (int col = 0; col < spins[row].length; ++col) { spins[row][col] = (line.charAt(col) == '0') ? 0 : 1; } } fsc.close(); return spins; } /** * Problem 9. Dispatching cabs (40 Points) */ public static void prob9() { /* You are given two data files (samples are available on the contest website). * Cabs.txt contains the location of available cabs at the beginning of the day. * Each cab has a sequential ID number beginning with 0. * Passengers.txt contains the location of passengers in the order that they call for a cab. * The passenger names in order are ‘A’, ‘B’, ‘C’, etc.; there are at most 26 passengers. * For the purposes of this problem, people take very long cab rides; once a cab is used, it is never available again. * Write a program that, given names of files containing cab locations and passengers, assigns the closest cab to each passenger and dispatches * it as the calls come in; your program will output a list of cabs and passengers (for example, “Cab 3 dispatched to passenger N.”). * If there are more passengers than cabs, stop after all cabs are dispatched. * Since the cabs need to travel streets that form a grid pattern with only right angles, “distance” between two points is defined as * the distance in the x direction plus the distance in the y direction (no square roots or trigonometry are involved). */ // Prompt user, or set PATH to where you keep your data files. String PATH = "C:\\Users\\durant\\Desktop\\op2013\\data\\"; final double[][] cabs = readArray(PATH + "p9-cabs.txt"); final double[][] passengers = readArray(PATH + "p9-passengers.txt"); for (int passId = 0; passId < Math.min(passengers.length, cabs.length); ++passId) { double minDistance = Double.MAX_VALUE; int cabMinIdx = -1; // invalid for (int thisCab = 0; thisCab < cabs.length; ++thisCab) { double distance = Math.abs(passengers[passId][0]-cabs[thisCab][0]) + Math.abs(passengers[passId][1]-cabs[thisCab][1]); if (distance < minDistance) { minDistance = distance; cabMinIdx = thisCab; } } assert cabMinIdx >= 0 : cabMinIdx; System.out.printf("Cab %d dispatched to passenger %c at city-block distance %g.\n", cabMinIdx, 'A'+passId, minDistance); cabs[cabMinIdx][0] = cabs[cabMinIdx][1] = Double.MAX_VALUE; } } /**Read a Nx2 array from a file. Assumes properly formatted file. * @param fn the name of the file to read from * @return the Nx2 array (N is determined from file) */ private static double[][] readArray(String fn) { Scanner sc = null; double[][] retval = null; try { sc = new Scanner(new File(fn)); } catch (FileNotFoundException e) { System.out.printf("File not found: [%s].\n", fn); } if (sc != null) { final int N = sc.nextInt(); retval = new double[N][2]; boolean success = true; for(int row = 0; row < N && success; ++row) { for(int col = 0; col < 2; ++col) { if (sc.hasNextDouble()) { retval[row][col] = sc.nextDouble(); } else { success = false; break; } } } if(!success) { System.out.printf("Fewer than the expected number of elements found in [%s].\n", fn); } sc.close(); } return retval; } /** * prompt for int, skipping any invalid preamble * @param prompt prompt to display * @return the entered int */ public 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 */ public static int inputUnsignedInt(String prompt) { System.out.print(prompt); int retval = -1; while (retval < 0) { while (!sc.hasNextInt()) sc.next(); // discard non-int token retval = sc.nextInt(); } 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 */ public 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; } public static String inputLine(String prompt) // Simple prompt for a line of text { System.out.print(prompt); String retval; // 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 { retval = sc.nextLine(); retval = retval.trim(); } while (retval.length() == 0); return retval; } private final static Scanner sc = new Scanner(System.in); }