init
This commit is contained in:
41
Bomb.java
Normal file
41
Bomb.java
Normal file
@@ -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"); }
|
||||
}
|
||||
}
|
||||
21
c1/.vscode/launch.json
vendored
Normal file
21
c1/.vscode/launch.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
47
c1/AbstractStrategyGame.java
Normal file
47
c1/AbstractStrategyGame.java
Normal file
@@ -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);
|
||||
}
|
||||
36
c1/Client.java
Normal file
36
c1/Client.java
Normal file
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
117
c1/ConnectFour.java
Normal file
117
c1/ConnectFour.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
53
c1/Testing.java
Normal file
53
c1/Testing.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
132
c1/TicTacToe.java
Normal file
132
c1/TicTacToe.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
83
c2/divide_canvas/Client.java
Normal file
83
c2/divide_canvas/Client.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
505
c2/divide_canvas/Picture.java
Normal file
505
c2/divide_canvas/Picture.java
Normal file
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
c2/divide_canvas/Point.java
Normal file
10
c2/divide_canvas/Point.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
27
c2/fill/Client.java
Normal file
27
c2/fill/Client.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
505
c2/fill/Picture.java
Normal file
505
c2/fill/Picture.java
Normal file
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
c2/fill/white.jpg
Normal file
BIN
c2/fill/white.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
35
c2/mondrian/Client.java
Normal file
35
c2/mondrian/Client.java
Normal file
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
97
c2/mondrian/Mondrian.java
Normal file
97
c2/mondrian/Mondrian.java
Normal file
@@ -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)];
|
||||
}
|
||||
}
|
||||
|
||||
505
c2/mondrian/Picture.java
Normal file
505
c2/mondrian/Picture.java
Normal file
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
c2/mondrian/basic.png
Normal file
BIN
c2/mondrian/basic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
2
c2/mondrian/compile.sh
Executable file
2
c2/mondrian/compile.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
javac *.java && java Client && rm *.class
|
||||
91
c3/Client.java
Normal file
91
c3/Client.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
170
c3/QuizTree.java
Normal file
170
c3/QuizTree.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
c3/colors-cereals.txt
Normal file
9
c3/colors-cereals.txt
Normal file
@@ -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
|
||||
3
c3/compile.sh
Executable file
3
c3/compile.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
javac *.java && java Client ; rm *.class
|
||||
BIN
ciphers/4as0ufZh9PpgbDEj0M0prBKp
Normal file
BIN
ciphers/4as0ufZh9PpgbDEj0M0prBKp
Normal file
Binary file not shown.
49
ciphers/Cipher.java
Normal file
49
ciphers/Cipher.java
Normal file
@@ -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);
|
||||
}
|
||||
45
ciphers/Client.java
Normal file
45
ciphers/Client.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
11
ciphers/SubstitutionRandom.java
Normal file
11
ciphers/SubstitutionRandom.java
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
19
ciphers/Testing.java
Normal file
19
ciphers/Testing.java
Normal file
@@ -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!");
|
||||
}
|
||||
}
|
||||
1
ciphers/Transposition.java
Normal file
1
ciphers/Transposition.java
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Write your implementation to Transposition here!
|
||||
1
ciphers/Vigenere.java
Normal file
1
ciphers/Vigenere.java
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Write your implementation to Vigenere here!
|
||||
1
ciphers/files/ag.txt
Normal file
1
ciphers/files/ag.txt
Normal file
@@ -0,0 +1 @@
|
||||
HADBADCABBAGEBAG
|
||||
4463
ciphers/files/hamlet.txt
Normal file
4463
ciphers/files/hamlet.txt
Normal file
File diff suppressed because it is too large
Load Diff
15
ciphers/files/simple.txt
Normal file
15
ciphers/files/simple.txt
Normal file
@@ -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
|
||||
BIN
ciphers/lib/junit-platform-console-standalone-1.10.2.jar
Normal file
BIN
ciphers/lib/junit-platform-console-standalone-1.10.2.jar
Normal file
Binary file not shown.
BIN
debugging/2XIGPFWWQh6rdNJQSyU34zis
Normal file
BIN
debugging/2XIGPFWWQh6rdNJQSyU34zis
Normal file
Binary file not shown.
71
debugging/Book.java
Normal file
71
debugging/Book.java
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
54
debugging/Debugging.java
Normal file
54
debugging/Debugging.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
52
debugging/InvertedIndex.java
Normal file
52
debugging/InvertedIndex.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
41
debugging/Media.java
Normal file
41
debugging/Media.java
Normal file
@@ -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();
|
||||
}
|
||||
40
debugging/Testing.java
Normal file
40
debugging/Testing.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
BIN
in_class/cse123-inclass11/.DS_Store
vendored
Normal file
BIN
in_class/cse123-inclass11/.DS_Store
vendored
Normal file
Binary file not shown.
79
in_class/cse123-inclass11/ScrabbleHelper/Scrabble.java
Normal file
79
in_class/cse123-inclass11/ScrabbleHelper/Scrabble.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
178691
in_class/cse123-inclass11/ScrabbleHelper/dictionary.txt
Normal file
178691
in_class/cse123-inclass11/ScrabbleHelper/dictionary.txt
Normal file
File diff suppressed because it is too large
Load Diff
67
in_class/cse123-inclass11/ScrabbleHelperv2/Scrabble.java
Normal file
67
in_class/cse123-inclass11/ScrabbleHelperv2/Scrabble.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
178691
in_class/cse123-inclass11/ScrabbleHelperv2/dictionary.txt
Normal file
178691
in_class/cse123-inclass11/ScrabbleHelperv2/dictionary.txt
Normal file
File diff suppressed because it is too large
Load Diff
25
in_class/cse123-inclass9/IsPalindromeArray.java
Normal file
25
in_class/cse123-inclass9/IsPalindromeArray.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
in_class/cse123-inclass9/IsPalindromeString.java
Normal file
19
in_class/cse123-inclass9/IsPalindromeString.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
38
in_class/cse123-inclass9/RecursiveMystery.java
Normal file
38
in_class/cse123-inclass9/RecursiveMystery.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
22
in_class/cse123-inclass9/ReverseString.java
Normal file
22
in_class/cse123-inclass9/ReverseString.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
91
p1/Client.java
Normal file
91
p1/Client.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
148
p1/ExampleTesting.java
Normal file
148
p1/ExampleTesting.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
241
p1/Repository.java
Normal file
241
p1/Repository.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
p1/Testing.java
Normal file
99
p1/Testing.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
128
p2/P2_GeneratePermutations/Allocation.java
Normal file
128
p2/P2_GeneratePermutations/Allocation.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
73
p2/P2_GeneratePermutations/Client.java
Normal file
73
p2/P2_GeneratePermutations/Client.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
84
p2/P2_GeneratePermutations/Region.java
Normal file
84
p2/P2_GeneratePermutations/Region.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
100
p3/BitInputStream.java
Normal file
100
p3/BitInputStream.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
74
p3/BitOutputStream.java
Normal file
74
p3/BitOutputStream.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
117
p3/HuffmanCode.java
Normal file
117
p3/HuffmanCode.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
p3/HuffmanCode.java.bak
Normal file
80
p3/HuffmanCode.java.bak
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
209
p3/HuffmanCompressor.java
Normal file
209
p3/HuffmanCompressor.java
Normal file
@@ -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
p3/hamlet.code
Normal file
140
p3/hamlet.code
Normal file
@@ -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
|
||||
BIN
p3/hamlet.short
Normal file
BIN
p3/hamlet.short
Normal file
Binary file not shown.
4463
p3/hamlet.txt
Normal file
4463
p3/hamlet.txt
Normal file
File diff suppressed because it is too large
Load Diff
40
p3/short.code
Normal file
40
p3/short.code
Normal file
@@ -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
|
||||
2
p3/short.txt
Normal file
2
p3/short.txt
Normal file
@@ -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
|
||||
82
p3/short256.code
Normal file
82
p3/short256.code
Normal file
@@ -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
|
||||
BIN
p3/short256.short
Normal file
BIN
p3/short256.short
Normal file
Binary file not shown.
2
p3/short256.txt
Normal file
2
p3/short256.txt
Normal file
@@ -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
|
||||
8
p3/simple-spec-example.code
Normal file
8
p3/simple-spec-example.code
Normal file
@@ -0,0 +1,8 @@
|
||||
98
|
||||
0
|
||||
99
|
||||
100
|
||||
32
|
||||
101
|
||||
97
|
||||
11
|
||||
1
p3/simple-spec-example.txt
Normal file
1
p3/simple-spec-example.txt
Normal file
@@ -0,0 +1 @@
|
||||
aba ab cabbb
|
||||
8
p3/taxi.code
Normal file
8
p3/taxi.code
Normal file
@@ -0,0 +1,8 @@
|
||||
97
|
||||
0
|
||||
98
|
||||
10
|
||||
99
|
||||
110
|
||||
32
|
||||
111
|
||||
1
p3/taxi.short
Normal file
1
p3/taxi.short
Normal file
@@ -0,0 +1 @@
|
||||
<06>>
|
||||
1
p3/taxi.txt
Normal file
1
p3/taxi.txt
Normal file
@@ -0,0 +1 @@
|
||||
ab ab cab
|
||||
6
p3/tiny.code
Normal file
6
p3/tiny.code
Normal file
@@ -0,0 +1,6 @@
|
||||
97
|
||||
0
|
||||
99
|
||||
10
|
||||
98
|
||||
11
|
||||
BIN
p3/tiny.short
Normal file
BIN
p3/tiny.short
Normal file
Binary file not shown.
1
p3/tiny.txt
Normal file
1
p3/tiny.txt
Normal file
@@ -0,0 +1 @@
|
||||
cbbaaa
|
||||
88
search-engine/Book.java
Normal file
88
search-engine/Book.java
Normal file
@@ -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!";
|
||||
}
|
||||
}
|
||||
|
||||
41
search-engine/Media.java
Normal file
41
search-engine/Media.java
Normal file
@@ -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();
|
||||
}
|
||||
213
search-engine/SearchClient.java
Normal file
213
search-engine/SearchClient.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
BIN
search-engine/X5j9Zv1uw5KI5rEyO4djmOx4
Normal file
BIN
search-engine/X5j9Zv1uw5KI5rEyO4djmOx4
Normal file
Binary file not shown.
5168
search-engine/books/A Doll's House : a play.txt
Normal file
5168
search-engine/books/A Doll's House : a play.txt
Normal file
File diff suppressed because it is too large
Load Diff
3406
search-engine/books/Alice's Adventures in Wonderland.txt
Normal file
3406
search-engine/books/Alice's Adventures in Wonderland.txt
Normal file
File diff suppressed because it is too large
Load Diff
15470
search-engine/books/Dracula.txt
Normal file
15470
search-engine/books/Dracula.txt
Normal file
File diff suppressed because it is too large
Load Diff
7246
search-engine/books/Frankenstein; Or, The Modern Prometheus.txt
Normal file
7246
search-engine/books/Frankenstein; Or, The Modern Prometheus.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5270
search-engine/books/Romeo and Juliet.txt
Normal file
5270
search-engine/books/Romeo and Juliet.txt
Normal file
File diff suppressed because it is too large
Load Diff
6404
search-engine/books/The Great Gatsby.txt
Normal file
6404
search-engine/books/The Great Gatsby.txt
Normal file
File diff suppressed because it is too large
Load Diff
8527
search-engine/books/The Picture of Dorian Gray.txt
Normal file
8527
search-engine/books/The Picture of Dorian Gray.txt
Normal file
File diff suppressed because it is too large
Load Diff
6370
search-engine/books/The War of the Worlds.txt
Normal file
6370
search-engine/books/The War of the Worlds.txt
Normal file
File diff suppressed because it is too large
Load Diff
7494
search-engine/books/Treasure Island.txt
Normal file
7494
search-engine/books/Treasure Island.txt
Normal file
File diff suppressed because it is too large
Load Diff
3981
search-engine/books/Winnie-the-Pooh.txt
Normal file
3981
search-engine/books/Winnie-the-Pooh.txt
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user