init
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import java.io.*;
|
||||
|
||||
public class Bomb {
|
||||
public static final int START = 124;
|
||||
public static final int CODE_LENGTH = 6;
|
||||
|
||||
public static void defuse(String code) throws Exception {
|
||||
if (code.length() != CODE_LENGTH) {
|
||||
throw new Bomb.Explosion();
|
||||
}
|
||||
|
||||
String curr = Integer.toHexString(CODE_LENGTH);
|
||||
for (int i = 1; i < CODE_LENGTH; i++) {
|
||||
curr = scramble(curr);
|
||||
if (curr.charAt(1) != code.charAt(i)) {
|
||||
throw new Bomb.Explosion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String scramble(String secret) {
|
||||
return Integer.toHexString(Integer.parseInt(secret, 16) * 0xA9 / 0b010011);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println();
|
||||
System.out.println("Bomb armed and ready for defusal.");
|
||||
System.setOut(new PrintStream(new OutputStream() { public void write(int b) {} }));
|
||||
|
||||
// defuse("21d74"); // TODO: Find the code, hurry!
|
||||
defuse("000000");
|
||||
System.setOut(SYSTEM_OUT);
|
||||
System.out.println("Bomb defused! You saved the day!");
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public static final PrintStream SYSTEM_OUT = System.out;
|
||||
private static class Explosion extends RuntimeException {
|
||||
public Explosion() { super("BOOM"); }
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Current File",
|
||||
"request": "launch",
|
||||
"mainClass": "${file}"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Client",
|
||||
"request": "launch",
|
||||
"mainClass": "Client",
|
||||
"projectName": "c1_b4eddbb1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A strategy game where all players have perfect information and no theme
|
||||
* or narrative around gameplay.
|
||||
*/
|
||||
public abstract class AbstractStrategyGame {
|
||||
/**
|
||||
* Constructs and returns a String describing how to play the game. Should include
|
||||
* any relevant details on how to interpret the game state as returned by toString(),
|
||||
* how to make moves, the game end condition, and how to win.
|
||||
*/
|
||||
public abstract String instructions();
|
||||
|
||||
/**
|
||||
* Constructs and returns a String representation of the current game state.
|
||||
* This representation should contain all information that should be known to
|
||||
* players at any point in the game, including board state (if any) and scores (if any).
|
||||
*/
|
||||
public abstract String toString();
|
||||
|
||||
/**
|
||||
* Returns true if the game has ended, and false otherwise.
|
||||
*/
|
||||
public boolean isGameOver() {
|
||||
return getWinner() != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the player who has won the game,
|
||||
* or -1 if the game is not over.
|
||||
*/
|
||||
public abstract int getWinner();
|
||||
|
||||
/**
|
||||
* Returns the index of the player who will take the next turn.
|
||||
* If the game is over, returns -1.
|
||||
*/
|
||||
public abstract int getNextPlayer();
|
||||
|
||||
/**
|
||||
* Takes input from the parameter to specify the move the player
|
||||
* with the next turn wishes to make, then executes that move.
|
||||
* If any part of the move is illegal, throws an IllegalArgumentException.
|
||||
*/
|
||||
public abstract void makeMove(Scanner input);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import java.util.*;
|
||||
|
||||
public class Client {
|
||||
public static void main(String[] args) {
|
||||
Scanner console = new Scanner(System.in);
|
||||
AbstractStrategyGame game = new ConnectFour("dude", "bro");
|
||||
|
||||
System.out.println(game.instructions());
|
||||
System.out.println();
|
||||
|
||||
while (!game.isGameOver()) {
|
||||
System.out.println(game);
|
||||
System.out.printf("Player %d's turn.\n", game.getNextPlayer());
|
||||
try {
|
||||
game.makeMove(console);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
System.out.println("**Illegal move: " + ex.getMessage());
|
||||
}
|
||||
/**
|
||||
* Note - the above structure is a try/catch, which is something
|
||||
* we've included to help deal with the IllegalArgumentExceptions
|
||||
* in your abstract strategy game!
|
||||
* We want to remind you that try/catch is a forbidden feature in 123,
|
||||
* so you SHOULD NOT INCLUDE IT in any code you submit (other than this file)!
|
||||
* Please see the Code Quality Guide for more info on this.
|
||||
*/
|
||||
}
|
||||
System.out.println(game);
|
||||
int winner = game.getWinner();
|
||||
if (winner > 0) {
|
||||
System.out.printf("Player %d wins!\n", winner);
|
||||
} else {
|
||||
System.out.println("It's a tie!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Nik Johnson
|
||||
// 4-15-2024
|
||||
// TA: Zachary Bi
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ConnectFour extends AbstractStrategyGame {
|
||||
|
||||
private char[][] board;
|
||||
private int currentPlayer;
|
||||
private String[] players;
|
||||
private List<Character> playerSymbols;
|
||||
|
||||
public ConnectFour(String player1, String player2) {
|
||||
this.board = new char[6][7];
|
||||
this.players = new String[2];
|
||||
this.playerSymbols = new ArrayList<>();
|
||||
this.currentPlayer = 0;
|
||||
players[0] = player1;
|
||||
players[1] = player2;
|
||||
playerSymbols.add('X');
|
||||
playerSymbols.add('O');
|
||||
|
||||
// initialize empty board
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
for (int n = 0; n < board[i].length; n++) {
|
||||
board[i][n] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String instructions() {
|
||||
String output = "";
|
||||
|
||||
output += "Welcome to Connect Four! This game consists of a 7x6 grid, each column of";
|
||||
output += " which you may choose to 'drop' a disc down by inputting numbers 1-7.\n";
|
||||
output += "Player 1's symbol is 'X', Player 2's is 'O'\n";
|
||||
output += "To win, you must place 4 of your discs in a row, vertically or horizontally.\n";
|
||||
output += "Have fun!";
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// constructs game board in readable format
|
||||
// no parameters
|
||||
// returns formatted representation of game board
|
||||
public String toString() {
|
||||
String output = "";
|
||||
for (char[] row : board) {
|
||||
for (char loc : row) {
|
||||
output += "[" + loc + "]";
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public int getWinner() {
|
||||
// rows
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
for (int j = 0; j <= board[i].length - 4; j++) {
|
||||
char symbol = board[i][j];
|
||||
if (symbol != ' ' && board[i][j + 1] == symbol && board[i][j + 2] == symbol && board[i][j + 3] == symbol) {
|
||||
return playerSymbols.indexOf(symbol) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// columns
|
||||
for (int j = 0; j < board[0].length; j++) {
|
||||
for (int i = 0; i <= board.length - 4; i++) {
|
||||
char symbol = board[i][j];
|
||||
if (symbol != ' ' && board[i + 1][j] == symbol && board[i + 2][j] == symbol && board[i + 3][j] == symbol) {
|
||||
return playerSymbols.indexOf(symbol) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public int getNextPlayer() {
|
||||
return this.currentPlayer + 1;
|
||||
}
|
||||
|
||||
public void makeMove(Scanner input) {
|
||||
System.out.println(players[getNextPlayer() - 1] + ": Choose a column (1-7)");
|
||||
int col = Integer.parseInt(input.next()) - 1;
|
||||
|
||||
if (col < 0 || col > 6) {
|
||||
throw new IllegalArgumentException("selected column out of bounds");
|
||||
}
|
||||
|
||||
boolean notPlaced = true;
|
||||
for (int n = board.length - 1; n >= 0 && notPlaced; n--) {
|
||||
if (board[n][col] == ' ') {
|
||||
if (currentPlayer == 0) {
|
||||
board[n][col] = 'X';
|
||||
} else if (currentPlayer == 1) {
|
||||
board[n][col] = 'O';
|
||||
}
|
||||
notPlaced = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPlayer == 0) {
|
||||
currentPlayer = 1;
|
||||
} else if (currentPlayer == 1) {
|
||||
currentPlayer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import org.junit.jupiter.api.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Testing {
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST CASE - Small TicTacToe Example")
|
||||
public void firstCaseTest() {
|
||||
AbstractStrategyGame g = new TicTacToe();
|
||||
|
||||
// You can add optional error messages that will be displayed if a test fails
|
||||
assertEquals(1, g.getNextPlayer(), "Player 1 not next player after construction");
|
||||
assertEquals(-1, g.getWinner(), "Winner incorrectly declared after construction");
|
||||
assertFalse(g.isGameOver(), "Game over immediately after construction");
|
||||
|
||||
// Weird way we're going to make moves - make our own scanners NOT
|
||||
// connected to System.in. Since we can make Scanners over strings this will
|
||||
// work the exact way and allow us to control input!
|
||||
g.makeMove(new Scanner("0 0"));
|
||||
assertEquals(2, g.getNextPlayer(), "Player 2 not next player after a single move");
|
||||
assertEquals(-1, g.getWinner(), "Winner incorrectly declared after a single move");
|
||||
assertFalse(g.isGameOver(), "Game over immediately after construction");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
// -1 is an illegal move so our code should throw an IllegalArgumentException
|
||||
g.makeMove(new Scanner("-1 2"));
|
||||
}, "IllegalArgumentException not thrown for illegal move");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST CASE - Large TicTacToe Example")
|
||||
public void secondCaseTest() {
|
||||
// You definitely don't have to get this fancy in your tests!
|
||||
AbstractStrategyGame g = new TicTacToe();
|
||||
|
||||
// Going to play a whole game where 1 plays in first row, 2 plays in second row
|
||||
// No optional error messages - up to you if you want your code to be easier to debug!
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int player = (i % 2) + 1;
|
||||
assertEquals(player, g.getNextPlayer());
|
||||
assertFalse(g.isGameOver());
|
||||
|
||||
int col = i / 2;
|
||||
g.makeMove(new Scanner(player + " " + col));
|
||||
}
|
||||
|
||||
// At this point, 5 moves have been played, player 1 should have three in a row and
|
||||
// player 2 should have two
|
||||
assertTrue(g.isGameOver());
|
||||
assertEquals(1, g.getWinner());
|
||||
assertEquals(-1, g.getNextPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// **THIS IS AN EXAMPLE IMPLEMENTATION!**
|
||||
// Brett Wortzman
|
||||
// CSE 123
|
||||
// C0: Abstract Strategy Games
|
||||
//
|
||||
// A class to represent a game of tic-tac-toe that implements the
|
||||
// AbstractStrategyGame interface.
|
||||
import java.util.*;
|
||||
|
||||
public class TicTacToe extends AbstractStrategyGame {
|
||||
private char[][] board;
|
||||
private boolean isXTurn;
|
||||
|
||||
// Constructs a new TicTacToe game.
|
||||
public TicTacToe() {
|
||||
board = new char[][]{{'-', '-', '-'},
|
||||
{'-', '-', '-'},
|
||||
{'-', '-', '-'}};
|
||||
isXTurn = true;
|
||||
}
|
||||
|
||||
// Returns whether or not the game is over.
|
||||
public boolean isGameOver() {
|
||||
return getWinner() >= 0;
|
||||
}
|
||||
|
||||
// Returns the index of the winner of the game.
|
||||
// 1 if player 1 (X), 2 if player 2 (O), 0 if a tie occurred,
|
||||
// and -1 if the game is not over.
|
||||
public int getWinner() {
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
// check row i
|
||||
if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != '-') {
|
||||
return board[i][0] == 'X' ? 1 : 2;
|
||||
}
|
||||
|
||||
// check col i
|
||||
if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != '-') {
|
||||
return board[0][i] == 'X' ? 1 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
// check diagonals
|
||||
if (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != '-') {
|
||||
return board[0][0] == 'X' ? 1 : 2;
|
||||
}
|
||||
if (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] != '-') {
|
||||
return board[0][2] == 'X' ? 1 : 2;
|
||||
}
|
||||
|
||||
// check for tie
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
for (int j = 0; j < board[i].length; j++) {
|
||||
if (board[i][j] == '-') {
|
||||
// unfilled space; game not over
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it's a tie!
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the index of which player's turn it is.
|
||||
// 1 if player 1 (X), 2 if player 2 (O), -1 if the game is over
|
||||
public int getNextPlayer() {
|
||||
if (isGameOver()) {
|
||||
return -1;
|
||||
}
|
||||
return isXTurn ? 1 : 2;
|
||||
}
|
||||
|
||||
// Given the input, places an X or an O where
|
||||
// the player specifies.
|
||||
// Throws an IllegalArgumentException if the position is
|
||||
// invalid, whether that be out of bounds or already occupied.
|
||||
// Board bounds are [0, 2] for both rows and cols.
|
||||
public void makeMove(Scanner input) {
|
||||
char currPlayer = isXTurn ? 'X' : 'O';
|
||||
|
||||
System.out.print("Row? ");
|
||||
int row = input.nextInt();
|
||||
System.out.print("Column? ");
|
||||
int col = input.nextInt();
|
||||
|
||||
makeMove(row, col, currPlayer);
|
||||
isXTurn = !isXTurn;
|
||||
}
|
||||
|
||||
// Private helper method for makeMove.
|
||||
// Given a row and col, as well as player index,
|
||||
// places an X or an O in that row and col.
|
||||
// Throws an IllegalArgumentException if the position is
|
||||
// invalid, whether that be out of bounds or already occupied.
|
||||
// Board bounds are [0, 2] for both rows and cols.
|
||||
private void makeMove(int row, int col, char player) {
|
||||
if (row < 0 || row >= board.length ||
|
||||
col < 0 || col >= board[0].length) {
|
||||
throw new IllegalArgumentException("Invalid board position: " + row + "," + col);
|
||||
}
|
||||
|
||||
if (board[row][col] != '-') {
|
||||
throw new IllegalArgumentException("Space already occupied: " + row + "," + col);
|
||||
}
|
||||
|
||||
board[row][col] = player;
|
||||
}
|
||||
|
||||
// Returns a String containing instructions to play the game.
|
||||
public String instructions() {
|
||||
String result = "";
|
||||
result += "Player 1 is X and goes first. Choose where to play by entering a row and\n";
|
||||
result += "column number, where (0, 0) is the upper left and (2, 2) is the lower right.\n";
|
||||
result += "Spaces show as a - are empty. The game ends when one player marks three spaces\n";
|
||||
result += "in a row, in which case that player wins, or when the board is full, in which\n";
|
||||
result += "case the game ends in a tie.";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a String representation of the current state of the board.
|
||||
public String toString() {
|
||||
String result = "";
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
for (int j = 0; j < board.length; j++) {
|
||||
result += board[i][j] + " ";
|
||||
}
|
||||
result += "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import java.awt.*;
|
||||
|
||||
public class Client {
|
||||
public static void main(String[] args) {
|
||||
// 1. Create a new picture sized 400 x 400 pixels
|
||||
// 2. Call divide divide canvas
|
||||
// 3. Save the image to display it
|
||||
Picture pic = new Picture(500,500);
|
||||
divideCanvas(pic, 4);
|
||||
pic.save("boner.jpg");
|
||||
}
|
||||
|
||||
public static void divideCanvas(Picture p, int n) {
|
||||
Color[][] pixels = p.getPixels();
|
||||
divideCanvas(p, pixels, n, new Point(0, 0), new Point(p.width() - 1, p.height() - 1));
|
||||
p.setPixels(pixels);
|
||||
}
|
||||
|
||||
private static void divideCanvas(Picture p, Color[][] pixels, int n, Point one, Point two) {
|
||||
// extract coordinates from points
|
||||
int x1 = one.x;
|
||||
int x2 = two.x;
|
||||
int y1 = one.y;
|
||||
int y2 = two.y;
|
||||
|
||||
// make useful boundary points
|
||||
Point topLeft = new Point(x1, y1);
|
||||
Point topMiddle = new Point((x1 + x2) / 2, y1);
|
||||
// Point topRight = new Point(x2,y1);
|
||||
Point middleLeft = new Point(x1, (y1 + y2) / 2);
|
||||
Point middleMiddle = new Point(topMiddle.x, middleLeft.y);
|
||||
Point middleRight = new Point(x2, middleLeft.y);
|
||||
// Point bottomLeft = new Point(x1,y2);
|
||||
Point bottomMiddle = new Point(topMiddle.x, y2);
|
||||
Point bottomRight = new Point(x2, y2);
|
||||
|
||||
// if we are actually dividing, do this
|
||||
if (n != 0) {
|
||||
// System.out.println(topLeft);
|
||||
// System.out.println(topMiddle);
|
||||
// System.out.println(middleLeft);
|
||||
// System.out.println(middleMiddle);
|
||||
// System.out.println(middleRight);
|
||||
// System.out.println(bottomMiddle);
|
||||
// System.out.println(bottomRight);
|
||||
|
||||
fill(pixels, topLeft.x, middleMiddle.x, topLeft.y, middleMiddle.y);
|
||||
fill(pixels, topMiddle.x, middleRight.x, topMiddle.y, middleRight.y);
|
||||
fill(pixels, middleLeft.x, bottomMiddle.x, middleLeft.y, bottomMiddle.y);
|
||||
fill(pixels, middleMiddle.x, bottomRight.x, middleMiddle.y, bottomRight.y);
|
||||
|
||||
divideCanvas(p, pixels, n - 1, topLeft, middleMiddle);
|
||||
divideCanvas(p, pixels, n - 1, topMiddle, middleRight);
|
||||
divideCanvas(p, pixels, n - 1, middleLeft, bottomMiddle);
|
||||
divideCanvas(p, pixels, n - 1, middleMiddle, bottomRight);
|
||||
}
|
||||
|
||||
// otherwise, just draw the border
|
||||
else {
|
||||
fill(pixels,topLeft.x,bottomRight.x,topLeft.y,bottomRight.y);
|
||||
}
|
||||
}
|
||||
|
||||
public static void fill(Color[][] pixels, int x1, int x2, int y1, int y2) {
|
||||
for (int i = y1 + 1; i <= y2 - 1; i++) {
|
||||
for (int n = x1 + 1; n <= x2-1; n++) {
|
||||
pixels[i][n] = Color.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
// outer borders
|
||||
for (int i = y1; i <= y2; i++) {
|
||||
pixels[i][x1] = Color.BLACK;
|
||||
pixels[i][x2] = Color.BLACK;
|
||||
}
|
||||
|
||||
for (int i = x1; i <= x2; i++) {
|
||||
pixels[y1][i] = Color.BLACK;
|
||||
pixels[y2][i] = Color.BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
/******************************************************************************
|
||||
* Compilation: javac Picture.java
|
||||
* Execution: java Picture imagename
|
||||
* Dependencies: none
|
||||
*
|
||||
* Data type for manipulating individual pixels of an image. The original
|
||||
* image can be read from a file in JPG, GIF, or PNG format, or the
|
||||
* user can create a blank image of a given dimension. Includes methods for
|
||||
* displaying the image in a window on the screen or saving to a file.
|
||||
*
|
||||
* % java Picture mandrill.jpg
|
||||
*
|
||||
* Remarks
|
||||
* -------
|
||||
* - pixel (x, y) is column x and row y, where (0, 0) is upper left
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides methods for manipulating individual pixels of
|
||||
* an image using the RGB color format. The alpha component (for transparency)
|
||||
* is not currently supported.
|
||||
* The original image can be read from a {@code PNG}, {@code GIF},
|
||||
* or {@code JPEG} file or the user can create a blank image of a given dimension.
|
||||
* This class includes methods for displaying the image in a window on
|
||||
* the screen or saving it to a file.
|
||||
* <p>
|
||||
* Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>.
|
||||
* By default, the origin (0, 0) is the pixel in the top-left corner,
|
||||
* which is a common convention in image processing.
|
||||
* The method {@link #setOriginLowerLeft()} change the origin to the lower left.
|
||||
* <p>
|
||||
* The {@code get()} and {@code set()} methods use {@link Color} objects to get
|
||||
* or set the color of the specified pixel.
|
||||
* The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int}
|
||||
* to encode the color, thereby avoiding the need to create temporary
|
||||
* {@code Color} objects. The red (R), green (G), and blue (B) components
|
||||
* are encoded using the least significant 24 bits.
|
||||
* Given a 32-bit {@code int} encoding the color, the following code extracts
|
||||
* the RGB components:
|
||||
* <blockquote><pre>
|
||||
* int r = (rgb >> 16) & 0xFF;
|
||||
* int g = (rgb >> 8) & 0xFF;
|
||||
* int b = (rgb >> 0) & 0xFF;
|
||||
* </pre></blockquote>
|
||||
* Given the RGB components (8-bits each) of a color,
|
||||
* the following statement packs it into a 32-bit {@code int}:
|
||||
* <blockquote><pre>
|
||||
* int rgb = (r << 16) + (g << 8) + (b << 0);
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
* A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory,
|
||||
* since the color of each pixel is encoded as a 32-bit <code>int</code>.
|
||||
* <p>
|
||||
* For additional documentation, see
|
||||
* <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of
|
||||
* <i>Computer Science: An Interdisciplinary Approach</i>
|
||||
* by Robert Sedgewick and Kevin Wayne.
|
||||
* See {@link GrayscalePicture} for a version that supports grayscale images.
|
||||
*
|
||||
* @author Robert Sedgewick
|
||||
* @author Kevin Wayne
|
||||
*/
|
||||
public final class Picture implements ActionListener {
|
||||
private BufferedImage image; // the rasterized image
|
||||
private JFrame frame; // on-screen view
|
||||
private String filename; // name of file
|
||||
private boolean isOriginUpperLeft = true; // location of origin
|
||||
private final int width, height; // width and height
|
||||
|
||||
/**
|
||||
* Creates a {@code width}-by-{@code height} picture, with {@code width} columns
|
||||
* and {@code height} rows, where each pixel is black.
|
||||
*
|
||||
* @param width the width of the picture
|
||||
* @param height the height of the picture
|
||||
* @throws IllegalArgumentException if {@code width} is negative or zero
|
||||
* @throws IllegalArgumentException if {@code height} is negative or zero
|
||||
*/
|
||||
public Picture(int width, int height) {
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
// set to TYPE_INT_ARGB here and in next constructor to support transparency
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new picture that is a deep copy of the argument picture.
|
||||
*
|
||||
* @param picture the picture to copy
|
||||
* @throws IllegalArgumentException if {@code picture} is {@code null}
|
||||
*/
|
||||
public Picture(Picture picture) {
|
||||
if (picture == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
width = picture.width();
|
||||
height = picture.height();
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
filename = picture.filename;
|
||||
isOriginUpperLeft = picture.isOriginUpperLeft;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
image.setRGB(col, row, picture.image.getRGB(col, row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading an image from a file or URL.
|
||||
*
|
||||
* @param name the name of the file (.png, .gif, or .jpg) or URL.
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public Picture(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
this.filename = name;
|
||||
try {
|
||||
// try to read from file in working directory
|
||||
File file = new File(name);
|
||||
if (file.isFile()) {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// resource relative to .class file
|
||||
URL url = getClass().getResource(filename);
|
||||
|
||||
// resource relative to classloader root
|
||||
if (url == null) {
|
||||
url = getClass().getClassLoader().getResource(name);
|
||||
}
|
||||
|
||||
// or URL from web
|
||||
if (url == null) {
|
||||
url = new URL(name);
|
||||
}
|
||||
|
||||
image = ImageIO.read(url);
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read image: " + name);
|
||||
}
|
||||
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open image: " + name, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading the image from a PNG, GIF, or JPEG file.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public Picture(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
try {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open file: " + file, ioe);
|
||||
}
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read file: " + file);
|
||||
}
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
filename = file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link JLabel} containing this picture, for embedding in a {@link JPanel},
|
||||
* {@link JFrame} or other GUI widget.
|
||||
*
|
||||
* @return the {@code JLabel}
|
||||
*/
|
||||
public JLabel getJLabel() {
|
||||
if (image == null) return null; // no image available
|
||||
ImageIcon icon = new ImageIcon(image);
|
||||
return new JLabel(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the upper left pixel. This is the default.
|
||||
*/
|
||||
public void setOriginUpperLeft() {
|
||||
isOriginUpperLeft = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the lower left pixel.
|
||||
*/
|
||||
public void setOriginLowerLeft() {
|
||||
isOriginUpperLeft = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the picture in a window on the screen.
|
||||
*/
|
||||
public void show() {
|
||||
|
||||
// create the GUI for viewing the image if needed
|
||||
if (frame == null) {
|
||||
frame = new JFrame();
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
JMenu menu = new JMenu("File");
|
||||
menuBar.add(menu);
|
||||
JMenuItem menuItem1 = new JMenuItem(" Save... ");
|
||||
menuItem1.addActionListener(this);
|
||||
// use getMenuShortcutKeyMaskEx() in Java 10 (getMenuShortcutKeyMask() deprecated)
|
||||
menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
menu.add(menuItem1);
|
||||
frame.setJMenuBar(menuBar);
|
||||
|
||||
|
||||
|
||||
frame.setContentPane(getJLabel());
|
||||
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
if (filename == null) frame.setTitle(width + "-by-" + height);
|
||||
else frame.setTitle(filename);
|
||||
frame.setResizable(false);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
// draw
|
||||
frame.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the picture.
|
||||
*
|
||||
* @return the height of the picture (in pixels)
|
||||
*/
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the picture.
|
||||
*
|
||||
* @return the width of the picture (in pixels)
|
||||
*/
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
private void validateRowIndex(int row) {
|
||||
if (row < 0 || row >= height())
|
||||
throw new IllegalArgumentException("row index must be between 0 and " + (height() - 1) + ": " + row);
|
||||
}
|
||||
|
||||
private void validateColumnIndex(int col) {
|
||||
if (col < 0 || col >= width())
|
||||
throw new IllegalArgumentException("column index must be between 0 and " + (width() - 1) + ": " + col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public Color get(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
int rgb = getRGB(col, row);
|
||||
return new Color(rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as an {@code int}.
|
||||
* Using this method can be more efficient than {@link #get(int, int)} because
|
||||
* it does not create a {@code Color} object.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the integer representation of the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public int getRGB(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) return image.getRGB(col, row);
|
||||
else return image.getRGB(col, height - row - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param color the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
* @throws IllegalArgumentException if {@code color} is {@code null}
|
||||
*/
|
||||
public void set(int col, int row, Color color) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (color == null) throw new IllegalArgumentException("color argument is null");
|
||||
int rgb = color.getRGB();
|
||||
setRGB(col, row, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param rgb the integer representation of the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public void setRGB(int col, int row, int rgb) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) image.setRGB(col, row, rgb);
|
||||
else image.setRGB(col, height - row - 1, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this picture is equal to the argument picture.
|
||||
*
|
||||
* @param other the other picture
|
||||
* @return {@code true} if this picture is the same dimension as {@code other}
|
||||
* and if all pixels have the same color; {@code false} otherwise
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (other == null) return false;
|
||||
if (other.getClass() != this.getClass()) return false;
|
||||
Picture that = (Picture) other;
|
||||
if (this.width() != that.width()) return false;
|
||||
if (this.height() != that.height()) return false;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
if (this.getRGB(col, row) != that.getRGB(col, row)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this picture.
|
||||
* The result is a <code>width</code>-by-<code>height</code> matrix of pixels,
|
||||
* where the color of a pixel is represented using 6 hex digits to encode
|
||||
* the red, green, and blue components.
|
||||
*
|
||||
* @return a string representation of this picture
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n");
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
int rgb = 0;
|
||||
if (isOriginUpperLeft) rgb = image.getRGB(col, row);
|
||||
else rgb = image.getRGB(col, height - row - 1);
|
||||
sb.append(String.format("#%06X ", rgb & 0xFFFFFF));
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation is not supported because pictures are mutable.
|
||||
*
|
||||
* @return does not return a value
|
||||
* @throws UnsupportedOperationException if called
|
||||
*/
|
||||
public int hashCode() {
|
||||
throw new UnsupportedOperationException("hashCode() is not supported because pictures are mutable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in either PNG or JPEG format.
|
||||
* The filetype extension must be either .png or .jpg.
|
||||
*
|
||||
* @param name the name of the file
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public void save(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
save(new File(name));
|
||||
filename = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in a PNG or JPEG image format.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public void save(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
filename = file.getName();
|
||||
if (frame != null) frame.setTitle(filename);
|
||||
String suffix = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) {
|
||||
try {
|
||||
ImageIO.write(image, suffix, file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Error: filename must end in .jpg or .png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a save dialog box when the user selects "Save As" from the menu.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
FileDialog chooser = new FileDialog(frame,
|
||||
"Use a .png or .jpg extension", FileDialog.SAVE);
|
||||
chooser.setVisible(true);
|
||||
if (chooser.getFile() != null) {
|
||||
save(chooser.getDirectory() + File.separator + chooser.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests this {@code Picture} data type.
|
||||
* Reads a picture specified by the command-line argument,
|
||||
* and shows it in a window on the screen.
|
||||
*
|
||||
* @param args the command-line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Picture picture = new Picture(args[0]);
|
||||
System.out.printf("%d-by-%d\n", picture.width(), picture.height());
|
||||
picture.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pixels in the picture as a two-dimensional array.
|
||||
*
|
||||
* @return a two-dimensional array of the colors of the pixels in the picture.
|
||||
*/
|
||||
public Color[][] getPixels() {
|
||||
Color[][] pixels = new Color[height][width];
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
pixels[j][i] = get(i, j);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all pixels in the picture based on the given two-dimensional array.
|
||||
*
|
||||
* @param pixels the new colors of the pixels for the picture
|
||||
* @throws IllegalArgumentException if the dimensions of {@code pixels} do not match
|
||||
* the dimensions of the picture
|
||||
*/
|
||||
public void setPixels(Color[][] pixels) {
|
||||
if (pixels.length != height || pixels[0].length != width) {
|
||||
throw new IllegalArgumentException("Wrong dimensions of pixel array");
|
||||
}
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
set(i, j, pixels[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
public class Point {
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
public Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import java.awt.Color;
|
||||
|
||||
public class Client {
|
||||
public static void main(String[] args) {
|
||||
Picture pic = new Picture(100, 100);
|
||||
Color[][] pixels = pic.getPixels();
|
||||
// 1. Create a new picture with size 400 x 400
|
||||
// 2. Get the pixels out of the image
|
||||
|
||||
// 3. Call fill, providing a specific region
|
||||
fill(pixels,0,40,0,40);
|
||||
|
||||
// 4. Set the pixels of the image
|
||||
// 5. Save the image to display it
|
||||
pic.setPixels(pixels);
|
||||
pic.save("white.jpg");
|
||||
}
|
||||
|
||||
// TODO: Implement fill below (this solution can be iterative)
|
||||
public static void fill(Color[][] pixels, int x1, int x2, int y1, int y2) {
|
||||
for (int i = y1 + 1; i < y2 - 1; i++) {
|
||||
for (int n = x1 + 1; n < x2 - 1; n++) {
|
||||
pixels[i][n] = new Color(255,255,255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
/******************************************************************************
|
||||
* Compilation: javac Picture.java
|
||||
* Execution: java Picture imagename
|
||||
* Dependencies: none
|
||||
*
|
||||
* Data type for manipulating individual pixels of an image. The original
|
||||
* image can be read from a file in JPG, GIF, or PNG format, or the
|
||||
* user can create a blank image of a given dimension. Includes methods for
|
||||
* displaying the image in a window on the screen or saving to a file.
|
||||
*
|
||||
* % java Picture mandrill.jpg
|
||||
*
|
||||
* Remarks
|
||||
* -------
|
||||
* - pixel (x, y) is column x and row y, where (0, 0) is upper left
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides methods for manipulating individual pixels of
|
||||
* an image using the RGB color format. The alpha component (for transparency)
|
||||
* is not currently supported.
|
||||
* The original image can be read from a {@code PNG}, {@code GIF},
|
||||
* or {@code JPEG} file or the user can create a blank image of a given dimension.
|
||||
* This class includes methods for displaying the image in a window on
|
||||
* the screen or saving it to a file.
|
||||
* <p>
|
||||
* Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>.
|
||||
* By default, the origin (0, 0) is the pixel in the top-left corner,
|
||||
* which is a common convention in image processing.
|
||||
* The method {@link #setOriginLowerLeft()} change the origin to the lower left.
|
||||
* <p>
|
||||
* The {@code get()} and {@code set()} methods use {@link Color} objects to get
|
||||
* or set the color of the specified pixel.
|
||||
* The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int}
|
||||
* to encode the color, thereby avoiding the need to create temporary
|
||||
* {@code Color} objects. The red (R), green (G), and blue (B) components
|
||||
* are encoded using the least significant 24 bits.
|
||||
* Given a 32-bit {@code int} encoding the color, the following code extracts
|
||||
* the RGB components:
|
||||
* <blockquote><pre>
|
||||
* int r = (rgb >> 16) & 0xFF;
|
||||
* int g = (rgb >> 8) & 0xFF;
|
||||
* int b = (rgb >> 0) & 0xFF;
|
||||
* </pre></blockquote>
|
||||
* Given the RGB components (8-bits each) of a color,
|
||||
* the following statement packs it into a 32-bit {@code int}:
|
||||
* <blockquote><pre>
|
||||
* int rgb = (r << 16) + (g << 8) + (b << 0);
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
* A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory,
|
||||
* since the color of each pixel is encoded as a 32-bit <code>int</code>.
|
||||
* <p>
|
||||
* For additional documentation, see
|
||||
* <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of
|
||||
* <i>Computer Science: An Interdisciplinary Approach</i>
|
||||
* by Robert Sedgewick and Kevin Wayne.
|
||||
* See {@link GrayscalePicture} for a version that supports grayscale images.
|
||||
*
|
||||
* @author Robert Sedgewick
|
||||
* @author Kevin Wayne
|
||||
*/
|
||||
public final class Picture implements ActionListener {
|
||||
private BufferedImage image; // the rasterized image
|
||||
private JFrame frame; // on-screen view
|
||||
private String filename; // name of file
|
||||
private boolean isOriginUpperLeft = true; // location of origin
|
||||
private final int width, height; // width and height
|
||||
|
||||
/**
|
||||
* Creates a {@code width}-by-{@code height} picture, with {@code width} columns
|
||||
* and {@code height} rows, where each pixel is black.
|
||||
*
|
||||
* @param width the width of the picture
|
||||
* @param height the height of the picture
|
||||
* @throws IllegalArgumentException if {@code width} is negative or zero
|
||||
* @throws IllegalArgumentException if {@code height} is negative or zero
|
||||
*/
|
||||
public Picture(int width, int height) {
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
// set to TYPE_INT_ARGB here and in next constructor to support transparency
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new picture that is a deep copy of the argument picture.
|
||||
*
|
||||
* @param picture the picture to copy
|
||||
* @throws IllegalArgumentException if {@code picture} is {@code null}
|
||||
*/
|
||||
public Picture(Picture picture) {
|
||||
if (picture == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
width = picture.width();
|
||||
height = picture.height();
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
filename = picture.filename;
|
||||
isOriginUpperLeft = picture.isOriginUpperLeft;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
image.setRGB(col, row, picture.image.getRGB(col, row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading an image from a file or URL.
|
||||
*
|
||||
* @param name the name of the file (.png, .gif, or .jpg) or URL.
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public Picture(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
this.filename = name;
|
||||
try {
|
||||
// try to read from file in working directory
|
||||
File file = new File(name);
|
||||
if (file.isFile()) {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// resource relative to .class file
|
||||
URL url = getClass().getResource(filename);
|
||||
|
||||
// resource relative to classloader root
|
||||
if (url == null) {
|
||||
url = getClass().getClassLoader().getResource(name);
|
||||
}
|
||||
|
||||
// or URL from web
|
||||
if (url == null) {
|
||||
url = new URL(name);
|
||||
}
|
||||
|
||||
image = ImageIO.read(url);
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read image: " + name);
|
||||
}
|
||||
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open image: " + name, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading the image from a PNG, GIF, or JPEG file.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public Picture(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
try {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open file: " + file, ioe);
|
||||
}
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read file: " + file);
|
||||
}
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
filename = file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link JLabel} containing this picture, for embedding in a {@link JPanel},
|
||||
* {@link JFrame} or other GUI widget.
|
||||
*
|
||||
* @return the {@code JLabel}
|
||||
*/
|
||||
public JLabel getJLabel() {
|
||||
if (image == null) return null; // no image available
|
||||
ImageIcon icon = new ImageIcon(image);
|
||||
return new JLabel(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the upper left pixel. This is the default.
|
||||
*/
|
||||
public void setOriginUpperLeft() {
|
||||
isOriginUpperLeft = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the lower left pixel.
|
||||
*/
|
||||
public void setOriginLowerLeft() {
|
||||
isOriginUpperLeft = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the picture in a window on the screen.
|
||||
*/
|
||||
public void show() {
|
||||
|
||||
// create the GUI for viewing the image if needed
|
||||
if (frame == null) {
|
||||
frame = new JFrame();
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
JMenu menu = new JMenu("File");
|
||||
menuBar.add(menu);
|
||||
JMenuItem menuItem1 = new JMenuItem(" Save... ");
|
||||
menuItem1.addActionListener(this);
|
||||
// use getMenuShortcutKeyMaskEx() in Java 10 (getMenuShortcutKeyMask() deprecated)
|
||||
menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
menu.add(menuItem1);
|
||||
frame.setJMenuBar(menuBar);
|
||||
|
||||
|
||||
|
||||
frame.setContentPane(getJLabel());
|
||||
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
if (filename == null) frame.setTitle(width + "-by-" + height);
|
||||
else frame.setTitle(filename);
|
||||
frame.setResizable(false);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
// draw
|
||||
frame.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the picture.
|
||||
*
|
||||
* @return the height of the picture (in pixels)
|
||||
*/
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the picture.
|
||||
*
|
||||
* @return the width of the picture (in pixels)
|
||||
*/
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
private void validateRowIndex(int row) {
|
||||
if (row < 0 || row >= height())
|
||||
throw new IllegalArgumentException("row index must be between 0 and " + (height() - 1) + ": " + row);
|
||||
}
|
||||
|
||||
private void validateColumnIndex(int col) {
|
||||
if (col < 0 || col >= width())
|
||||
throw new IllegalArgumentException("column index must be between 0 and " + (width() - 1) + ": " + col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public Color get(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
int rgb = getRGB(col, row);
|
||||
return new Color(rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as an {@code int}.
|
||||
* Using this method can be more efficient than {@link #get(int, int)} because
|
||||
* it does not create a {@code Color} object.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the integer representation of the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public int getRGB(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) return image.getRGB(col, row);
|
||||
else return image.getRGB(col, height - row - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param color the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
* @throws IllegalArgumentException if {@code color} is {@code null}
|
||||
*/
|
||||
public void set(int col, int row, Color color) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (color == null) throw new IllegalArgumentException("color argument is null");
|
||||
int rgb = color.getRGB();
|
||||
setRGB(col, row, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param rgb the integer representation of the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public void setRGB(int col, int row, int rgb) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) image.setRGB(col, row, rgb);
|
||||
else image.setRGB(col, height - row - 1, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this picture is equal to the argument picture.
|
||||
*
|
||||
* @param other the other picture
|
||||
* @return {@code true} if this picture is the same dimension as {@code other}
|
||||
* and if all pixels have the same color; {@code false} otherwise
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (other == null) return false;
|
||||
if (other.getClass() != this.getClass()) return false;
|
||||
Picture that = (Picture) other;
|
||||
if (this.width() != that.width()) return false;
|
||||
if (this.height() != that.height()) return false;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
if (this.getRGB(col, row) != that.getRGB(col, row)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this picture.
|
||||
* The result is a <code>width</code>-by-<code>height</code> matrix of pixels,
|
||||
* where the color of a pixel is represented using 6 hex digits to encode
|
||||
* the red, green, and blue components.
|
||||
*
|
||||
* @return a string representation of this picture
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n");
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
int rgb = 0;
|
||||
if (isOriginUpperLeft) rgb = image.getRGB(col, row);
|
||||
else rgb = image.getRGB(col, height - row - 1);
|
||||
sb.append(String.format("#%06X ", rgb & 0xFFFFFF));
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation is not supported because pictures are mutable.
|
||||
*
|
||||
* @return does not return a value
|
||||
* @throws UnsupportedOperationException if called
|
||||
*/
|
||||
public int hashCode() {
|
||||
throw new UnsupportedOperationException("hashCode() is not supported because pictures are mutable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in either PNG or JPEG format.
|
||||
* The filetype extension must be either .png or .jpg.
|
||||
*
|
||||
* @param name the name of the file
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public void save(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
save(new File(name));
|
||||
filename = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in a PNG or JPEG image format.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public void save(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
filename = file.getName();
|
||||
if (frame != null) frame.setTitle(filename);
|
||||
String suffix = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) {
|
||||
try {
|
||||
ImageIO.write(image, suffix, file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Error: filename must end in .jpg or .png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a save dialog box when the user selects "Save As" from the menu.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
FileDialog chooser = new FileDialog(frame,
|
||||
"Use a .png or .jpg extension", FileDialog.SAVE);
|
||||
chooser.setVisible(true);
|
||||
if (chooser.getFile() != null) {
|
||||
save(chooser.getDirectory() + File.separator + chooser.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests this {@code Picture} data type.
|
||||
* Reads a picture specified by the command-line argument,
|
||||
* and shows it in a window on the screen.
|
||||
*
|
||||
* @param args the command-line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Picture picture = new Picture(args[0]);
|
||||
System.out.printf("%d-by-%d\n", picture.width(), picture.height());
|
||||
picture.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pixels in the picture as a two-dimensional array.
|
||||
*
|
||||
* @return a two-dimensional array of the colors of the pixels in the picture.
|
||||
*/
|
||||
public Color[][] getPixels() {
|
||||
Color[][] pixels = new Color[height][width];
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
pixels[j][i] = get(i, j);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all pixels in the picture based on the given two-dimensional array.
|
||||
*
|
||||
* @param pixels the new colors of the pixels for the picture
|
||||
* @throws IllegalArgumentException if the dimensions of {@code pixels} do not match
|
||||
* the dimensions of the picture
|
||||
*/
|
||||
public void setPixels(Color[][] pixels) {
|
||||
if (pixels.length != height || pixels[0].length != width) {
|
||||
throw new IllegalArgumentException("Wrong dimensions of pixel array");
|
||||
}
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
set(i, j, pixels[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,35 @@
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Client {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Scanner console = new Scanner(System.in);
|
||||
System.out.println("Welcome to the CSE 123 Mondrian Art Generator!");
|
||||
|
||||
int choice = 0;
|
||||
while (choice != 1 && choice != 2) {
|
||||
System.out.print("Enter 1 for a basic Mondrian or 2 for a complex Mondrian: ");
|
||||
choice = console.nextInt();
|
||||
}
|
||||
System.out.print("Enter image width (>= 300px): ");
|
||||
int width = console.nextInt();
|
||||
System.out.print("Enter image height (>= 300px): ");
|
||||
int height = console.nextInt();
|
||||
|
||||
Mondrian mond = new Mondrian();
|
||||
Picture pic = new Picture(width, height);
|
||||
Color[][] pixels = pic.getPixels();
|
||||
|
||||
if (choice == 1) {
|
||||
mond.paintBasicMondrian(pixels);
|
||||
} else { // choice == 2
|
||||
mond.paintComplexMondrian(pixels);
|
||||
}
|
||||
|
||||
pic.setPixels(pixels);
|
||||
pic.save(choice == 1 ? "basic.png" : "extension.png");
|
||||
pic.show();
|
||||
System.out.println("Enjoy your artwork!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import java.awt.*;
|
||||
import java.util.Random;
|
||||
|
||||
public class Mondrian {
|
||||
|
||||
private int canvasWidth;
|
||||
private int canvasHeight;
|
||||
private static final int MIN_REGION_SIZE = 10;
|
||||
private Random rand;
|
||||
|
||||
public void paintBasicMondrian(Color[][] pixels) {
|
||||
rand = new Random();
|
||||
int width = pixels[0].length;
|
||||
int height = pixels.length;
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
|
||||
divideCanvas(pixels, 0, width, 0, height);
|
||||
}
|
||||
|
||||
public void paintComplexMondrian(Color[][] pixels) {
|
||||
|
||||
}
|
||||
|
||||
private void divideCanvas(Color[][] pixels, int x1, int x2, int y1, int y2) {
|
||||
if (x2 - x1 >= MIN_REGION_SIZE * 3 && y2 - y1 >= MIN_REGION_SIZE * 3) {
|
||||
//int hSplit = (x2 - x1) / 2 + x1;
|
||||
//int vSplit = (y2 - y1) / 2 + y1;
|
||||
|
||||
int hSplit = rand.nextInt(x1+1, x2-10);
|
||||
int vSplit = rand.nextInt(y1+1, y2-10);
|
||||
|
||||
//if (x2 - x1 >= canvasWidth / 4) {
|
||||
//int rangeX = (x2 - x1 - MIN_REGION_SIZE * 2) / MIN_REGION_SIZE;
|
||||
// hSplit = rand.nextInt(rangeX) * MIN_REGION_SIZE + x1 + MIN_REGION_SIZE;
|
||||
// hSplit = Math.min(hSplit, x2 - MIN_REGION_SIZE); // Ensure hSplit is within canvas bounds
|
||||
// hSplit = Math.max(hSplit, x1 + MIN_REGION_SIZE); // Ensure hSplit is at least MIN_REGION_SIZE away from the canvas edge
|
||||
//}
|
||||
|
||||
//if (y2 - y1 >= canvasHeight / 4) {
|
||||
// int rangeY = (y2 - y1 - MIN_REGION_SIZE * 2) / MIN_REGION_SIZE;
|
||||
// vSplit = rand.nextInt(rangeY) * MIN_REGION_SIZE + y1 + MIN_REGION_SIZE;
|
||||
// vSplit = Math.min(vSplit, y2 - MIN_REGION_SIZE); // Ensure vSplit is within canvas bounds
|
||||
// vSplit = Math.max(vSplit, y1 + MIN_REGION_SIZE); // Ensure vSplit is at least MIN_REGION_SIZE away from the canvas edge
|
||||
//}
|
||||
|
||||
//// Additional constraints for the horizontal splitting points to prevent rectangles wider than canvasWidth/4
|
||||
//if (x2 - x1 >= canvasWidth / 4) {
|
||||
// hSplit = Math.min(hSplit, x1 + (x2 - x1) / 2); // Ensure hSplit is not farther than halfway across the canvas
|
||||
//}
|
||||
|
||||
fill(pixels, x1, x2, y1, y2);
|
||||
|
||||
if (x2 - x1 >= canvasWidth / 4 && y2 - y1 >= canvasHeight / 4) {
|
||||
divideCanvas(pixels, x1, hSplit, y1, vSplit);
|
||||
divideCanvas(pixels, hSplit, x2, y1, vSplit);
|
||||
divideCanvas(pixels, x1, hSplit, vSplit, y2);
|
||||
divideCanvas(pixels, hSplit, x2, vSplit, y2);
|
||||
} else if (x2 - x1 >= canvasWidth / 4) {
|
||||
divideCanvas(pixels, x1, hSplit, y1, y2);
|
||||
divideCanvas(pixels, hSplit, x2, y1, y2);
|
||||
} else if (y2 - y1 >= canvasHeight / 4) {
|
||||
divideCanvas(pixels, x1, x2, y1, vSplit);
|
||||
divideCanvas(pixels, x1, x2, vSplit, y2);
|
||||
}
|
||||
} else {
|
||||
fill(pixels, x1, x2, y1, y2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fill(Color[][] pixels, int x1, int x2, int y1, int y2) {
|
||||
Color region = getRandomColor();
|
||||
for (int i = y1; i < y2; i++) {
|
||||
pixels[i][x1] = Color.BLACK;
|
||||
pixels[i][x2 - 1] = Color.BLACK;
|
||||
}
|
||||
|
||||
for (int j = x1; j < x2; j++) {
|
||||
pixels[y1][j] = Color.BLACK;
|
||||
pixels[y2 - 1][j] = Color.BLACK;
|
||||
}
|
||||
|
||||
for (int i = y1 + 1; i < y2 - 1; i++) {
|
||||
for (int j = x1 + 1; j < x2 - 1; j++) {
|
||||
pixels[i][j] = region;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Color getRandomColor() {
|
||||
Color[] colors = {Color.RED, Color.YELLOW, Color.CYAN, Color.WHITE};
|
||||
return colors[rand.nextInt(4)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
/******************************************************************************
|
||||
* Compilation: javac Picture.java
|
||||
* Execution: java Picture imagename
|
||||
* Dependencies: none
|
||||
*
|
||||
* Data type for manipulating individual pixels of an image. The original
|
||||
* image can be read from a file in JPG, GIF, or PNG format, or the
|
||||
* user can create a blank image of a given dimension. Includes methods for
|
||||
* displaying the image in a window on the screen or saving to a file.
|
||||
*
|
||||
* % java Picture mandrill.jpg
|
||||
*
|
||||
* Remarks
|
||||
* -------
|
||||
* - pixel (x, y) is column x and row y, where (0, 0) is upper left
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides methods for manipulating individual pixels of
|
||||
* an image using the RGB color format. The alpha component (for transparency)
|
||||
* is not currently supported.
|
||||
* The original image can be read from a {@code PNG}, {@code GIF},
|
||||
* or {@code JPEG} file or the user can create a blank image of a given dimension.
|
||||
* This class includes methods for displaying the image in a window on
|
||||
* the screen or saving it to a file.
|
||||
* <p>
|
||||
* Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>.
|
||||
* By default, the origin (0, 0) is the pixel in the top-left corner,
|
||||
* which is a common convention in image processing.
|
||||
* The method {@link #setOriginLowerLeft()} change the origin to the lower left.
|
||||
* <p>
|
||||
* The {@code get()} and {@code set()} methods use {@link Color} objects to get
|
||||
* or set the color of the specified pixel.
|
||||
* The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int}
|
||||
* to encode the color, thereby avoiding the need to create temporary
|
||||
* {@code Color} objects. The red (R), green (G), and blue (B) components
|
||||
* are encoded using the least significant 24 bits.
|
||||
* Given a 32-bit {@code int} encoding the color, the following code extracts
|
||||
* the RGB components:
|
||||
* <blockquote><pre>
|
||||
* int r = (rgb >> 16) & 0xFF;
|
||||
* int g = (rgb >> 8) & 0xFF;
|
||||
* int b = (rgb >> 0) & 0xFF;
|
||||
* </pre></blockquote>
|
||||
* Given the RGB components (8-bits each) of a color,
|
||||
* the following statement packs it into a 32-bit {@code int}:
|
||||
* <blockquote><pre>
|
||||
* int rgb = (r << 16) + (g << 8) + (b << 0);
|
||||
* </pre></blockquote>
|
||||
* <p>
|
||||
* A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory,
|
||||
* since the color of each pixel is encoded as a 32-bit <code>int</code>.
|
||||
* <p>
|
||||
* For additional documentation, see
|
||||
* <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of
|
||||
* <i>Computer Science: An Interdisciplinary Approach</i>
|
||||
* by Robert Sedgewick and Kevin Wayne.
|
||||
* See {@link GrayscalePicture} for a version that supports grayscale images.
|
||||
*
|
||||
* @author Robert Sedgewick
|
||||
* @author Kevin Wayne
|
||||
*/
|
||||
public final class Picture implements ActionListener {
|
||||
private BufferedImage image; // the rasterized image
|
||||
private JFrame frame; // on-screen view
|
||||
private String filename; // name of file
|
||||
private boolean isOriginUpperLeft = true; // location of origin
|
||||
private final int width, height; // width and height
|
||||
|
||||
/**
|
||||
* Creates a {@code width}-by-{@code height} picture, with {@code width} columns
|
||||
* and {@code height} rows, where each pixel is black.
|
||||
*
|
||||
* @param width the width of the picture
|
||||
* @param height the height of the picture
|
||||
* @throws IllegalArgumentException if {@code width} is negative or zero
|
||||
* @throws IllegalArgumentException if {@code height} is negative or zero
|
||||
*/
|
||||
public Picture(int width, int height) {
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
// set to TYPE_INT_ARGB here and in next constructor to support transparency
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new picture that is a deep copy of the argument picture.
|
||||
*
|
||||
* @param picture the picture to copy
|
||||
* @throws IllegalArgumentException if {@code picture} is {@code null}
|
||||
*/
|
||||
public Picture(Picture picture) {
|
||||
if (picture == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
width = picture.width();
|
||||
height = picture.height();
|
||||
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
filename = picture.filename;
|
||||
isOriginUpperLeft = picture.isOriginUpperLeft;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
image.setRGB(col, row, picture.image.getRGB(col, row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading an image from a file or URL.
|
||||
*
|
||||
* @param name the name of the file (.png, .gif, or .jpg) or URL.
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public Picture(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
this.filename = name;
|
||||
try {
|
||||
// try to read from file in working directory
|
||||
File file = new File(name);
|
||||
if (file.isFile()) {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
// resource relative to .class file
|
||||
URL url = getClass().getResource(filename);
|
||||
|
||||
// resource relative to classloader root
|
||||
if (url == null) {
|
||||
url = getClass().getClassLoader().getResource(name);
|
||||
}
|
||||
|
||||
// or URL from web
|
||||
if (url == null) {
|
||||
url = new URL(name);
|
||||
}
|
||||
|
||||
image = ImageIO.read(url);
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read image: " + name);
|
||||
}
|
||||
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open image: " + name, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a picture by reading the image from a PNG, GIF, or JPEG file.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if cannot read image
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public Picture(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("constructor argument is null");
|
||||
|
||||
try {
|
||||
image = ImageIO.read(file);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
throw new IllegalArgumentException("could not open file: " + file, ioe);
|
||||
}
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("could not read file: " + file);
|
||||
}
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
filename = file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link JLabel} containing this picture, for embedding in a {@link JPanel},
|
||||
* {@link JFrame} or other GUI widget.
|
||||
*
|
||||
* @return the {@code JLabel}
|
||||
*/
|
||||
public JLabel getJLabel() {
|
||||
if (image == null) return null; // no image available
|
||||
ImageIcon icon = new ImageIcon(image);
|
||||
return new JLabel(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the upper left pixel. This is the default.
|
||||
*/
|
||||
public void setOriginUpperLeft() {
|
||||
isOriginUpperLeft = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the origin to be the lower left pixel.
|
||||
*/
|
||||
public void setOriginLowerLeft() {
|
||||
isOriginUpperLeft = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the picture in a window on the screen.
|
||||
*/
|
||||
public void show() {
|
||||
|
||||
// create the GUI for viewing the image if needed
|
||||
if (frame == null) {
|
||||
frame = new JFrame();
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
JMenu menu = new JMenu("File");
|
||||
menuBar.add(menu);
|
||||
JMenuItem menuItem1 = new JMenuItem(" Save... ");
|
||||
menuItem1.addActionListener(this);
|
||||
// use getMenuShortcutKeyMaskEx() in Java 10 (getMenuShortcutKeyMask() deprecated)
|
||||
menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
menu.add(menuItem1);
|
||||
frame.setJMenuBar(menuBar);
|
||||
|
||||
|
||||
|
||||
frame.setContentPane(getJLabel());
|
||||
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
if (filename == null) frame.setTitle(width + "-by-" + height);
|
||||
else frame.setTitle(filename);
|
||||
frame.setResizable(false);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
// draw
|
||||
frame.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the picture.
|
||||
*
|
||||
* @return the height of the picture (in pixels)
|
||||
*/
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the picture.
|
||||
*
|
||||
* @return the width of the picture (in pixels)
|
||||
*/
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
private void validateRowIndex(int row) {
|
||||
if (row < 0 || row >= height())
|
||||
throw new IllegalArgumentException("row index must be between 0 and " + (height() - 1) + ": " + row);
|
||||
}
|
||||
|
||||
private void validateColumnIndex(int col) {
|
||||
if (col < 0 || col >= width())
|
||||
throw new IllegalArgumentException("column index must be between 0 and " + (width() - 1) + ": " + col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public Color get(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
int rgb = getRGB(col, row);
|
||||
return new Color(rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of pixel ({@code col}, {@code row}) as an {@code int}.
|
||||
* Using this method can be more efficient than {@link #get(int, int)} because
|
||||
* it does not create a {@code Color} object.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @return the integer representation of the color of pixel ({@code col}, {@code row})
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public int getRGB(int col, int row) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) return image.getRGB(col, row);
|
||||
else return image.getRGB(col, height - row - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param color the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
* @throws IllegalArgumentException if {@code color} is {@code null}
|
||||
*/
|
||||
public void set(int col, int row, Color color) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (color == null) throw new IllegalArgumentException("color argument is null");
|
||||
int rgb = color.getRGB();
|
||||
setRGB(col, row, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of pixel ({@code col}, {@code row}) to given color.
|
||||
*
|
||||
* @param col the column index
|
||||
* @param row the row index
|
||||
* @param rgb the integer representation of the color
|
||||
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
|
||||
*/
|
||||
public void setRGB(int col, int row, int rgb) {
|
||||
validateColumnIndex(col);
|
||||
validateRowIndex(row);
|
||||
if (isOriginUpperLeft) image.setRGB(col, row, rgb);
|
||||
else image.setRGB(col, height - row - 1, rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this picture is equal to the argument picture.
|
||||
*
|
||||
* @param other the other picture
|
||||
* @return {@code true} if this picture is the same dimension as {@code other}
|
||||
* and if all pixels have the same color; {@code false} otherwise
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) return true;
|
||||
if (other == null) return false;
|
||||
if (other.getClass() != this.getClass()) return false;
|
||||
Picture that = (Picture) other;
|
||||
if (this.width() != that.width()) return false;
|
||||
if (this.height() != that.height()) return false;
|
||||
for (int col = 0; col < width(); col++)
|
||||
for (int row = 0; row < height(); row++)
|
||||
if (this.getRGB(col, row) != that.getRGB(col, row)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this picture.
|
||||
* The result is a <code>width</code>-by-<code>height</code> matrix of pixels,
|
||||
* where the color of a pixel is represented using 6 hex digits to encode
|
||||
* the red, green, and blue components.
|
||||
*
|
||||
* @return a string representation of this picture
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n");
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
int rgb = 0;
|
||||
if (isOriginUpperLeft) rgb = image.getRGB(col, row);
|
||||
else rgb = image.getRGB(col, height - row - 1);
|
||||
sb.append(String.format("#%06X ", rgb & 0xFFFFFF));
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation is not supported because pictures are mutable.
|
||||
*
|
||||
* @return does not return a value
|
||||
* @throws UnsupportedOperationException if called
|
||||
*/
|
||||
public int hashCode() {
|
||||
throw new UnsupportedOperationException("hashCode() is not supported because pictures are mutable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in either PNG or JPEG format.
|
||||
* The filetype extension must be either .png or .jpg.
|
||||
*
|
||||
* @param name the name of the file
|
||||
* @throws IllegalArgumentException if {@code name} is {@code null}
|
||||
*/
|
||||
public void save(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
save(new File(name));
|
||||
filename = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the picture to a file in a PNG or JPEG image format.
|
||||
*
|
||||
* @param file the file
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}
|
||||
*/
|
||||
public void save(File file) {
|
||||
if (file == null) throw new IllegalArgumentException("argument to save() is null");
|
||||
filename = file.getName();
|
||||
if (frame != null) frame.setTitle(filename);
|
||||
String suffix = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) {
|
||||
try {
|
||||
ImageIO.write(image, suffix, file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Error: filename must end in .jpg or .png");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a save dialog box when the user selects "Save As" from the menu.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
FileDialog chooser = new FileDialog(frame,
|
||||
"Use a .png or .jpg extension", FileDialog.SAVE);
|
||||
chooser.setVisible(true);
|
||||
if (chooser.getFile() != null) {
|
||||
save(chooser.getDirectory() + File.separator + chooser.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests this {@code Picture} data type.
|
||||
* Reads a picture specified by the command-line argument,
|
||||
* and shows it in a window on the screen.
|
||||
*
|
||||
* @param args the command-line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Picture picture = new Picture(args[0]);
|
||||
System.out.printf("%d-by-%d\n", picture.width(), picture.height());
|
||||
picture.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pixels in the picture as a two-dimensional array.
|
||||
*
|
||||
* @return a two-dimensional array of the colors of the pixels in the picture.
|
||||
*/
|
||||
public Color[][] getPixels() {
|
||||
Color[][] pixels = new Color[height][width];
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
pixels[j][i] = get(i, j);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all pixels in the picture based on the given two-dimensional array.
|
||||
*
|
||||
* @param pixels the new colors of the pixels for the picture
|
||||
* @throws IllegalArgumentException if the dimensions of {@code pixels} do not match
|
||||
* the dimensions of the picture
|
||||
*/
|
||||
public void setPixels(Color[][] pixels) {
|
||||
if (pixels.length != height || pixels[0].length != width) {
|
||||
throw new IllegalArgumentException("Wrong dimensions of pixel array");
|
||||
}
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
set(i, j, pixels[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
javac *.java && java Client && rm *.class
|
||||
@@ -0,0 +1,91 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Client {
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
Scanner console = new Scanner(System.in);
|
||||
|
||||
System.out.print("Enter quiz file to read: ");
|
||||
String inFileName = console.nextLine();
|
||||
File inFile = new File(inFileName);
|
||||
while (!inFile.exists()) {
|
||||
System.out.println(" File does not exist. Please try again.");
|
||||
System.out.print("Enter quiz file to read: ");
|
||||
inFileName = console.nextLine();
|
||||
inFile = new File(inFileName);
|
||||
}
|
||||
|
||||
QuizTree quiz = new QuizTree(new Scanner(inFile));
|
||||
System.out.println("Quiz created!");
|
||||
System.out.println();
|
||||
|
||||
String option = "";
|
||||
while (!option.equalsIgnoreCase("quit")) {
|
||||
option = menu(console);
|
||||
System.out.println();
|
||||
|
||||
if (option.equalsIgnoreCase("take")) {
|
||||
quiz.takeQuiz(console);
|
||||
System.out.println();
|
||||
} else if (option.equalsIgnoreCase("creative")) {
|
||||
// quiz.creativeExtension(); // TODO: Update with any parameters you need!
|
||||
System.out.println();
|
||||
} else if (option.equalsIgnoreCase("export")) {
|
||||
System.out.print("Enter file to export to: ");
|
||||
String outFileName = console.nextLine();
|
||||
PrintStream outFile = new PrintStream(new File(outFileName));
|
||||
quiz.export(outFile);
|
||||
System.out.println("Quiz exported!");
|
||||
System.out.println();
|
||||
} else if (option.equalsIgnoreCase("add")) {
|
||||
addQ(console, quiz);
|
||||
System.out.println();
|
||||
} else if (!option.equalsIgnoreCase("quit")) {
|
||||
System.out.println(" Invalid choice. Please try again.");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String menu(Scanner console) {
|
||||
System.out.println("What would you like to do? Choose an option in brackets.");
|
||||
System.out.println(" [take] quiz");
|
||||
System.out.println(" [add] question");
|
||||
System.out.println(" [export] quiz");
|
||||
System.out.println(" [creative] extension");
|
||||
System.out.println(" [quit] program");
|
||||
return console.nextLine();
|
||||
}
|
||||
|
||||
private static void addQ(Scanner console, QuizTree quiz) {
|
||||
System.out.print("Enter result to replace: ");
|
||||
String toReplace = console.nextLine();
|
||||
|
||||
System.out.print("Enter left choice: ");
|
||||
String leftChoice = console.nextLine();
|
||||
|
||||
System.out.print("Enter right choice: ");
|
||||
String rightChoice = console.nextLine();
|
||||
|
||||
System.out.print("Enter score of choices: ");
|
||||
String choiceScore = console.nextLine();
|
||||
|
||||
System.out.print("Enter left result: ");
|
||||
String leftResult = console.nextLine();
|
||||
|
||||
System.out.print("Enter left score: ");
|
||||
int leftScore = Integer.parseInt(console.nextLine());
|
||||
|
||||
System.out.print("Enter right result: ");
|
||||
String rightResult = console.nextLine();
|
||||
|
||||
System.out.print("Enter right score: ");
|
||||
int rightScore = Integer.parseInt(console.nextLine());
|
||||
|
||||
String choices = leftChoice + "/" + rightChoice + "-" + choiceScore;
|
||||
leftResult = leftResult + "-" + leftScore;
|
||||
rightResult = rightResult + "-" + rightScore;
|
||||
quiz.addQuestion(toReplace, choices, leftResult, rightResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class QuizTree {
|
||||
|
||||
private QuizTreeNode head;
|
||||
|
||||
// PROVIDED
|
||||
// Returns the given percent rounded to two decimal places.
|
||||
private double roundTwoPlaces(double percent) {
|
||||
return (double) Math.round(percent * 100) / 100;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size(head);
|
||||
}
|
||||
|
||||
private int size(QuizTreeNode root) {
|
||||
if (root == null) {
|
||||
return 0;
|
||||
}
|
||||
return 1 + size(root.left) + size(root.right);
|
||||
}
|
||||
|
||||
public QuizTree(Scanner inputFile) {
|
||||
this.head = null;
|
||||
this.head = populateTree(inputFile, head);
|
||||
}
|
||||
|
||||
private void printTree(QuizTreeNode root) {
|
||||
if (root != null) {
|
||||
System.out.println(root.data);
|
||||
printTree(root.left);
|
||||
printTree(root.right);
|
||||
}
|
||||
}
|
||||
|
||||
private QuizTreeNode populateTree(Scanner inputFile, QuizTreeNode root) {
|
||||
if (inputFile.hasNextLine()) {
|
||||
String next = inputFile.nextLine();
|
||||
String data;
|
||||
int score;
|
||||
|
||||
// we've reached a result, make a node but dont recurse
|
||||
if (next.startsWith("END:")) {
|
||||
data = next.substring(4, next.lastIndexOf("-"));
|
||||
score = Integer.parseInt(next.substring(next.lastIndexOf("-") + 1));
|
||||
return new QuizTreeNode(data, score);
|
||||
}
|
||||
|
||||
// otherwise, we're not at the end yet, make a node and keep going
|
||||
else {
|
||||
data = next.substring(0, next.lastIndexOf("-"));
|
||||
score = Integer.parseInt(next.substring(next.lastIndexOf("-") + 1));
|
||||
root = new QuizTreeNode(data, score);
|
||||
root.left = populateTree(inputFile, root.left);
|
||||
root.right = populateTree(inputFile, root.right);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
public void takeQuiz(Scanner console) {
|
||||
takeQuiz(console, head);
|
||||
}
|
||||
|
||||
private void takeQuiz(Scanner console, QuizTreeNode root) {
|
||||
if (root.left != null && root.right != null) {
|
||||
String left = root.data.substring(0, root.data.lastIndexOf("/"));
|
||||
String right = root.data.substring(root.data.lastIndexOf("/")+1, root.data.length());
|
||||
System.out.print("Do you prefer " + left + " or " + right + "? ");
|
||||
String input = console.nextLine();
|
||||
|
||||
while (!input.equalsIgnoreCase(left) || !input.equalsIgnoreCase(right)) {
|
||||
System.out.println(" Invalid response; try again.");
|
||||
System.out.print("Do you prefer " + left + " or " + right + "? ");
|
||||
input = console.nextLine();
|
||||
}
|
||||
|
||||
takeQuiz(console, input.equalsIgnoreCase(left) ? root.left : root.right);
|
||||
}
|
||||
|
||||
else {
|
||||
System.out.println("Your result is: " + root.data);
|
||||
System.out.println("Your score is: " + root.score);
|
||||
}
|
||||
}
|
||||
|
||||
public void export(PrintStream outputFile) {
|
||||
export(outputFile, this.head);
|
||||
}
|
||||
|
||||
private void export(PrintStream outputFile, QuizTreeNode root) {
|
||||
if (root.left == null && root.right == null) {
|
||||
outputFile.println("END:" + root.data + "-" + root.score);
|
||||
} else {
|
||||
outputFile.println(root.data + "-" + root.score);
|
||||
}
|
||||
|
||||
if (root.left != null) {
|
||||
export(outputFile, root.left);
|
||||
}
|
||||
if (root.right != null) {
|
||||
export(outputFile, root.right);
|
||||
}
|
||||
}
|
||||
|
||||
//public void addQuestion(String toReplace, String choices, String leftResult, String rightResult) {
|
||||
//}
|
||||
|
||||
public void addQuestion(String toReplace, String choices, String leftResult, String rightResult) {
|
||||
head = addQuestion(head, toReplace.toLowerCase(), choices, leftResult, rightResult);
|
||||
}
|
||||
|
||||
private QuizTreeNode addQuestion(QuizTreeNode root, String toReplace, String choices, String leftResult, String rightResult) {
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (root.left == null && root.right == null && root.data.equalsIgnoreCase(toReplace)) {
|
||||
// Parse choices to get left choice, right choice, and score
|
||||
String choiceText = choices.substring(0, choices.lastIndexOf("-"));
|
||||
int choiceScore = Integer.parseInt(choices.substring(choices.lastIndexOf("-") + 1));
|
||||
|
||||
// Create the new choice node
|
||||
QuizTreeNode choiceNode = new QuizTreeNode(choiceText, choiceScore);
|
||||
|
||||
// Parse leftResult to get left result data and score
|
||||
String leftData = leftResult.substring(0, leftResult.lastIndexOf("-"));
|
||||
int leftScore = Integer.parseInt(leftResult.substring(leftResult.lastIndexOf("-") + 1));
|
||||
QuizTreeNode leftNode = new QuizTreeNode(leftData, leftScore);
|
||||
|
||||
// Parse rightResult to get right result data and score
|
||||
String rightData = rightResult.substring(0, rightResult.lastIndexOf("-"));
|
||||
int rightScore = Integer.parseInt(rightResult.substring(rightResult.lastIndexOf("-") + 1));
|
||||
QuizTreeNode rightNode = new QuizTreeNode(rightData, rightScore);
|
||||
|
||||
// Set the left and right children of the new choice node
|
||||
choiceNode.left = leftNode;
|
||||
choiceNode.right = rightNode;
|
||||
|
||||
return choiceNode;
|
||||
} else {
|
||||
root.left = addQuestion(root.left, toReplace, choices, leftResult, rightResult);
|
||||
root.right = addQuestion(root.right, toReplace, choices, leftResult, rightResult);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class QuizTreeNode {
|
||||
|
||||
public final String data;
|
||||
public final int score;
|
||||
public QuizTreeNode left;
|
||||
public QuizTreeNode right;
|
||||
|
||||
public QuizTreeNode(String data, int score) {
|
||||
this(data, score, null, null);
|
||||
}
|
||||
|
||||
public QuizTreeNode(String data, int score, QuizTreeNode left, QuizTreeNode right) {
|
||||
this.data = data;
|
||||
this.score = score;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
red/blue-0
|
||||
yellow/green-1
|
||||
END:Froot Loops-3
|
||||
END:Raisin Bran-5
|
||||
purple/orange-2
|
||||
END:Frosted Flakes-1
|
||||
black/white-3
|
||||
END:Rice Krispies-2
|
||||
END:Fruity Pebbles-4
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
javac *.java && java Client ; rm *.class
|
||||
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
// Represents a classical cipher that is able to encode a plaintext into a ciphertext, and
|
||||
// decode a ciphertext into a plaintext. Also capable of encoding and decoding entire files
|
||||
|
||||
public abstract class Cipher {
|
||||
// The minimum character able to be encoded by any cipher
|
||||
public static final int MIN_CHAR = (int)(' ');
|
||||
|
||||
// The maximum character able to be encoded by any cipher
|
||||
public static final int MAX_CHAR = (int)('}');
|
||||
|
||||
// The total number of characters able to be encoded by any cipher (aka. the encodable range)
|
||||
public static final int TOTAL_CHARS = MAX_CHAR - MIN_CHAR + 1;
|
||||
|
||||
// Pre: Throws a FileNotFoundException if a file with the provided 'fileName' doesn't exist
|
||||
// Post: Applies this Cipher's encryption scheme to the file with the given 'fileName', creating
|
||||
// a new file to store the results.
|
||||
public void encryptFile(String fileName) throws FileNotFoundException {
|
||||
fileHelper(fileName, true, "-encoded");
|
||||
}
|
||||
|
||||
// Pre: Throws a FileNotFoundException if a file with the provided 'fileName' doesn't exist
|
||||
// Post: Applies this Cipher's decryption scheme (reversing a single round of encryption if applied)
|
||||
// to the file with the given 'fileName', creating a new file to store the results.
|
||||
public void decryptFile(String fileName) throws FileNotFoundException {
|
||||
fileHelper(fileName, false, "-decoded");
|
||||
}
|
||||
|
||||
// Pre: Throws a FileNotFoundException if a file with the provided 'fileName' doesn't exist
|
||||
// Post: Reads from an input file with 'fileName', either encrypting or decrypting depending on 'encode',
|
||||
// printing the results to a new file with 'suffix' appended to the input file's name
|
||||
private void fileHelper(String fileName, boolean encode, String suffix) throws FileNotFoundException{
|
||||
Scanner sc = new Scanner(new File(fileName));
|
||||
String out = fileName.split("\\.txt")[0] + suffix + ".txt";
|
||||
PrintStream ps = new PrintStream(out);
|
||||
while(sc.hasNextLine()) {
|
||||
String line = sc.nextLine();
|
||||
ps.println(encode ? encrypt(line) : decrypt(line));
|
||||
}
|
||||
}
|
||||
|
||||
// Post: Returns the result of applying this Cipher's encryption scheme to 'input'
|
||||
public abstract String encrypt(String input);
|
||||
|
||||
// Post: Returns the result of applying this Cipher's decryption scheme to 'input'
|
||||
public abstract String decrypt(String input);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Client {
|
||||
// TODO: Change this line once you've implemented a cipher!
|
||||
public static final Cipher CHOSEN_CIPHER = null;
|
||||
|
||||
// (we also encourage you to change Cipher.MIN_CHAR and Cipher.MAX_CHAR when testing!)
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
Scanner console = new Scanner(System.in);
|
||||
System.out.println("Welcome to the CSE 123 cryptography application!");
|
||||
System.out.println("What would you like to do?");
|
||||
int chosen = -1;
|
||||
do {
|
||||
System.out.println();
|
||||
System.out.println("(1) Encode / (2) Decode a string");
|
||||
System.out.println("(3) Encode / (4) Decode a file");
|
||||
System.out.println("(5) Quit");
|
||||
System.out.print("Enter your choice here: ");
|
||||
|
||||
chosen = Integer.parseInt(console.nextLine());
|
||||
while (chosen < 1 || chosen > 5) {
|
||||
System.out.print("Please enter a valid option from above: ");
|
||||
chosen = Integer.parseInt(console.nextLine());
|
||||
}
|
||||
|
||||
if (chosen == 1 || chosen == 2) {
|
||||
System.out.println("Please enter the string you'd like to " +
|
||||
(chosen == 1 ? "encode" : "decode") + ": ");
|
||||
String input = console.nextLine();
|
||||
System.out.println(chosen == 1 ? CHOSEN_CIPHER.encrypt(input) :
|
||||
CHOSEN_CIPHER.decrypt(input));
|
||||
} else if (chosen == 3 || chosen == 4) {
|
||||
System.out.print("Please enter the name of the file you'd like to " +
|
||||
(chosen == 3 ? "encode" : "decode") + ": ");
|
||||
String fileName = console.nextLine();
|
||||
if (chosen == 3) {
|
||||
CHOSEN_CIPHER.encryptFile(fileName);
|
||||
} else {
|
||||
CHOSEN_CIPHER.decryptFile(fileName);
|
||||
}
|
||||
}
|
||||
} while (chosen != 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// TODO: Write your implementation to SubstitutionRandom here!
|
||||
import java.util.*;
|
||||
|
||||
public class SubstitutionRandom extends Substitution {
|
||||
public static void main(String[] args) {
|
||||
Random balls = new Random();
|
||||
int boner = balls.nextInt(4);
|
||||
System.out.println(boner);
|
||||
Collections.shuffle()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import org.junit.jupiter.api.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import java.util.*;
|
||||
|
||||
public class Testing {
|
||||
|
||||
@Test
|
||||
@DisplayName("TODO: 1 Your Extension - ' '-'}' Shifter")
|
||||
public void thirdCaseTest() {
|
||||
assertTrue(false, "Not yet implemented!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("TODO: 1 Your Extension - ' '-'}' Shifter")
|
||||
public void thirdCaseTest() {
|
||||
assertTrue(false, "Not yet implemented!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// TODO: Write your implementation to Transposition here!
|
||||
@@ -0,0 +1 @@
|
||||
// TODO: Write your implementation to Vigenere here!
|
||||
@@ -0,0 +1 @@
|
||||
HADBADCABBAGEBAG
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
Hi everyone,
|
||||
|
||||
We're using Ed Discussion for class Q&A.
|
||||
This is the best place to ask questions about the course, whether curricular or administrative. You will get faster answers here from staff and peers than through email.
|
||||
|
||||
Here are some tips:
|
||||
Search before you post
|
||||
Heart questions and answers you find useful
|
||||
Answer questions you feel confident answering
|
||||
Share interesting course related content with staff and peers
|
||||
|
||||
For more information on Ed Discussion, you can refer to the Quick Start Guide.
|
||||
|
||||
All the best this semester!
|
||||
Brett
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
import java.util.*;
|
||||
|
||||
public class Book implements Media {
|
||||
|
||||
private String title;
|
||||
private String author;
|
||||
private List<String> authors;
|
||||
private List<Integer> ratings;
|
||||
|
||||
public Book(String title, String author) {
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public Book(String title, List<String> authors) {
|
||||
this.title = title;
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public List<String> getArtists() {
|
||||
List<String> artists = new ArrayList<>();
|
||||
if (this.author != null) {
|
||||
artists.add(this.author);
|
||||
}
|
||||
|
||||
if (this.authors != null) {
|
||||
for (String author : authors) {
|
||||
artists.add(author);
|
||||
}
|
||||
}
|
||||
return artists;
|
||||
}
|
||||
|
||||
public void addRating(int score) {
|
||||
if (this.ratings == null) {
|
||||
ratings = new ArrayList<>();
|
||||
}
|
||||
this.ratings.add(score);
|
||||
}
|
||||
|
||||
public int getNumRatings() {
|
||||
|
||||
if (this.ratings == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.ratings.size();
|
||||
}
|
||||
|
||||
public double getAverageRating() {
|
||||
if (this.ratings == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
for (int rating : ratings) {
|
||||
sum += rating;
|
||||
}
|
||||
|
||||
return (double)sum / this.ratings.size();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.title + " by " + this.getArtists() + ": " + this.getAverageRating() +
|
||||
(this.ratings.size()) + " ratings";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import java.util.*;
|
||||
|
||||
public class Debugging {
|
||||
public static void main(String[] args) {
|
||||
Map<String, Set<Integer>> testMap = new TreeMap<>();
|
||||
Set<Integer> c121 = arrToSet(new int[]{42, 17, 42, 42});
|
||||
Set<Integer> c122 = arrToSet(new int[]{10, 12, 14});
|
||||
Set<Integer> c123 = arrToSet(new int[]{100, 99, 98, -97});
|
||||
testMap.put("cse121", c121);
|
||||
testMap.put("cse122", c122);
|
||||
testMap.put("cse123", c123);
|
||||
|
||||
Map<String, Set<Integer>> deepCopyMap = deepCopy(testMap);
|
||||
|
||||
if (deepCopyMap.isEmpty()) {
|
||||
System.out.println("{}");
|
||||
} else {
|
||||
String line = "";
|
||||
for (String key : deepCopyMap.keySet()) {
|
||||
line += key + "=" + deepCopyMap.get(key).toString() + ", ";
|
||||
}
|
||||
System.out.println("{" + line.substring(0, line.length() - 2) + "}");
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Integer> arrToSet(int[] arr) {
|
||||
Set<Integer> s = new TreeSet<>();
|
||||
for (int num : arr) {
|
||||
s.add(num);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Produces and returns a "deep copy" of the parameter map, which has the same
|
||||
// structure and values as the parameter, but with all internal data structures
|
||||
// and values copied. After calling this method, modifying the parameter or
|
||||
// return value should NOT affect the other.
|
||||
//
|
||||
// Parameters:
|
||||
// inputMap - the map to duplicate
|
||||
//
|
||||
// Returns:
|
||||
// A deep copy of the parameter map.
|
||||
public static Map<String, Set<Integer>> deepCopy(Map<String, Set<Integer>> inputMap) {
|
||||
Map<String, Set<Integer>> deepCopy = new TreeMap<>();
|
||||
|
||||
for (String key : inputMap.keySet()) {
|
||||
Set<Integer> inputSet = new TreeSet<>(inputMap.get(key));
|
||||
// Set<Integer> inputSet = inputMap.get(key);
|
||||
deepCopy.put(key, inputSet);
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import java.util.*;
|
||||
|
||||
public class InvertedIndex {
|
||||
public static void main(String[] args) {
|
||||
List<String> docs = new ArrayList<>();
|
||||
docs.add("Raiders of the Lost Ark");
|
||||
docs.add("The Temple of Doom");
|
||||
docs.add("The Last Crusade");
|
||||
|
||||
Map<String, Set<String>> result = createIndex(docs);
|
||||
System.out.println(docs);
|
||||
System.out.println();
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
// TODO: Write and document your createIndex method here
|
||||
|
||||
public static Map<String, Set<String>> createIndex(List<String> docs) {
|
||||
Map<String, Set<String>> index = new TreeMap<>();
|
||||
Set<String> uniqueWords = getUniqueWords(docs);
|
||||
|
||||
for (String uniqueWord : uniqueWords) {
|
||||
index.put(uniqueWord.toLowerCase(), new HashSet<String>());
|
||||
}
|
||||
|
||||
for (String word : index.keySet()) {
|
||||
for (int i = 0; i < docs.size(); i++) {
|
||||
Scanner wordScanner = new Scanner(docs.get(i));
|
||||
while (wordScanner.hasNext()) {
|
||||
if (wordScanner.next().equalsIgnoreCase(word)) {
|
||||
index.get(word).add(docs.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
public static Set<String> getUniqueWords(List<String> docs) {
|
||||
Set<String> uniqueWords = new HashSet<>();
|
||||
for (String title : docs) {
|
||||
Scanner titleScanner = new Scanner(title);
|
||||
while (titleScanner.hasNext()) {
|
||||
uniqueWords.add(titleScanner.next());
|
||||
}
|
||||
titleScanner.close();
|
||||
}
|
||||
return uniqueWords;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An interface to represent various types of media (movies, books, tv shows, songs, etc.).
|
||||
*/
|
||||
public interface Media {
|
||||
/**
|
||||
* Gets the title of this media.
|
||||
*
|
||||
* @return The title of this media.
|
||||
*/
|
||||
public String getTitle();
|
||||
|
||||
/**
|
||||
* Gets all artists associated with this media.
|
||||
*
|
||||
* @return A list of artists for this media.
|
||||
*/
|
||||
public List<String> getArtists();
|
||||
|
||||
/**
|
||||
* Adds a rating to this media.
|
||||
*
|
||||
* @param score The score for the new rating. Should be non-negative.
|
||||
*/
|
||||
public void addRating(int score);
|
||||
|
||||
/**
|
||||
* Gets the number of times this media has been rated.
|
||||
*
|
||||
* @return The number of ratings for this media.
|
||||
*/
|
||||
public int getNumRatings();
|
||||
|
||||
/**
|
||||
* Gets the average (mean) of all ratings for this media.
|
||||
*
|
||||
* @return The average (mean) of all ratings for this media. If no ratings exist, returns 0.
|
||||
*/
|
||||
public double getAverageRating();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import org.junit.jupiter.api.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Testing {
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST CASE - createIndex Example")
|
||||
public void firstCaseTest() {
|
||||
List<String> documents = new ArrayList<>(List.of("The Bee Movie is great!",
|
||||
"I love the Bee Movie",
|
||||
"Y'all seen Dune 2?"));
|
||||
Map<String, Set<String>> index = InvertedIndex.createIndex(documents);
|
||||
|
||||
// Make sure that tokens are correctly converted to lower case
|
||||
assertTrue(index.containsKey("bee"));
|
||||
assertFalse(index.containsKey("Bee"));
|
||||
|
||||
// Make sure that punctuation is ignored
|
||||
assertTrue(index.containsKey("great!"));
|
||||
assertFalse(index.containsKey("great"));
|
||||
|
||||
// Check one of the sets
|
||||
assertEquals(Set.of("The Bee Movie is great!",
|
||||
"I love the Bee Movie"),
|
||||
index.get("movie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST CASE - Book 2 String constructor + getters")
|
||||
public void secondCaseTest() {
|
||||
Book b = new Book("Title", "Author");
|
||||
|
||||
// Test all getters after constructing with 2 Strings
|
||||
assertEquals("Title", b.getTitle());
|
||||
assertEquals(List.of("Author"), b.getArtists());
|
||||
assertEquals(0, b.getNumRatings());
|
||||
assertEquals(0.0, b.getAverageRating());
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Scrabble {
|
||||
public static void main(String[] args) {
|
||||
Set<String> dictionary = loadDictionary();
|
||||
List<Character> letters = readLetters();
|
||||
|
||||
System.out.println("Searching " + letters);
|
||||
findWords(letters, dictionary);
|
||||
}
|
||||
|
||||
public static void findWords(List<Character> letters, Set<String> dict) {
|
||||
findWords(letters, dict, "");
|
||||
}
|
||||
|
||||
private static void findWords(List<Character> letters, Set<String> dict, String soFar) {
|
||||
if (dict.contains(soFar)) {
|
||||
System.out.println(soFar);
|
||||
}
|
||||
if (!letters.isEmpty()) {
|
||||
for (int i = 0; i < letters.size(); i++) {
|
||||
char letter = letters.remove(i);
|
||||
findWords(letters, dict, soFar+letter);
|
||||
letters.add(i, letter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER METHODS
|
||||
|
||||
// does not check if word is legal
|
||||
private static int scoreWord(String word) {
|
||||
// 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
|
||||
int[] values = {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};
|
||||
|
||||
int score = 0;
|
||||
word = word.toUpperCase();
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
char ch = word.charAt(i);
|
||||
score += values[ch - 'A'];
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static List<Character> readLetters() {
|
||||
List<Character> result = new ArrayList<>();
|
||||
|
||||
Scanner input = new Scanner(System.in);
|
||||
System.out.print("Enter letters separated by whitespace: ");
|
||||
Scanner letters = new Scanner(input.nextLine());
|
||||
while (letters.hasNext()) {
|
||||
String letter = letters.next();
|
||||
if (letter.length() > 1) {
|
||||
System.out.println(" Skipping " + letter + " - not one letter");
|
||||
} else {
|
||||
result.add(letter.toUpperCase().charAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> loadDictionary() {
|
||||
try {
|
||||
Scanner input = new Scanner(new File("dictionary.txt"));
|
||||
Set<String> dict = new HashSet<>();
|
||||
|
||||
while (input.hasNextLine()) {
|
||||
dict.add(input.nextLine());
|
||||
}
|
||||
|
||||
return dict;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Scrabble {
|
||||
public static void main(String[] args) {
|
||||
Set<String> dictionary = loadDictionary();
|
||||
List<Character> letters = readLetters();
|
||||
|
||||
String best = findBestWord(letters, dictionary);
|
||||
System.out.println(best + " (" + scoreWord(best) + ")");
|
||||
}
|
||||
|
||||
public static String findBestWord(List<Character> letters, Set<String> dict) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// HELPER METHODS
|
||||
|
||||
// does not check if word is legal
|
||||
private static int scoreWord(String word) {
|
||||
// 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
|
||||
int[] values = {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};
|
||||
|
||||
int score = 0;
|
||||
word = word.toUpperCase();
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
char ch = word.charAt(i);
|
||||
score += values[ch - 'A'];
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static List<Character> readLetters() {
|
||||
List<Character> result = new ArrayList<>();
|
||||
|
||||
Scanner input = new Scanner(System.in);
|
||||
System.out.print("Enter letters separated by whitespace: ");
|
||||
Scanner letters = new Scanner(input.nextLine());
|
||||
while (letters.hasNext()) {
|
||||
String letter = letters.next();
|
||||
if (letter.length() > 1) {
|
||||
System.out.println(" Skipping " + letter + " - not one letter");
|
||||
} else {
|
||||
result.add(letter.toUpperCase().charAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> loadDictionary() {
|
||||
try {
|
||||
Scanner input = new Scanner(new File("dictionary.txt"));
|
||||
Set<String> dict = new HashSet<>();
|
||||
|
||||
while (input.hasNextLine()) {
|
||||
dict.add(input.nextLine());
|
||||
}
|
||||
|
||||
return dict;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
public class IsPalindromeArray {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(isPalindrome(new int[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 }));
|
||||
System.out.println(isPalindrome(new int[] { 10, 20, 20, 10 }));
|
||||
System.out.println(isPalindrome(new int[] { 1 }));
|
||||
System.out.println(isPalindrome(new int[] {}));
|
||||
System.out.println(isPalindrome(new int[] { 1, 2, 3, 4, 5 }));
|
||||
System.out.println(isPalindrome(new int[] { 1, 2, 5, 3 }));
|
||||
System.out.println(isPalindrome(new int[] { 10, 20, 30, 10 }));
|
||||
}
|
||||
|
||||
public static boolean isPalindrome(int[] array) {
|
||||
return isPalindrome(array, 0, array.length - 1);
|
||||
}
|
||||
|
||||
private static boolean isPalindrome(int[] array, int lower, int upper) {
|
||||
if (upper - lower < 1) {
|
||||
return true;
|
||||
} else if (array[lower] != array[upper]) {
|
||||
return false;
|
||||
} else {
|
||||
return isPalindrome(array, lower+1,upper-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
public class IsPalindromeString {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(isPalindrome("racecar"));
|
||||
System.out.println(isPalindrome("tacocat"));
|
||||
System.out.println(isPalindrome("ABBA"));
|
||||
System.out.println(isPalindrome("x"));
|
||||
System.out.println(isPalindrome(""));
|
||||
System.out.println(isPalindrome("banana"));
|
||||
System.out.println(isPalindrome("abcbba"));
|
||||
System.out.println(isPalindrome("bramb"));
|
||||
}
|
||||
|
||||
public static boolean isPalindrome(String s) {
|
||||
if (s.length() <= 1 || s.equals(ReverseString.reverse(s))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
public class RecursiveMystery {
|
||||
public static void main(String[] args) {
|
||||
// mystery1(3);
|
||||
// mystery2(4);
|
||||
mystery3("taco");
|
||||
}
|
||||
|
||||
public static void mystery1(int n) {
|
||||
if (n <= 1) {
|
||||
System.out.print(n);
|
||||
} else {
|
||||
mystery1(n / 2);
|
||||
System.out.print(", " + n);
|
||||
}
|
||||
}
|
||||
|
||||
public static void mystery2(int n) {
|
||||
if (n <= 0) {
|
||||
System.out.print("*");
|
||||
} else if (n % 2 == 0) {
|
||||
System.out.print("(");
|
||||
mystery2(n - 1);
|
||||
System.out.print(")");
|
||||
} else {
|
||||
System.out.print("[");
|
||||
mystery2(n - 1);
|
||||
System.out.print("]");
|
||||
}
|
||||
}
|
||||
|
||||
public static void mystery3(String str) {
|
||||
if (!str.isEmpty()) {
|
||||
System.out.print(str.charAt(0));
|
||||
mystery3(str.substring(1));
|
||||
System.out.print(str.charAt(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
public class ReverseString {
|
||||
public static void main(String[] args){
|
||||
System.out.println(reverse("Hello"));
|
||||
System.out.println(reverse("nathan"));
|
||||
System.out.println(reverse("racecar"));
|
||||
System.out.println(reverse("taco"));
|
||||
System.out.println(reverse("ABBA"));
|
||||
System.out.println(reverse(""));
|
||||
System.out.println(reverse("a"));
|
||||
}
|
||||
|
||||
public static String reverse(String str){
|
||||
if (str.isEmpty()) {
|
||||
return "";
|
||||
} else if (str.length() == 1) {
|
||||
return str;
|
||||
}
|
||||
|
||||
String backwards = reverse(str.substring(1)) + str.charAt(0);
|
||||
return backwards;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import java.util.*;
|
||||
|
||||
// A program to work with Mini-Git. Manages the state of repositories and allows for all
|
||||
// operations defined in Mini-Git.
|
||||
public class Client {
|
||||
private static List<String> ops = new ArrayList<>();
|
||||
|
||||
public static void main(String[] args) {
|
||||
Collections.addAll(ops, "create", "head", "history", "commit", "drop",
|
||||
"synchronize", "quit");
|
||||
Scanner console = new Scanner(System.in);
|
||||
Map<String, Repository> repos = new HashMap<>();
|
||||
String op = "";
|
||||
String name = "";
|
||||
|
||||
intro();
|
||||
|
||||
while (!op.equalsIgnoreCase("quit")) {
|
||||
System.out.println("Available repositories: ");
|
||||
for (Repository repo : repos.values()) {
|
||||
System.out.println("\t" + repo);
|
||||
}
|
||||
System.out.println("Operations: " + ops);
|
||||
System.out.print("Enter operation and repository: ");
|
||||
String[] input = console.nextLine().split("\\s+");
|
||||
op = input[0];
|
||||
name = input.length > 1 ? input[1] : "";
|
||||
while (!ops.contains(op) || (!op.equalsIgnoreCase("create") &&
|
||||
!op.equalsIgnoreCase("quit") &&
|
||||
!repos.containsKey(name))) {
|
||||
System.out.println(" **ERROR**: Operation or repository not recognized.");
|
||||
System.out.print("Enter operation and repository: ");
|
||||
input = console.nextLine().split("\\s+");
|
||||
op = input[0];
|
||||
name = input.length > 1 ? input[1] : "";
|
||||
}
|
||||
|
||||
Repository currRepo = repos.get(name);
|
||||
op = op.toLowerCase();
|
||||
if (op.equalsIgnoreCase("create")) {
|
||||
if (currRepo != null) {
|
||||
System.out.println(" **ERROR**: Repository with that name already exists.");
|
||||
} else {
|
||||
Repository newRepo = new Repository(name);
|
||||
repos.put(name, newRepo);
|
||||
System.out.println(" New repository created: " + newRepo);
|
||||
}
|
||||
} else if (op.equalsIgnoreCase("head")) {
|
||||
System.out.println(currRepo.getRepoHead());
|
||||
} else if (op.equalsIgnoreCase("history")) {
|
||||
System.out.print("How many commits back? ");
|
||||
int nHist = console.nextInt();
|
||||
console.nextLine();
|
||||
System.out.println(currRepo.getHistory(nHist));
|
||||
} else if (op.equalsIgnoreCase("commit")) {
|
||||
System.out.print("Enter commit message: ");
|
||||
String message = console.nextLine();
|
||||
System.out.println(" New commit: " + currRepo.commit(message));
|
||||
} else if (op.equalsIgnoreCase("drop")) {
|
||||
System.out.print("Enter ID to drop: ");
|
||||
String idDrop = console.nextLine();
|
||||
if (currRepo.drop(idDrop)) {
|
||||
System.out.println(" Successfully dropped " + idDrop);
|
||||
} else {
|
||||
System.out.println(" No commit dropped!");
|
||||
}
|
||||
} else if (op.equalsIgnoreCase("synchronize")) {
|
||||
System.out.print("Which repository would you like to " +
|
||||
"synchronize into the given one? ");
|
||||
String repo = console.nextLine();
|
||||
if (repo.equals(name)) {
|
||||
System.out.println("Cannot synchronize the same repositories!");
|
||||
} else if (!repos.containsKey(repo)) {
|
||||
System.out.println("Repository does not exist!");
|
||||
} else {
|
||||
currRepo.synchronize(repos.get(repo));
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
// Prints out an introduction to the Mini-Git test client.
|
||||
public static void intro() {
|
||||
System.out.println("Welcome to the Mini-Git test client!");
|
||||
System.out.println("Use this program to test your Mini-Git repository implemenation.");
|
||||
System.out.println("Make sure to test all operations in all cases --");
|
||||
System.out.println("some cases are particularly tricky.");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import org.junit.jupiter.api.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Testing {
|
||||
private Repository repo1;
|
||||
private Repository repo2;
|
||||
|
||||
// Occurs before each of the individual test cases
|
||||
// (creates new repos and resets commit ids)
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
repo1 = new Repository("repo1");
|
||||
repo2 = new Repository("repo2");
|
||||
Repository.Commit.resetIds();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST - getHistory()")
|
||||
public void getHistory() throws InterruptedException {
|
||||
// Initialize commit messages
|
||||
String[] commitMessages = new String[]{"Initial commit.",
|
||||
"Updated method documentation.",
|
||||
"Removed unnecessary object creation."};
|
||||
commitAll(repo1, commitMessages);
|
||||
testHistory(repo1, 1, commitMessages);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST - drop() (empty case)")
|
||||
public void testDropEmpty() {
|
||||
assertFalse(repo1.drop("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST - drop() (front case)")
|
||||
public void testDropFront() throws InterruptedException {
|
||||
// Initialize commit messages
|
||||
commitAll(repo1, new String[]{"First commit"}); // ID "0"
|
||||
commitAll(repo2, new String[]{"Added unit tests."}); // ID "1"
|
||||
|
||||
// Assert that repo1 successfully dropped "0"
|
||||
assertTrue(repo1.drop("0"));
|
||||
assertEquals(repo1.getRepoSize(), 0);
|
||||
|
||||
// Assert that repo2 does not drop "0" but drops "1"
|
||||
// (Note that the commit ID increments regardless of the repository!)
|
||||
assertFalse(repo2.drop("0"));
|
||||
assertTrue(repo2.drop("1"));
|
||||
assertEquals(repo2.getRepoSize(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("EXAMPLE TEST - synchronize() (one: [1, 2], two: [3, 4])")
|
||||
public void testSynchronizeOne() throws InterruptedException {
|
||||
// Initialize commit messages
|
||||
commitAll(repo1, new String[]{"One", "Two"});
|
||||
commitAll(repo2, new String[]{"Three", "Four"});
|
||||
|
||||
// Make sure both repos got exactly 2 commits each
|
||||
assertEquals(2, repo1.getRepoSize());
|
||||
assertEquals(2, repo2.getRepoSize());
|
||||
|
||||
// Synchronize repo2 into repo1
|
||||
repo1.synchronize(repo2);
|
||||
assertEquals(4, repo1.getRepoSize());
|
||||
assertEquals(0, repo2.getRepoSize());
|
||||
|
||||
// Make sure the history of repo1 is correctly synchronized
|
||||
testHistory(repo1, 4, new String[]{"One", "Two", "Three", "Four"});
|
||||
}
|
||||
|
||||
// Commits all of the provided messages into the provided repo, making sure timestamps
|
||||
// are correctly sequential (no ties). If used, make sure to include
|
||||
// 'throws InterruptedException'
|
||||
// much like we do with 'throws FileNotFoundException'. Example useage:
|
||||
//
|
||||
// repo1:
|
||||
// head -> null
|
||||
// To commit the messages "one", "two", "three", "four"
|
||||
// commitAll(repo1, new String[]{"one", "two", "three", "four"})
|
||||
// This results in the following after picture
|
||||
// repo1:
|
||||
// head -> "four" -> "three" -> "two" -> "one" -> null
|
||||
//
|
||||
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT!
|
||||
// (this is why documentation is important!)
|
||||
public void commitAll(Repository repo, String[] messages) throws InterruptedException {
|
||||
// Commit all of the provided messages
|
||||
for (String message : messages) {
|
||||
int size = repo.getRepoSize();
|
||||
repo.commit(message);
|
||||
|
||||
// Make sure exactly one commit was added to the repo
|
||||
assertEquals(size + 1, repo.getRepoSize(),
|
||||
String.format("Size not correctly updated after commiting message [%s]",
|
||||
message));
|
||||
|
||||
// Sleep to guarantee that all commits have different time stamps
|
||||
Thread.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure the given repositories history is correct up to 'n' commits, checking against
|
||||
// all commits made in order. Example useage:
|
||||
//
|
||||
// repo1:
|
||||
// head -> "four" -> "three" -> "two" -> "one" -> null
|
||||
// (Commits made in the order ["one", "two", "three", "four"])
|
||||
// To test the getHistory() method up to n=3 commits this can be done with:
|
||||
// testHistory(repo1, 3, new String[]{"one", "two", "three", "four"})
|
||||
// Similarly, to test getHistory() up to n=4 commits you'd use:
|
||||
// testHistory(repo1, 4, new String[]{"one", "two", "three", "four"})
|
||||
//
|
||||
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT!
|
||||
// (this is why documentation is important!)
|
||||
public void testHistory(Repository repo, int n, String[] allCommits) {
|
||||
int totalCommits = repo.getRepoSize();
|
||||
assertTrue(n <= totalCommits,
|
||||
String.format("Provided n [%d] too big. Only [%d] commits",
|
||||
n, totalCommits));
|
||||
|
||||
String[] nCommits = repo.getHistory(n).split("\n");
|
||||
|
||||
assertTrue(nCommits.length <= n,
|
||||
String.format("getHistory(n) returned more than n [%d] commits", n));
|
||||
assertTrue(nCommits.length <= allCommits.length,
|
||||
String.format("Not enough expected commits to check against. " +
|
||||
"Expected at least [%d]. Actual [%d]",
|
||||
n, allCommits.length));
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
String commit = nCommits[i];
|
||||
|
||||
// Old commit messages/ids are on the left and the more recent commit messages/ids are
|
||||
// on the right so need to traverse from right to left
|
||||
int backwardsIndex = totalCommits - 1 - i;
|
||||
String commitMessage = allCommits[backwardsIndex];
|
||||
|
||||
assertTrue(commit.contains(commitMessage),
|
||||
String.format("Commit [%s] doesn't contain expected message [%s]",
|
||||
commit, commitMessage));
|
||||
assertTrue(commit.contains("" + backwardsIndex),
|
||||
String.format("Commit [%s] doesn't contain expected id [%d]",
|
||||
commit, backwardsIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import java.util.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class Repository {
|
||||
|
||||
private String name;
|
||||
private Commit head;
|
||||
private int size;
|
||||
|
||||
public Repository(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new IllegalArgumentException("Repository needs a name!");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getRepoHead() {
|
||||
if (head == null) {
|
||||
return null;
|
||||
}
|
||||
return head.id;
|
||||
}
|
||||
|
||||
public int getRepoSize() {
|
||||
Commit temp = head;
|
||||
int size = 0;
|
||||
while (temp != null) {
|
||||
size++;
|
||||
temp = temp.past;
|
||||
}
|
||||
this.size = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (head == null) {
|
||||
return name + " - No commits";
|
||||
}
|
||||
return name + " - Current head: " + head.toString();
|
||||
}
|
||||
|
||||
public boolean contains(String targetId) {
|
||||
Commit temp = head;
|
||||
|
||||
while (temp != null) {
|
||||
if (temp.id.equals(targetId)) {
|
||||
return true;
|
||||
}
|
||||
temp = temp.past;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getHistory(int n) {
|
||||
if (n <= 0) {
|
||||
throw new IllegalArgumentException("do you want the history or not?");
|
||||
}
|
||||
|
||||
Commit temp = head;
|
||||
String history = "";
|
||||
if (n > size) {
|
||||
while (temp != null) {
|
||||
history += temp.toString() + "\n";
|
||||
temp = temp.past;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n && temp != null; i++) {
|
||||
history += temp.toString() + "\n";
|
||||
temp = temp.past;
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
public String commit(String message) {
|
||||
if (head == null) {
|
||||
head = new Commit(message);
|
||||
return head.id;
|
||||
}
|
||||
Commit old = head;
|
||||
head = new Commit(message, old);
|
||||
size++;
|
||||
return head.id;
|
||||
}
|
||||
|
||||
public boolean drop(String targetId) {
|
||||
// list is empty
|
||||
if (head == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size--;
|
||||
Commit temp = head;
|
||||
|
||||
// front
|
||||
if (temp.id.equals(targetId)) {
|
||||
head = temp.past;
|
||||
return true;
|
||||
}
|
||||
|
||||
// middle
|
||||
while (temp.past != null && temp.past.past != null) {
|
||||
if (temp.past.id == targetId) {
|
||||
temp.past = temp.past.past;
|
||||
return true;
|
||||
}
|
||||
temp = temp.past;
|
||||
}
|
||||
|
||||
// end
|
||||
if (temp.past != null && temp.past.id.equals(targetId)) {
|
||||
temp.past = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we get here, targetId was not found, return size to initial state
|
||||
size++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void synchronize(Repository other) {
|
||||
if (head == null && other.head == null) {
|
||||
head = null;
|
||||
} else if (head == null) {
|
||||
head = other.head;
|
||||
other.head = null;
|
||||
} else if (other.head == null) {
|
||||
other.head = null;
|
||||
} else {
|
||||
List<Commit> commitList = new ArrayList<>();
|
||||
Comparator commitComparator = new Comparator<Commit>() {
|
||||
public int compare(Commit one, Commit two) {
|
||||
return Long.compare(one.timeStamp, two.timeStamp);
|
||||
}
|
||||
};
|
||||
Commit temp = head;
|
||||
while (temp != null) {
|
||||
commitList.add(temp);
|
||||
temp = temp.past;
|
||||
}
|
||||
temp = other.head;
|
||||
while (temp != null) {
|
||||
commitList.add(temp);
|
||||
temp = temp.past;
|
||||
}
|
||||
commitList.sort(commitComparator);
|
||||
Commit prev = null;
|
||||
for (Commit commit : commitList) {
|
||||
commit.past = prev;
|
||||
prev = commit;
|
||||
}
|
||||
head = commitList.get(commitList.size() - 1);
|
||||
other.head = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* DO NOT MODIFY
|
||||
* A class that represents a single commit in the repository.
|
||||
* Commits are characterized by an identifier, a commit message,
|
||||
* and the time that the commit was made. A commit also stores
|
||||
* a reference to the immediately previous commit if it exists.
|
||||
*
|
||||
* Staff Note: You may notice that the comments in this
|
||||
* class openly mention the fields of the class. This is fine
|
||||
* because the fields of the Commit class are public. In general,
|
||||
* be careful about revealing implementation details!
|
||||
*/
|
||||
public class Commit {
|
||||
|
||||
private static int currentCommitID;
|
||||
|
||||
/**
|
||||
* The time, in milliseconds, at which this commit was created.
|
||||
*/
|
||||
public final long timeStamp;
|
||||
|
||||
/**
|
||||
* A unique identifier for this commit.
|
||||
*/
|
||||
public final String id;
|
||||
|
||||
/**
|
||||
* A message describing the changes made in this commit.
|
||||
*/
|
||||
public final String message;
|
||||
|
||||
/**
|
||||
* A reference to the previous commit, if it exists. Otherwise, null.
|
||||
*/
|
||||
public Commit past;
|
||||
|
||||
/**
|
||||
* Constructs a commit object. The unique identifier and timestamp
|
||||
* are automatically generated.
|
||||
* @param message A message describing the changes made in this commit.
|
||||
* @param past A reference to the commit made immediately before this
|
||||
* commit.
|
||||
*/
|
||||
public Commit(String message, Commit past) {
|
||||
this.id = "" + currentCommitID++;
|
||||
this.message = message;
|
||||
this.timeStamp = System.currentTimeMillis();
|
||||
this.past = past;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a commit object with no previous commit. The unique
|
||||
* identifier and timestamp are automatically generated.
|
||||
* @param message A message describing the changes made in this commit.
|
||||
*/
|
||||
public Commit(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this commit. The string
|
||||
* representation consists of this commit's unique identifier,
|
||||
* timestamp, and message, in the following form:
|
||||
* "[identifier] at [timestamp]: [message]"
|
||||
* @return The string representation of this collection.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
|
||||
Date date = new Date(timeStamp);
|
||||
|
||||
return id + " at " + formatter.format(date) + ": " + message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the IDs of the commit nodes such that they reset to 0.
|
||||
* Primarily for testing purposes.
|
||||
*/
|
||||
public static void resetIds() {
|
||||
Commit.currentCommitID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import org.junit.jupiter.api.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Testing {
|
||||
private Repository repo1;
|
||||
private Repository repo2;
|
||||
|
||||
// Occurs before each of the individual test cases
|
||||
// (creates new repos and resets commit ids)
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
repo1 = new Repository("repo1");
|
||||
repo2 = new Repository("repo2");
|
||||
Repository.Commit.resetIds();
|
||||
}
|
||||
|
||||
// TODO: Write your tests here!
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// PROVIDED HELPER METHODS (You don't have to use these if you don't want to!) //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Commits all of the provided messages into the provided repo, making sure timestamps
|
||||
// are correctly sequential (no ties). If used, make sure to include
|
||||
// 'throws InterruptedException'
|
||||
// much like we do with 'throws FileNotFoundException'. Example useage:
|
||||
//
|
||||
// repo1:
|
||||
// head -> null
|
||||
// To commit the messages "one", "two", "three", "four"
|
||||
// commitAll(repo1, new String[]{"one", "two", "three", "four"})
|
||||
// This results in the following after picture
|
||||
// repo1:
|
||||
// head -> "four" -> "three" -> "two" -> "one" -> null
|
||||
//
|
||||
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT! (this is why documentation
|
||||
// is important!)
|
||||
public void commitAll(Repository repo, String[] messages) throws InterruptedException {
|
||||
// Commit all of the provided messages
|
||||
for (String message : messages) {
|
||||
int size = repo.getRepoSize();
|
||||
repo.commit(message);
|
||||
|
||||
// Make sure exactly one commit was added to the repo
|
||||
assertEquals(size + 1, repo.getRepoSize(),
|
||||
String.format("Size not correctly updated after commiting message [%s]",
|
||||
message));
|
||||
|
||||
// Sleep to guarantee that all commits have different time stamps
|
||||
Thread.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure the given repositories history is correct up to 'n' commits, checking against
|
||||
// all commits made in order. Example useage:
|
||||
//
|
||||
// repo1:
|
||||
// head -> "four" -> "three" -> "two" -> "one" -> null
|
||||
// (Commits made in the order ["one", "two", "three", "four"])
|
||||
// To test the getHistory() method up to n=3 commits this can be done with:
|
||||
// testHistory(repo1, 3, new String[]{"one", "two", "three", "four"})
|
||||
// Similarly, to test getHistory() up to n=4 commits you'd use:
|
||||
// testHistory(repo1, 4, new String[]{"one", "two", "three", "four"})
|
||||
//
|
||||
// YOU DO NOT NEED TO UNDERSTAND HOW THIS METHOD WORKS TO USE IT! (this is why documentation
|
||||
// is important!)
|
||||
public void testHistory(Repository repo, int n, String[] allCommits) {
|
||||
int totalCommits = repo.getRepoSize();
|
||||
assertTrue(n <= totalCommits,
|
||||
String.format("Provided n [%d] too big. Only [%d] commits",
|
||||
n, totalCommits));
|
||||
|
||||
String[] nCommits = repo.getHistory(n).split("\n");
|
||||
|
||||
assertTrue(nCommits.length <= n,
|
||||
String.format("getHistory(n) returned more than n [%d] commits", n));
|
||||
assertTrue(nCommits.length <= allCommits.length,
|
||||
String.format("Not enough expected commits to check against. " +
|
||||
"Expected at least [%d]. Actual [%d]",
|
||||
n, allCommits.length));
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
String commit = nCommits[i];
|
||||
|
||||
// Old commit messages/ids are on the left and the more recent commit messages/ids are
|
||||
// on the right so need to traverse from right to left
|
||||
int backwardsIndex = totalCommits - 1 - i;
|
||||
String commitMessage = allCommits[backwardsIndex];
|
||||
|
||||
assertTrue(commit.contains(commitMessage),
|
||||
String.format("Commit [%s] doesn't contain expected message [%s]",
|
||||
commit, commitMessage));
|
||||
assertTrue(commit.contains("" + backwardsIndex),
|
||||
String.format("Commit [%s] doesn't contain expected id [%d]",
|
||||
commit, backwardsIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Allocation class represents an unmodifiable relief solution.
|
||||
* It provides methods to retrieve the total cost and total helped population
|
||||
* of the solution. The ordering of the regions in the solution determines
|
||||
* the population that can be helped.
|
||||
*/
|
||||
public class Allocation {
|
||||
|
||||
private List<Region> regions;
|
||||
|
||||
/**
|
||||
* Creates a new Allocation object representing the given regions.
|
||||
* @param regions the regions in the solution
|
||||
*/
|
||||
private Allocation(List<Region> regions) {
|
||||
this.regions = new ArrayList<>(regions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Allocation object with no regions in it.
|
||||
*/
|
||||
public Allocation() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this allocation's regions.
|
||||
*/
|
||||
public List<Region> getRegions() {
|
||||
return new ArrayList<>(regions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Allocation with the contents of this allocation
|
||||
* and the passed in region added to it.
|
||||
* @param r Region to be added to the end of the new Allocation.
|
||||
* @return a new Allocation with r added to it.
|
||||
*/
|
||||
public Allocation withRegion(Region r) {
|
||||
if (regions.contains(r)) {
|
||||
throw new IllegalArgumentException("Allocation already contains region " + r);
|
||||
}
|
||||
List<Region> newRegions = new ArrayList<>(regions);
|
||||
newRegions.add(r);
|
||||
return new Allocation(newRegions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Allocation with the contents of this allocation
|
||||
* and the passed in region removed from it.
|
||||
* @param r Region to be removed from the new Allocation.
|
||||
* @return a new Allocation with r removed from it.
|
||||
*/
|
||||
public Allocation withoutRegion(Region r) {
|
||||
if (!regions.contains(r)) {
|
||||
throw new IllegalArgumentException("Allocation doesn't contain region " + r);
|
||||
}
|
||||
List<Region> newRegions = new ArrayList<>(regions);
|
||||
newRegions.remove(r);
|
||||
return new Allocation(newRegions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of regions in this Allocation.
|
||||
*/
|
||||
public int size() {
|
||||
return regions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns the total population that can be helped
|
||||
* by this Allocation.
|
||||
* @return the total population that can be helped by this Allocation.
|
||||
*/
|
||||
public int totalPeople() {
|
||||
int total = 0;
|
||||
for (Region r : regions) {
|
||||
total += r.getPopulation();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns the combined cost of this Allocation.
|
||||
* @return the combined cost of this Allocation.
|
||||
*/
|
||||
public double totalCost() {
|
||||
double total = 0;
|
||||
for (int i = 0; i < regions.size(); i++) {
|
||||
total += regions.get(i).getCost(i);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String representation of an Allocation object in the format:
|
||||
* "[Region, ..., Region]" where each Region is in its string representation.
|
||||
* @return the String representation of an Allocation object
|
||||
*/
|
||||
public String toString() {
|
||||
return regions.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the specified object with this allocation for equality. Returns true if the
|
||||
* specified object is also an Allocation and the two Allocations have the same
|
||||
* collection of regions.
|
||||
* @param other object to be compared for equality with this allocation
|
||||
* @return true if the specified object is equal to this allocation
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof Allocation)) {
|
||||
return false;
|
||||
}
|
||||
Allocation otherAlloc = (Allocation)other;
|
||||
return this.regions.equals(otherAlloc.getRegions());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return regions.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import java.util.*;
|
||||
|
||||
public class Client {
|
||||
private static Random rand = new Random();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// List<Region> scenario = createRandomScenario(10, 10, 100, 1000, 100000);
|
||||
List<Region> scenario = createSimpleScenario();
|
||||
System.out.println(scenario);
|
||||
|
||||
double budget = 2000;
|
||||
Set<Allocation> allocations = generateOptions(budget, scenario);
|
||||
printAllocations(allocations);
|
||||
}
|
||||
|
||||
public static Set<Allocation> generateOptions(double budget, List<Region> sites) {
|
||||
Set<Allocation> allocations = new HashSet<>();
|
||||
return generateOptions(budget, sites, allocations);
|
||||
}
|
||||
|
||||
private static Set<Allocation> generateOptions(double budget, List<Region> sites,
|
||||
Set<Allocation> allocations) {
|
||||
if (sites.isEmpty()) {
|
||||
return allocations;
|
||||
}
|
||||
|
||||
Region temp = sites.remove();
|
||||
if (budget >= temp.baseCost) {
|
||||
budget -= temp.baseCost;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PROVIDED HELPER METHODS - **DO NOT MODIFY ANYTHING BELOW THIS LINE!** //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void printAllocations(Set<Allocation> allocations) {
|
||||
System.out.println("All Allocations:");
|
||||
for (Allocation a : allocations) {
|
||||
System.out.println(" " + a);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Region> createRandomScenario(int numLocs, int minPop, int maxPop,
|
||||
double minCostPer, double maxCostPer) {
|
||||
List<Region> result = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < numLocs; i++) {
|
||||
int pop = rand.nextInt(minPop, maxPop + 1);
|
||||
double cost = rand.nextDouble(minCostPer, maxCostPer) * pop;
|
||||
result.add(new Region("Region #" + i, pop, round2(cost)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Region> createSimpleScenario() {
|
||||
List<Region> result = new ArrayList<>();
|
||||
|
||||
result.add(new Region("Region #1", 50, 500));
|
||||
result.add(new Region("Region #2", 100, 700));
|
||||
result.add(new Region("Region #3", 60, 1000));
|
||||
result.add(new Region("Region #4", 20, 1000));
|
||||
result.add(new Region("Region #5", 200, 900));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double round2(double num) {
|
||||
return Math.round(num * 100) / 100.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* The Region class represents a geographical location with a name, population, and cost.
|
||||
* It provides methods to retrieve the population and cost of the location,
|
||||
* as well as a method to generate a string representation of the object.
|
||||
*/
|
||||
public class Region {
|
||||
private String name;
|
||||
private int population;
|
||||
private double baseCost;
|
||||
|
||||
/**
|
||||
* Creates a new Region object with the given name, population, and cost.
|
||||
* @param name the name of the location
|
||||
* @param pop the population of the location
|
||||
* @param baseCost the base cost of the location
|
||||
*/
|
||||
public Region(String name, int pop, double baseCost) {
|
||||
this.name = name;
|
||||
this.population = pop;
|
||||
this.baseCost = baseCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the population of the location
|
||||
* @return the population of the location
|
||||
*/
|
||||
public int getPopulation() { return this.population; }
|
||||
|
||||
/**
|
||||
* Returns the cost of the location
|
||||
* @param index a number indicating when this region is provided relief. A larger value for
|
||||
* index indicates that this region is helped later.
|
||||
* @return the cost of providing relief to this region. Regions that are
|
||||
* helped later (i.e. with a larger index value) have a higher cost.
|
||||
*/
|
||||
public double getCost(int index) {
|
||||
return (1 + 0.1 * index) * this.baseCost;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a String representation of a Region object in the format:
|
||||
* "<name>: pop. <population>, cost: $<cost>"
|
||||
* @return the String representation of a Region object
|
||||
*/
|
||||
public String toString() {
|
||||
return name + ": pop. " + population + ", base cost: $" + baseCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the specified object with this location for equality. Returns true if the
|
||||
* specified object is also a location and the two locations have the
|
||||
* same name, population, and cost.
|
||||
* @param other object to be compared for equality with this location
|
||||
* @return true if the specified object is equal to this location
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof Region)) {
|
||||
return false;
|
||||
}
|
||||
Region otherLoc = (Region)other;
|
||||
|
||||
return this.name.equals(otherLoc.name) &&
|
||||
this.population == otherLoc.population &&
|
||||
this.baseCost == otherLoc.baseCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code value for this location
|
||||
* @return the hash code value for this location
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = name.hashCode();
|
||||
result = 31 * result + Integer.hashCode(population);
|
||||
result = 31 * result + Double.hashCode(baseCost);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// The BitOutputStream and BitInputStream classes provide the ability to
|
||||
// write and read individual bits to a file in a compact form. One major
|
||||
// limitation of this approach is that the resulting file will always have
|
||||
// a number of bits that is a multiple of 8. In effect, whatever bits are
|
||||
// output to the file are padded at the end with 0's to make the total
|
||||
// number of bits a multiple of 8.
|
||||
//
|
||||
// BitInputStream has the following public methods:
|
||||
// public BitInputStream(String file)
|
||||
// opens an input stream with the given file name
|
||||
// public int nextBit()
|
||||
// reads the next bit from input (throws exception if at end of file)
|
||||
// public boolean hasNextBit()
|
||||
// returns true if there's another bit in the input stream to be read
|
||||
// public void close()
|
||||
// closes the input
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BitInputStream {
|
||||
private FileInputStream input;
|
||||
private int currentByte; // current set of bits (buffer)
|
||||
private int nextByte; // next set of bits (buffer)
|
||||
private int numBits; // how many bits from buffer have been used
|
||||
private int remainingAtEnd; // how many bits will be remaining at the end
|
||||
// after we're done
|
||||
|
||||
private static final int BYTE_SIZE = 8; // bits per byte
|
||||
|
||||
// pre : given file name is legal
|
||||
// post: creates a BitInputStream reading input from the file
|
||||
public BitInputStream(String file) {
|
||||
try {
|
||||
this.input = new FileInputStream(file);
|
||||
|
||||
// Read in the number of remaining bits at the end
|
||||
this.remainingAtEnd = this.input.read();
|
||||
|
||||
// Set up the nextByte field.
|
||||
this.nextByte = this.input.read();
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex.toString());
|
||||
}
|
||||
|
||||
this.nextByte();
|
||||
}
|
||||
|
||||
public boolean hasNextBit() {
|
||||
boolean atEnd = this.currentByte == -1;
|
||||
boolean onlyRemaining = this.nextByte == -1
|
||||
&& BYTE_SIZE - this.numBits == this.remainingAtEnd;
|
||||
return !atEnd && !onlyRemaining;
|
||||
}
|
||||
|
||||
// post: reads next bit from input
|
||||
// throws NoSuchElementException if there is no bit to return
|
||||
public int nextBit() {
|
||||
// if at eof, throw exception
|
||||
if (!this.hasNextBit()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
int result = this.currentByte % 2;
|
||||
this.currentByte /= 2;
|
||||
this.numBits++;
|
||||
if (this.numBits == BYTE_SIZE) {
|
||||
this.nextByte();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// post: refreshes the internal buffer with the next BYTE_SIZE bits
|
||||
private void nextByte() {
|
||||
this.currentByte = this.nextByte;
|
||||
if (this.currentByte != -1) {
|
||||
try {
|
||||
this.nextByte = this.input.read();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this.numBits = 0;
|
||||
}
|
||||
|
||||
// post: input is closed
|
||||
public void close() {
|
||||
try {
|
||||
this.input.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// included to ensure that the stream is closed
|
||||
protected void finalize() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// The BitOutputStream and BitInputStream classes provide the ability to
|
||||
// write and read individual bits to a file in a compact form. One major
|
||||
// limitation of this approach is that the resulting file will always have
|
||||
// a number of bits that is a multiple of 8. In effect, whatever bits are
|
||||
// output to the file are padded at the end with 0's to make the total
|
||||
// number of bits a multiple of 8.
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BitOutputStream {
|
||||
private PrintStream output;
|
||||
private List<Integer> buffer;
|
||||
private int currentByte; // a buffer used to build up next set of digits
|
||||
private int numBits; // how many digits are currently in the buffer
|
||||
private boolean debug; // set to true to write ASCII 0s and 1s rather than
|
||||
// bits
|
||||
|
||||
private static final int BYTE_SIZE = 8; // digits per byte
|
||||
|
||||
// Creates a BitOutputStream sending output to the given stream. If debug
|
||||
// is set to true, bits are printed as ASCII 0s and 1s.
|
||||
public BitOutputStream(PrintStream output, boolean debug) {
|
||||
this.buffer = new ArrayList<Integer>();
|
||||
this.output = output;
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
// Writes given bit to output
|
||||
public void write(int bit) {
|
||||
if (this.debug) {
|
||||
System.out.print(bit);
|
||||
}
|
||||
if (bit < 0 || bit > 1) {
|
||||
throw new IllegalArgumentException("Illegal bit: " + bit);
|
||||
}
|
||||
this.currentByte += bit << this.numBits;
|
||||
this.numBits++;
|
||||
if (this.numBits == BYTE_SIZE) {
|
||||
this.buffer.add(this.currentByte);
|
||||
this.numBits = 0;
|
||||
this.currentByte = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// post: output is closed
|
||||
public void close() {
|
||||
int remaining = BYTE_SIZE - this.numBits;
|
||||
|
||||
if (remaining == 8) {
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
/* Flush the last byte (if there is one) */
|
||||
if (remaining > 0) {
|
||||
this.buffer.add(this.currentByte);
|
||||
}
|
||||
|
||||
/* Now that we've received all the output, prepend it with the number
|
||||
* of missing bits from the end.
|
||||
*/
|
||||
this.output.write(remaining);
|
||||
for (int b : this.buffer) {
|
||||
this.output.write(b);
|
||||
}
|
||||
this.output.close();
|
||||
}
|
||||
|
||||
// included to ensure that the stream is closed
|
||||
protected void finalize() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class HuffmanCode {
|
||||
|
||||
public HuffmanNode root;
|
||||
|
||||
// Constructor that builds the Huffman tree from an array of frequencies
|
||||
public HuffmanCode(int[] frequencies) {
|
||||
Queue<HuffmanNode> nodes = new PriorityQueue<>();
|
||||
for (int i = 0; i < frequencies.length; i++) {
|
||||
if (frequencies[i] > 0) {
|
||||
nodes.add(new HuffmanNode((char) i, frequencies[i]));
|
||||
}
|
||||
}
|
||||
while (nodes.size() > 1) {
|
||||
HuffmanNode left = nodes.remove();
|
||||
HuffmanNode right = nodes.remove();
|
||||
HuffmanNode branch = new HuffmanNode('\0', left.freq + right.freq, left, right);
|
||||
nodes.add(branch);
|
||||
}
|
||||
this.root = nodes.isEmpty() ? null : nodes.remove();
|
||||
}
|
||||
|
||||
// Constructor that reads the Huffman tree from a Scanner
|
||||
public HuffmanCode(Scanner input) {
|
||||
this.root = new HuffmanNode('\0', 0); // Initialize root with a dummy node
|
||||
while (input.hasNextLine()) {
|
||||
int character = Integer.parseInt(input.nextLine());
|
||||
String path = input.nextLine();
|
||||
addNode(character, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a node to the Huffman tree based on the given path
|
||||
private void addNode(int character, String path) {
|
||||
HuffmanNode current = root;
|
||||
for (int i = 0; i < path.length(); i++) {
|
||||
char direction = path.charAt(i);
|
||||
if (direction == '0') {
|
||||
if (current.left == null) {
|
||||
if (i == path.length() - 1) {
|
||||
current.left = new HuffmanNode((char) character, 1); // Final node
|
||||
} else {
|
||||
current.left = new HuffmanNode('\0', 0); // Create a dummy node if it doesn't exist
|
||||
}
|
||||
}
|
||||
current = current.left;
|
||||
} else if (direction == '1') {
|
||||
if (current.right == null) {
|
||||
if (i == path.length() - 1) {
|
||||
current.right = new HuffmanNode((char) character, 1); // Final node
|
||||
} else {
|
||||
current.right = new HuffmanNode('\0', 0); // Create a dummy node if it doesn't exist
|
||||
}
|
||||
}
|
||||
current = current.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Saves the Huffman tree to the given output stream in standard format
|
||||
public void save(PrintStream output) {
|
||||
save(output, this.root, "");
|
||||
}
|
||||
|
||||
private void save(PrintStream output, HuffmanNode head, String path) {
|
||||
if (head != null) {
|
||||
if (head.data != '\0') {
|
||||
output.println((int) head.data);
|
||||
output.println(path);
|
||||
}
|
||||
save(output, head.left, path + "0");
|
||||
save(output, head.right, path + "1");
|
||||
}
|
||||
}
|
||||
|
||||
// Translates bits from the input stream to characters and writes them to the output stream
|
||||
public void translate(BitInputStream input, PrintStream output) {
|
||||
HuffmanNode current = root;
|
||||
while (input.hasNextBit()) {
|
||||
int bit = input.nextBit();
|
||||
if (bit == 0) {
|
||||
current = current.left;
|
||||
} else {
|
||||
current = current.right;
|
||||
}
|
||||
if (current.left == null && current.right == null) { // Leaf node
|
||||
output.print(current.data);
|
||||
current = root; // Restart for next character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inner class representing a node in the Huffman tree
|
||||
private static class HuffmanNode implements Comparable<HuffmanNode> {
|
||||
public char data;
|
||||
public int freq;
|
||||
public HuffmanNode left;
|
||||
public HuffmanNode right;
|
||||
|
||||
public HuffmanNode(char data, int frequency) {
|
||||
this(data, frequency, null, null);
|
||||
}
|
||||
|
||||
public HuffmanNode(char data, int frequency, HuffmanNode left, HuffmanNode right) {
|
||||
this.data = data;
|
||||
this.freq = frequency;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public int compareTo(HuffmanNode other) {
|
||||
return Integer.compare(this.freq, other.freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class HuffmanCode {
|
||||
|
||||
public HuffmanNode root;
|
||||
|
||||
public HuffmanCode(int[] frequencies) {
|
||||
Queue<HuffmanNode> nodes = new PriorityQueue<>();
|
||||
for (int i=0;i<frequencies.length;i++) {
|
||||
nodes.add(new HuffmanNode((char) i, frequencies[i]));
|
||||
}
|
||||
while (nodes.size()>1) {
|
||||
HuffmanNode left = nodes.remove();
|
||||
HuffmanNode right = nodes.remove();
|
||||
HuffmanNode branch = new HuffmanNode(null, left.freq + right.freq, left, right);
|
||||
nodes.add(branch);
|
||||
}
|
||||
this.root = nodes.remove();
|
||||
}
|
||||
|
||||
public HuffmanCode(Scanner input) {
|
||||
assembleTree(input, new HuffmanNode(null, null));
|
||||
}
|
||||
|
||||
private assembleTree(Scanner input, HuffmanNode head) {
|
||||
if (input.hasNextLine()) {
|
||||
char character = (char) input.nextLine();
|
||||
}
|
||||
if (input.hasNextLine()) {
|
||||
String loc = input.nextLine();
|
||||
}
|
||||
if (head == null) {
|
||||
head = new HuffmanNode(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(PrintStream output) {
|
||||
save(output, this.root)
|
||||
}
|
||||
|
||||
private void save(PrintStream output, HuffmanNode head) {
|
||||
output.println((int) head.data);
|
||||
output.println() ;
|
||||
save(head.left);
|
||||
save(head.right);
|
||||
}
|
||||
|
||||
public void translate(BitInputStream input, PrintStream output) {
|
||||
}
|
||||
|
||||
private static class HuffmanNode implements Comparable<HuffmanNode> {
|
||||
public final char data;
|
||||
public final int freq;
|
||||
public HuffmanNode left;
|
||||
public HuffmanNode right;
|
||||
|
||||
public HuffmanNode(char data, int frequency) {
|
||||
this(data, frequency, null, null);
|
||||
}
|
||||
|
||||
public HuffmanNode(char data, int frequency, HuffmanNode left, HuffmanNode right) {
|
||||
this.data = data;
|
||||
this.freq = freq;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public int compareTo(HuffmanNode other) {
|
||||
if (this.freq == other.freq) {
|
||||
return 0;
|
||||
}
|
||||
else if (this.freq < other.freq) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class HuffmanCompressor {
|
||||
private String filename;
|
||||
|
||||
public static final int CHAR_MAX = 255; // max char value
|
||||
public static final int MAKE_CODE = 1;
|
||||
public static final int COMPRESS = 2;
|
||||
public static final int DECOMPRESS = 3;
|
||||
public static final int ROUND_TRIP = 4;
|
||||
|
||||
public HuffmanCompressor(String filename) {
|
||||
if (!filename.endsWith(".txt")) {
|
||||
throw new IllegalArgumentException(
|
||||
"This compressor only works on text files!");
|
||||
}
|
||||
this.filename = filename.split(".txt")[0];
|
||||
}
|
||||
|
||||
public void makeCode() throws IOException {
|
||||
System.out.println("I am about to make the Huffman code for "
|
||||
+ filename + ".txt...");
|
||||
FileInputStream input = new FileInputStream(filename + ".txt");
|
||||
int[] count = new int[CHAR_MAX];
|
||||
int n = input.read();
|
||||
while (n != -1) {
|
||||
int prev = n;
|
||||
count[n]++;
|
||||
n = input.read();
|
||||
|
||||
// Remove added newline by ed
|
||||
if (n == -1 && prev == 10) {
|
||||
count[prev]--;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the code; open the output file; save the code
|
||||
System.out.println("\tI built up a frequency table of the " +
|
||||
"characters in your file.");
|
||||
System.out.println("\tNow, I'm going to call your HuffmanCode(int[]) " +
|
||||
"constructor using that frequency table.");
|
||||
HuffmanCode t = new HuffmanCode(count);
|
||||
|
||||
System.out.println("\tOkay! Now, I am going to save the code (using " +
|
||||
"your save method) to the file " + filename +
|
||||
".code!");
|
||||
PrintStream output = new PrintStream(new File(filename + ".code"));
|
||||
t.save(output);
|
||||
System.out.println("...I am done making the Huffman Code!");
|
||||
}
|
||||
|
||||
public void compress(boolean debug) throws IOException {
|
||||
System.out.println("I am about to attempt to COMPRESS "
|
||||
+ filename + ".txt:");
|
||||
/* We must make the code before we can compress... */
|
||||
this.makeCode();
|
||||
System.out.println("\tNow that I have the Huffman Code, I am going " +
|
||||
"to use the huffman code file created by");
|
||||
System.out.println("\tyour save() method to compress the contents " +
|
||||
"into " + filename + ".short!");
|
||||
|
||||
String[] codes = new String[CHAR_MAX];
|
||||
Scanner codeInput = new Scanner(new File(filename + ".code"));
|
||||
while (codeInput.hasNextLine()) {
|
||||
int n = Integer.parseInt(codeInput.nextLine());
|
||||
codes[n] = codeInput.nextLine();
|
||||
}
|
||||
|
||||
// Open file to be compressed; open output file
|
||||
FileInputStream input = new FileInputStream(this.filename + ".txt");
|
||||
BitOutputStream output = new BitOutputStream(
|
||||
new PrintStream(this.filename + ".short"), debug);
|
||||
|
||||
// Print out the debug binary to a .debug file
|
||||
FileOutputStream debugOut = null;
|
||||
if (debug) {
|
||||
debugOut = new FileOutputStream(this.filename + ".debug");
|
||||
}
|
||||
|
||||
// Do the compression
|
||||
int n = input.read();
|
||||
while (n != -1) {
|
||||
if (codes[n] == null) {
|
||||
// check if this is actually an error, or an ed added newline
|
||||
if (!(n == 10 && input.read() == -1)) {
|
||||
System.out.println("Your code file has no code for " + n +
|
||||
" (the character '" + (char) n + "')");
|
||||
System.out.println("exiting...");
|
||||
System.exit(1);
|
||||
}
|
||||
n = -1;
|
||||
} else {
|
||||
for (int i = 0; i < codes[n].length(); i++) {
|
||||
output.write(codes[n].charAt(i) - '0');
|
||||
// Write to the .debug file
|
||||
if (debugOut != null) {
|
||||
debugOut.write(codes[n].charAt(i));
|
||||
}
|
||||
}
|
||||
n = input.read();
|
||||
}
|
||||
}
|
||||
input.close();
|
||||
output.close();
|
||||
System.out.println("...I am done compressing the file");
|
||||
}
|
||||
|
||||
public void decompress(boolean printToConsole) throws IOException {
|
||||
System.out.println("I am about to attempt to DECOMPRESS " +
|
||||
filename + ".short:");
|
||||
|
||||
System.out.println("\tTo do this, I must first read in the huffman " +
|
||||
"code used to compress the file.");
|
||||
System.out.println("\tI will use your HuffmanCode(Scanner) " +
|
||||
"constructor!");
|
||||
// Open code file and construct tree
|
||||
Scanner codeInput = new Scanner(new File(this.filename + ".code"));
|
||||
HuffmanCode t = new HuffmanCode(codeInput);
|
||||
|
||||
// Open compressed file; open output
|
||||
BitInputStream input = new BitInputStream(this.filename + ".short");
|
||||
PrintStream output = System.out;
|
||||
if (!printToConsole) {
|
||||
System.out.println("\tNow, I will decompress the file using your " +
|
||||
"translate() method and save");
|
||||
System.out.println("\tthe output into " + this.filename + ".new");
|
||||
output = new PrintStream(new File(this.filename + ".new"));
|
||||
}
|
||||
else {
|
||||
System.out.println("\tNow, I will decompress the file using your " +
|
||||
"translate() method and display it on the console");
|
||||
}
|
||||
|
||||
// Decompress the file
|
||||
t.translate(input, output);
|
||||
output.close();
|
||||
System.out.println("...I am done decompressing the file");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Scanner console = new Scanner(System.in);
|
||||
|
||||
System.out.println("Welcome to the CSE 123 Huffman Compressor!");
|
||||
System.out.println();
|
||||
|
||||
String filename;
|
||||
|
||||
do {
|
||||
System.out.print("Which file would you like to work with (it " +
|
||||
"must be a txt file)? ");
|
||||
filename = console.nextLine().trim();
|
||||
} while (!filename.endsWith(".txt"));
|
||||
|
||||
System.out.println();
|
||||
|
||||
int choice = -1;
|
||||
|
||||
while (choice == -1) {
|
||||
System.out.println("Would you like to:");
|
||||
System.out.println("\t(1) make a huffman code,");
|
||||
System.out.println("\t(2) compress a file,");
|
||||
System.out.println("\t(3) decompress a file, or");
|
||||
System.out.println(
|
||||
"\t(4) do a compression followed by a decompression");
|
||||
System.out.print("1-4? ");
|
||||
String choiceStr = console.nextLine().trim();
|
||||
try {
|
||||
choice = Integer.parseInt(choiceStr);
|
||||
if (choice < 1 || choice > 4) {
|
||||
choice = -1;
|
||||
}
|
||||
} catch(NumberFormatException e) {
|
||||
/* Don't change choice; so, the loop will repeat. */
|
||||
}
|
||||
}
|
||||
|
||||
boolean debug = false;
|
||||
if (choice == COMPRESS || choice == ROUND_TRIP) {
|
||||
debug = prompt(
|
||||
console,
|
||||
"Would you like to debug the compressed file (y/n)? ");
|
||||
}
|
||||
|
||||
boolean toConsole = false;
|
||||
if (choice == DECOMPRESS || choice == ROUND_TRIP) {
|
||||
toConsole = !prompt(
|
||||
console,
|
||||
"Would you like to print the result to a file (y/n)? ");
|
||||
}
|
||||
|
||||
HuffmanCompressor huffman = new HuffmanCompressor(filename);
|
||||
switch (choice) {
|
||||
case 1: huffman.makeCode(); break;
|
||||
case 2: huffman.compress(debug); break;
|
||||
case 3: huffman.decompress(toConsole); break;
|
||||
case 4:
|
||||
huffman.compress(debug);
|
||||
huffman.decompress(toConsole);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean prompt(Scanner console, String message) {
|
||||
System.out.print(message);
|
||||
return console.nextLine().trim().toLowerCase().startsWith("y");
|
||||
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
91
|
||||
0000000000
|
||||
74
|
||||
0000000001000
|
||||
48
|
||||
0000000001001000
|
||||
52
|
||||
0000000001001001
|
||||
54
|
||||
0000000001001010
|
||||
34
|
||||
00000000010010110
|
||||
38
|
||||
00000000010010111
|
||||
49
|
||||
00000000010011
|
||||
58
|
||||
000000000101
|
||||
86
|
||||
000000000110
|
||||
85
|
||||
000000000111
|
||||
69
|
||||
000000001
|
||||
87
|
||||
00000001
|
||||
76
|
||||
000000100
|
||||
71
|
||||
000000101
|
||||
77
|
||||
000000110
|
||||
83
|
||||
000000111
|
||||
103
|
||||
000001
|
||||
117
|
||||
00001
|
||||
10
|
||||
00010
|
||||
107
|
||||
0001100
|
||||
66
|
||||
000110100
|
||||
89
|
||||
0001101010
|
||||
68
|
||||
0001101011
|
||||
45
|
||||
000110110
|
||||
82
|
||||
0001101110
|
||||
122
|
||||
00011011110
|
||||
41
|
||||
000110111110
|
||||
40
|
||||
000110111111
|
||||
118
|
||||
0001110
|
||||
39
|
||||
0001111
|
||||
97
|
||||
0010
|
||||
100
|
||||
00110
|
||||
99
|
||||
001110
|
||||
102
|
||||
001111
|
||||
111
|
||||
0100
|
||||
116
|
||||
0101
|
||||
119
|
||||
011000
|
||||
65
|
||||
01100100
|
||||
67
|
||||
0110010100
|
||||
120
|
||||
0110010101
|
||||
78
|
||||
0110010110
|
||||
75
|
||||
0110010111
|
||||
33
|
||||
011001100
|
||||
79
|
||||
011001101
|
||||
84
|
||||
01100111
|
||||
108
|
||||
01101
|
||||
44
|
||||
011100
|
||||
121
|
||||
011101
|
||||
46
|
||||
011110
|
||||
98
|
||||
0111110
|
||||
70
|
||||
0111111000
|
||||
106
|
||||
01111110010
|
||||
113
|
||||
01111110011
|
||||
59
|
||||
011111101
|
||||
73
|
||||
01111111
|
||||
32
|
||||
10
|
||||
101
|
||||
1100
|
||||
112
|
||||
1101000
|
||||
72
|
||||
11010010
|
||||
63
|
||||
110100110
|
||||
81
|
||||
11010011100
|
||||
93
|
||||
11010011101
|
||||
80
|
||||
1101001111
|
||||
109
|
||||
110101
|
||||
114
|
||||
11011
|
||||
105
|
||||
11100
|
||||
104
|
||||
11101
|
||||
110
|
||||
11110
|
||||
115
|
||||
11111
|
||||
Binary file not shown.
+4463
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
105
|
||||
0000
|
||||
99
|
||||
0001
|
||||
119
|
||||
001000
|
||||
10
|
||||
0010010
|
||||
109
|
||||
0010011
|
||||
102
|
||||
00101
|
||||
108
|
||||
0011
|
||||
110
|
||||
0100
|
||||
114
|
||||
0101
|
||||
97
|
||||
0110
|
||||
104
|
||||
01110
|
||||
112
|
||||
01111
|
||||
101
|
||||
100
|
||||
115
|
||||
1010
|
||||
116
|
||||
1011
|
||||
111
|
||||
1100
|
||||
100
|
||||
11010
|
||||
117
|
||||
110110
|
||||
121
|
||||
110111
|
||||
32
|
||||
111
|
||||
@@ -0,0 +1,2 @@
|
||||
this is a short input file composed entirely of spaces and lowercase letters
|
||||
and end of line characters to help you test your code
|
||||
@@ -0,0 +1,82 @@
|
||||
110
|
||||
00000
|
||||
109
|
||||
0000100
|
||||
237
|
||||
00001010
|
||||
119
|
||||
00001011
|
||||
249
|
||||
000011
|
||||
105
|
||||
00010
|
||||
227
|
||||
00011
|
||||
99
|
||||
00100
|
||||
108
|
||||
00101
|
||||
236
|
||||
00110
|
||||
238
|
||||
00111
|
||||
233
|
||||
01000
|
||||
97
|
||||
01001
|
||||
114
|
||||
01010
|
||||
242
|
||||
01011
|
||||
229
|
||||
0110
|
||||
101
|
||||
0111
|
||||
225
|
||||
10000
|
||||
104
|
||||
100010
|
||||
112
|
||||
100011
|
||||
240
|
||||
100100
|
||||
247
|
||||
10010100
|
||||
127
|
||||
10010101
|
||||
10
|
||||
10010110
|
||||
13
|
||||
10010111
|
||||
232
|
||||
100110
|
||||
138
|
||||
1001110
|
||||
117
|
||||
1001111
|
||||
115
|
||||
10100
|
||||
244
|
||||
10101
|
||||
100
|
||||
101100
|
||||
228
|
||||
101101
|
||||
116
|
||||
10111
|
||||
243
|
||||
11000
|
||||
239
|
||||
11001
|
||||
111
|
||||
11010
|
||||
121
|
||||
1101100
|
||||
230
|
||||
1101101
|
||||
102
|
||||
1101110
|
||||
245
|
||||
1101111
|
||||
32
|
||||
111
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
ôèéó éó á óèïòô éîðõô æéìå ãïíðïóåä åîôéòåìù ïæ óðáãåó áîä ìï÷åòãáóå ìåôôåòóŠáîä åîä ïæ ìéîå ãèáòáãôåòó ôï èåìð ùïõ ôåóô ùïõò ãïäåŠthis is a short input file composed entirely of spaces and lowercase letters
|
||||
and end of line characters to help you test your code
|
||||
@@ -0,0 +1,8 @@
|
||||
98
|
||||
0
|
||||
99
|
||||
100
|
||||
32
|
||||
101
|
||||
97
|
||||
11
|
||||
@@ -0,0 +1 @@
|
||||
aba ab cabbb
|
||||
@@ -0,0 +1,8 @@
|
||||
97
|
||||
0
|
||||
98
|
||||
10
|
||||
99
|
||||
110
|
||||
32
|
||||
111
|
||||
@@ -0,0 +1 @@
|
||||
�>
|
||||
@@ -0,0 +1 @@
|
||||
ab ab cab
|
||||
@@ -0,0 +1,6 @@
|
||||
97
|
||||
0
|
||||
99
|
||||
10
|
||||
98
|
||||
11
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
cbbaaa
|
||||
@@ -0,0 +1,88 @@
|
||||
// Nik Johnson
|
||||
// TA: Zachary Bi
|
||||
// 4-2-2024
|
||||
|
||||
import java.util.*;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
// book object, which can have a title, authors, and a list of ratings
|
||||
public class Book implements Media {
|
||||
|
||||
private String title;
|
||||
private String author;
|
||||
private List<String> authors;
|
||||
private List<Integer> ratings;
|
||||
|
||||
public Book(String title, String author) {
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public Book(String title, List<String> authors) {
|
||||
this.title = title;
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public List<String> getArtists() {
|
||||
List<String> artists = new ArrayList<>();
|
||||
if (this.author != null) {
|
||||
artists.add(this.author);
|
||||
}
|
||||
|
||||
if (this.authors != null) {
|
||||
for (String author : authors) {
|
||||
artists.add(author);
|
||||
}
|
||||
}
|
||||
return artists;
|
||||
}
|
||||
|
||||
public void addRating(int score) {
|
||||
if (this.ratings == null) {
|
||||
ratings = new ArrayList<>();
|
||||
}
|
||||
this.ratings.add(score);
|
||||
}
|
||||
|
||||
public int getNumRatings() {
|
||||
|
||||
if (this.ratings == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.ratings.size();
|
||||
}
|
||||
|
||||
public double getAverageRating() {
|
||||
if (this.ratings == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
for (int rating : ratings) {
|
||||
sum += rating;
|
||||
}
|
||||
|
||||
return (double)sum / this.ratings.size();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String output = "";
|
||||
|
||||
output += this.title + " by " + this.getArtists() + ": ";
|
||||
|
||||
DecimalFormat df = new DecimalFormat("#." + "0".repeat(2));
|
||||
|
||||
if (ratings != null) {
|
||||
output += df.format(this.getAverageRating()) + " (" + (this.ratings.size()) + " ratings" + ")";
|
||||
return output;
|
||||
}
|
||||
|
||||
return output + "No ratings yet!";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An interface to represent various types of media (movies, books, tv shows, songs, etc.).
|
||||
*/
|
||||
public interface Media {
|
||||
/**
|
||||
* Gets the title of this media.
|
||||
*
|
||||
* @return The title of this media.
|
||||
*/
|
||||
public String getTitle();
|
||||
|
||||
/**
|
||||
* Gets all artists associated with this media.
|
||||
*
|
||||
* @return A list of artists for this media.
|
||||
*/
|
||||
public List<String> getArtists();
|
||||
|
||||
/**
|
||||
* Adds a rating to this media.
|
||||
*
|
||||
* @param score The score for the new rating. Should be non-negative.
|
||||
*/
|
||||
public void addRating(int score);
|
||||
|
||||
/**
|
||||
* Gets the number of times this media has been rated.
|
||||
*
|
||||
* @return The number of ratings for this media.
|
||||
*/
|
||||
public int getNumRatings();
|
||||
|
||||
/**
|
||||
* Gets the average (mean) of all ratings for this media.
|
||||
*
|
||||
* @return The average (mean) of all ratings for this media. If no ratings exist, returns 0.
|
||||
*/
|
||||
public double getAverageRating();
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
// Name: Niklas Johnson
|
||||
// Date: 4-2-2024
|
||||
|
||||
// This class allows users to find and rate books within BOOK_DIRECTORY
|
||||
// containing certain terms. Also handles omitting items if the query is prefixed
|
||||
// with a '-'
|
||||
public class SearchClient {
|
||||
public static final String BOOK_DIRECTORY = "./books";
|
||||
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
Scanner console = new Scanner(System.in);
|
||||
List<Book> books = loadBooks();
|
||||
List<Media> media = new ArrayList<>(books);
|
||||
|
||||
Map<String, Set<Media>> index = createIndex(media);
|
||||
|
||||
System.out.println("Welcome to the CSE 123 Search Engine!");
|
||||
String command = "";
|
||||
while (!command.equalsIgnoreCase("quit")) {
|
||||
System.out.println("What would you like to do? [Search, Rate, Quit]");
|
||||
System.out.print("> ");
|
||||
command = console.nextLine();
|
||||
|
||||
if (command.equalsIgnoreCase("search")) {
|
||||
searchQuery(console, index);
|
||||
} else if (command.equalsIgnoreCase("rate")) {
|
||||
addRating(console, media);
|
||||
} else if (!command.equalsIgnoreCase("quit")) {
|
||||
System.out.println("Invalid command, please try again.");
|
||||
}
|
||||
}
|
||||
System.out.println("See you next time!");
|
||||
}
|
||||
|
||||
// Produces and returns a "deep copy" of the parameter map, which has the same
|
||||
// structure and values as the parameter, but with all internal data structures
|
||||
// and values copied. After calling this method, modifying the parameter or
|
||||
// return value should NOT affect the other.
|
||||
//
|
||||
// Parameters:
|
||||
// inputMap - the map to duplicate
|
||||
//
|
||||
// Returns:
|
||||
// A deep copy of the parameter map.
|
||||
// TODO: Copy fixed version and update to use Media
|
||||
public static Map<String, Set<Media>> deepCopy(Map<String, Set<Media>> inputMap) {
|
||||
Map<String, Set<Media>> deepCopy = new TreeMap<>();
|
||||
|
||||
for (String key : inputMap.keySet()) {
|
||||
Set<Media> inputSet = new HashSet<>(inputMap.get(key));
|
||||
deepCopy.put(key, inputSet);
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
// creates a directory of words that appear in a given list of Strings
|
||||
// takes a list of strings to process
|
||||
// returns sorted map containing the index
|
||||
// no thrown exceptions
|
||||
public static Map<String, Set<Media>> createIndex(List<Media> media) throws FileNotFoundException {
|
||||
Map<String, Set<Media>> index = new TreeMap<>();
|
||||
Set<String> uniqueWords = getUniqueWords(media);
|
||||
|
||||
for (String uniqueWord : uniqueWords) {
|
||||
index.put(uniqueWord.toLowerCase(), new HashSet<Media>());
|
||||
}
|
||||
|
||||
for (String word : index.keySet()) {
|
||||
for (int i = 0; i < media.size(); i++) {
|
||||
Scanner titleScanner = new Scanner(media.get(i).getTitle());
|
||||
while (titleScanner.hasNext()) {
|
||||
if (titleScanner.next().equalsIgnoreCase(word)) {
|
||||
index.get(word).add(media.get(i));
|
||||
}
|
||||
}
|
||||
titleScanner.close();
|
||||
|
||||
for (String artists : media.get(i).getArtists()) {
|
||||
Scanner artistScanner = new Scanner(artists);
|
||||
while (artistScanner.hasNext()) {
|
||||
if (artistScanner.next().equalsIgnoreCase(word)) {
|
||||
index.get(word).add(media.get(i));
|
||||
}
|
||||
}
|
||||
artistScanner.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// helper method for createIndex. looks through given list of strings,
|
||||
// keeping track of only unique words
|
||||
// takes list of words to prune
|
||||
// returns pruned set of words
|
||||
// no thrown exceptions
|
||||
public static Set<String> getUniqueWords(List<Media> media) throws FileNotFoundException {
|
||||
Set<String> uniqueWords = new HashSet<>();
|
||||
for (Media thing : media) {
|
||||
Scanner mediaScanner = new Scanner(new File(BOOK_DIRECTORY + "/" + thing.getTitle() + ".txt"));
|
||||
while (mediaScanner.hasNext()) {
|
||||
uniqueWords.add(mediaScanner.next());
|
||||
}
|
||||
mediaScanner.close();
|
||||
}
|
||||
return uniqueWords;
|
||||
}
|
||||
|
||||
// Allows the user to search a specific query using the provided 'index' to find appropraite
|
||||
// Media entries.
|
||||
//
|
||||
// Parameters:
|
||||
// console - the Scanner to get user input from
|
||||
// index - invertedIndex mapping terms to the Set of media containing those terms
|
||||
public static void searchQuery(Scanner console, Map<String, Set<Media>> index) {
|
||||
System.out.println("Enter query:");
|
||||
System.out.print("> ");
|
||||
String query = console.nextLine();
|
||||
Optional<Set<Media>> result = search(deepCopy(index), query);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
System.out.println("\tNo results!");
|
||||
} else {
|
||||
for (Media m : result.get()) {
|
||||
System.out.println("\t" + m.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allows the user to add a rating to one of the options wthin 'media'
|
||||
//
|
||||
// Parameters:
|
||||
// console - the Scanner to get user input from
|
||||
// media - list of all media options loaded into the search engine
|
||||
public static void addRating(Scanner console, List<Media> media) {
|
||||
System.out.print("[" + media.get(0).getTitle());
|
||||
for (int i = 1; i < media.size(); i++) {
|
||||
System.out.print(", " + media.get(i).getTitle());
|
||||
}
|
||||
System.out.println("]");
|
||||
System.out.println("What would you like to rate (enter index)?");
|
||||
System.out.print("> ");
|
||||
int choice = Integer.parseInt(console.nextLine());
|
||||
if (choice < 0 || choice >= media.size()) {
|
||||
System.out.println("Invalid choice");
|
||||
} else {
|
||||
System.out.println("Rating [" + media.get(choice).getTitle() + "]");
|
||||
System.out.println("What rating would you give?");
|
||||
System.out.print("> ");
|
||||
int rating = Integer.parseInt(console.nextLine());
|
||||
media.get(choice).addRating(rating);
|
||||
}
|
||||
}
|
||||
|
||||
// Searches a specific query using the provided 'index' to find appropraite Media entries.
|
||||
// terms are determined by whitespace separation. If a term is proceeded by '-' any entry
|
||||
// containing that term will be removed from the result.
|
||||
//
|
||||
// Parameters:
|
||||
// index - invertedIndex mapping terms to the Set of media containing those terms
|
||||
// query - user's entered query string to use in searching
|
||||
//
|
||||
// Returns:
|
||||
// An optional set of all Media containing the requirested terms. If none, Optional.Empty()
|
||||
public static Optional<Set<Media>> search(Map<String, Set<Media>> index, String query) {
|
||||
Optional<Set<Media>> ret = Optional.empty();
|
||||
|
||||
Scanner tokens = new Scanner(query);
|
||||
while (tokens.hasNext()) {
|
||||
boolean minus = false;
|
||||
String token = tokens.next().toLowerCase();
|
||||
if (token.startsWith("-")) {
|
||||
minus = true;
|
||||
token = token.substring(1);
|
||||
}
|
||||
|
||||
if (index.containsKey(token)) {
|
||||
if (ret.isEmpty() && !minus) {
|
||||
ret = Optional.of(index.get(token));
|
||||
} else if (!ret.isEmpty() && minus) {
|
||||
ret.get().removeAll(index.get(token));
|
||||
} else if (!ret.isEmpty() && !minus) {
|
||||
ret.get().retainAll(index.get(token));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Loads all books from BOOK_DIRECTORY. Assumes that each book starts with two lines -
|
||||
// "Title: " which is followed by the book's title
|
||||
// "Author: " which is followed by the book's author
|
||||
//
|
||||
// Returns:
|
||||
// A list of all book objects corresponding to the ones located in BOOK_DIRECTORY
|
||||
public static List<Book> loadBooks() throws FileNotFoundException {
|
||||
List<Book> ret = new ArrayList<>();
|
||||
|
||||
File dir = new File(BOOK_DIRECTORY);
|
||||
for (File f : dir.listFiles()) {
|
||||
Scanner sc = new Scanner(f);
|
||||
String title = sc.nextLine().substring("Title: ".length());
|
||||
String author = sc.nextLine().substring("Author: ".length());
|
||||
|
||||
ret.add(new Book(title, author));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user