Lec3
临时测试
创建一个空白方法
public class Sort {
//空白的方法体
public static void sort(String[] x) {
}
}
编写一个测试来检查方法的输出是否有问题 打印出第一个不匹配并终止测试
class TsetSort {
//测试Sort类的sort方法
public static void testSort(){
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "egg", "have", "i"};
Sort.sort(input);
for(int i=0;i<input.length;i++){
if (!input[i].equals(expected[i])){
System.out.println("Mismatch in position " + i + ", expected: " + expected[i] + ", but got: " + input[i] + ".");
break;
}
}
}
public static void main(String[] args) {
testSort();
}
}
得到的输出是
说明编写的测试没问题
JUnit测试
JUnit是一个用于调试程序的软件包 org.junit库提供了许多有用的方法和功能用于简化测试的编写 在编写程序时 可以把代码分成一个个部分 然后用junit来测试每个单元的正确性
将上面的简单临时测试替换为
import org.junit.Test;
public class TsetSort {
//测试Sort类的sort方法
public static void testSort(){
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "egg", "have", "i"};
Sort.sort(input);
org.junit.Assert.assertArrayEquals(expected, input);
}
public static void main(String[] args) {
testSort();
}
}
得到的输出是
选择排序
编写一个选择排序并验证是否正确
选择排序需要三个步骤
- 找出最小的项目
findSmallest - 将其移到前面(交换位置)
swap - 对剩余的n-1项进行排序(递归)
public class Sort {
//把方法组合起来 并使用递归对剩余项进行排序
public static void sort(String[] x){
sort(x, 0);
}
private static void sort(String[] x, int start){
if (start == x.length) {
return;
}
int smallestIndex = findSmallest(x, start);
swap(x, start, smallestIndex);
sort(x, start + 1);
}
//找出最小的项目的索引
public static int findSmallest(String[] x, int start){
int smallestIndex = start;
for(int i = start; i < x.length; i++){
int cmp = x[i].compareTo(x[smallestIndex]);
if(cmp < 0){
smallestIndex = i;
}
}
return smallestIndex;
}
//把最小的项目移到前面
public static void swap(String[] x, int a, int b){
String temp = x[a];
x[a] = x[b];
x[b] = temp;
}
}
验证选择排序也需要三个步骤
- 验证是否找出了最小的项目
testFindSmallest - 验证是否交换位置
testSawp - 验证是否成功排序
import org.junit.Test;
public class TsetSort {
//测试Sort.sort方法
@org.junit.Test
public void testSort(){
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "egg", "have", "i"};
Sort.sort(input);
org.junit.Assert.assertArrayEquals(expected, input);
}
//测试Sort.findSmallest方法
@org.junit.Test
public void testFindSmallest() {
String[] input = {"i", "have", "an", "egg"};
int expected = 2;
int actual = Sort.findSmallest(input, 0);
org.junit.Assert.assertEquals(expected, actual);
String[] input2 = {"there", "are", "many", "pigs"};
int expected2 = 2;
int actual2 = Sort.findSmallest(input2, 2);
org.junit.Assert.assertEquals(expected2, actual2);
}
//测试Sort.swap方法
@org.junit.Test
public void testSwap(){
String[] input = {"i", "have", "an", "egg"};
int a = 0;
int b = 2;
String[] expected = {"an", "have", "i", "egg"};
Sort.swap(input, a, b);
org.junit.Assert.assertArrayEquals(expected, input);
}
}
另外 需要做三件事情
- 在每个测试方法前加上
@org.junit.Test - 把每个测试方法改成非静态
- 删掉TestSort中的main方法
- 另外 可以导入一些语句缩写 比如
import org.junit.Test;后只需要@Test;import static org.junit.Assert.* ;后只需要assertEquals(expected2, actual2);
TDD
驱动测试开发 在编写代码本身之前编写代码测试 步骤如下
- 标识新功能
- 为该功能编写单元测试
- 运行测试 应该是失败的
- 编写那些通过测试的代码
- 重构使代码更快更简洁
Lec4
引用变量表示法
数据有八种基本类型:byte short int long float double bool char 其余都是引用类型
Java与c语言不同 我们无法得知数据的内存地址
当我们声明引用类型时 Java会分配一个64位框来储存内存的地址
我们为引用变量创造一个简化的框表示法
- 如果一个地址全为0 用null表示
- 非零地址将由指向实例化的箭头表示
值传递
参数传递总是复制所有位 称为按值传递
- 基本类型作为参数传递时,传递的是变量的值,在函数内部修改并不影响原变量
- 引用类型作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数,不改变地址的情况下,在函数内部修改会影响原变量
图示如下
Walrus a = new Walrus(1000, 8.3);
Walrus b;
b = a;
列表
比数组更有扩展性
构建一个列表类 其中包括int数值和另一个int列表的指针和地址
public class IntList {
public int first;
public IntList rest;
public IntList(int f, IntList r){
first = f;
rest = r;
}
}
想列出数字5.15.20
从前往后
IntList L = new IntList(5, null);
L.rest = new IntList(10, null);
L.rest.rest = new IntList(15, null);
或者从后往前
IntList L = new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);
编写一个函数 返回列表的长度
使用递归
public int size(){
if(rest == null){
return 1;
}
return 1 + this.rest.size();
}
不使用递归
public int size(){
IntList p = this;
int total = 0;
while(p != null){
total++;
p = p.rest;
}
return total;
}
编写一个函数 返回列表的第i个值
public int get(int i){
if(i == 0){
return first;
}
return rest.get(i - 1);
}
Lec5
SLList 另一种使用列表的方式
现代程序员在使用列表时 往往隐藏递归的结构 而创造一个列表类 丢掉其中的辅助方法 再创造一个用户名类来使用列表并在其中编写辅助方法
public class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n){
item = i;
next = n;
}
}
public class SLList {
public IntNode first;
//构造函数
public SLList(int x){
first = new IntNode(x, null);
}
//空列表的构造函数
public SLList() {
first = null;
size = 0;
}
}
与原列表相比 使用起来更简洁
IntList L1 = new IntList(5, null);
SLList L2 = new SLList(5);
辅助方法
添加首项
public void addFirst(int x){
first = new IntNode(x, first);
}
读取首项
public int getFirst(){
return first.item;
}
添加尾项
public void addLast(int x){
//空列表的情况
if (first == null) {
first = new IntNode(x, null);
return;
}
IntNode p = first;
//移动p直到列表的末尾
while(p.next != null){
p = p.next;
}
p.next = new IntNode(x,null);
}
返回列表的长度
private static int size(IntNode p) {
if (p.next == null) {
return 1;
}
return 1 + size(p.next);
}
与原列表相比 更加易于使用
//SLList
SLList L = new SLList(15);
L.addFirst(10);
L.addFirst(5);
int x = L.getFirst();
//IntList
IntList L = new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);
int x = L.first;
本质上 该类充当列表用户和递归结构的中介
另外 为了防止访问错误 要把SLList类中的IntNode改成private
public class SLList {
private IntNode first;
...
有时也会将IntNode作为静态嵌套类放在SLList中 (如果不使用外部类的任何实例成员 请将嵌套类设置为静态
public class SLList {
public static class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode first;
...
}
缓存
如果通过递归来计算列表大小的话 如果列表太大了 将耗费许多时间 所以可以添加一个属性size 在添加首尾项的时候+1 就可以快速得到列表的大小了 把这种方法称作缓存
public class SLList {
private IntNode first;
private int size;
public SLList(int x){
first = new IntNode(x, null);
size = 1;
}
public void addFirst(int x){
first = new IntNode(x, first);
size++;
}
public int size(){
return size;
}
哨兵节点
为了应对空节点的情况并使代码更整洁 创建一个始终存在在最前面的节点 就叫哨兵节点 值无所谓
哨兵节点有以下几个不变量
sentinel引用始终指向哨兵节点- 前面的项目(如果存在)始终位于
sentinel.next.item size变量始终是已添加的项目总数
public class SLList {
private class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n){
item = i;
next = n;
}
}
private IntNode sentinel;
private int size;
public SLList(){
sentinel = new IntNode(63, null);
size = 0;
}
public SLList(int x){
sentinel = new IntNode(63, null);
sentinel.next = new IntNode(x, null);
size = 1;
}
public void addFirst(int x){
sentinel.next = new IntNode(x, sentinel.next);
size++;
}
public int getFirst(){
return sentinel.next.item;
}
public void addLast(int x){
IntNode p = sentinel;
//移动p直到列表的末尾
while(p.next != null){
p = p.next;
}
p.next = new IntNode(x,null);
size++;
}
public int size(){
return size;
}
public static void main(String[] args) {
SLList L = new SLList();
L.addLast(20);
System.out.println(L.size);
}
}
Lab2
调试指南调试指南 |CS 61B 2021 年春季 (datastructur.es)
lab2主要讲了一些调试的技巧 junit的使用方法 跟着guide一步步做就可以了
Pro0
4*4棋盘的2048 实现Model类中的四个函数
emptySpaceExists maxTileExists atLeastOneMoveExists tilt
棋盘左下角坐标为(0, 0)
emptySpaceExists (Board b)
- 功能:如果棋盘上有空位 则返回true
- 用到
Board类中的tile(int col, int row)和size()方法 分别返回格子Tile(空格子就返回null)和棋盘的规格 - 思路:遍历整个棋盘 有null则返回true
public static boolean emptySpaceExists(Board b) {
// TODO: Fill in this function.
int size = b.size();
for(int col = 0; col < size; col++){
for(int row = 0; row < size; row++){
if(b.tile(col, row) == null){
return true;
}
}
}
maxTileExists(Board b)
- 功能:如果棋盘上有2048数值 则返回true
- 用到
Tile类中的t.value()方法 返回格子中的值 要用MAX_PIECE代替2048 - 思路:遍历整个棋盘 有
t.values() == MAX_PIECE则返回true
public static boolean maxTileExists(Board b) {
// TODO: Fill in this function.
int size = b.size();
for(int col = 0; col < size; col++){
for(int row = 0; row < size; row++){
Tile t = b.tile(col, row);
//只有当t不为空时才检查t.value()
if(t != null && t.value() == MAX_PIECE) {
return true;
}
}
}
return false;
}
atLeastOneMoveExists(Board b)
- 功能:如果棋盘可以进行有效移动 则返回true
- 棋盘上至少有一个空位
- 至少有一对相邻格子具有同样的值
- 用到
int[] x = {0, -1, 0, 1} int[] y = {-1, 0, 1, 0};来移动坐标找到相邻格子 判断格子是否在棋盘内 再判断两个格子的值是否相等 - 思路:先判断有无空格 再遍历整个棋盘找相邻同值
public static boolean atLeastOneMoveExists(Board b) {
// TODO: Fill in this function.
//棋盘上有空位的情况
if(emptySpaceExists(b)){
return true;
}
//有相邻格子值相同的情况
int size = b.size();
int[] x = {0, -1, 0, 1};//左 下 右 上移动坐标
int[] y = {-1, 0, 1, 0};
for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
int value = b.tile(i, j).value();
for(int n = 0; n < 4; n++){
int i2 = i + x[n];
int j2 = j + y[n];
if(i2 < size && j2 < size && i2 >= 0 && j2 >= 0){
int value2 = b.tile(i2, j2).value();
if(value == value2){
return true;
}
}
}
}
}
return false;
}
tilt(Side side)
- 构建2048的核心逻辑
- 两个具有相同值的图块合并为一个图块,其中包含双倍的初始数字。
- 合并结果的图块将不会在该方向上再次合并。例如[X, 2, 2, 4]向左移动应该得到 [4, 4, X, X] 而不是 [8, X, X, X] 这是因为最左边的4已经是合并的一部分,因此不应再次合并。
- 当运动方向上的三个相邻块具有相同的编号时,则运动方向上前两个块合并,而尾随块不合并。 例如[X, 2, 2, 2] 并向左移动图块应该得到 [4, 2, X, X] 而不是 [2, 4, X, X]
- 用到
board.move(int col, int row, tile t);移动格子t到c列r行
board.setViewingPerspective(side);视角转换到side方向(旋转棋盘使side方向为北边)board.setViewingPerspective(side); // put your code here board.setViewingPerspective(Side.NORTH); - 思路:按列遍历棋盘 每列从上往下检查
- 先把非空格子移到前面 空格子移到后面
[x, 2, 2, x] -> [2, 2, x, x] - 再判断能否和相邻格子合并
[2, 2, 2, x] -> [4, x, 2, x] -> [4, 2, x, x]
- 先把非空格子移到前面 空格子移到后面
public boolean tilt(Side side) {
boolean changed;
changed = false;
// TODO: Modify this.board (and perhaps this.score) to account
// for the tilt to the Side SIDE. If the board changed, set the
// changed local variable to true.
board.setViewingPerspective(side);
int size = board.size();
//把非空格子移到前面
for (int col = 0; col < size; col++){
for (int row = size - 1; row >= 0; row--){
Tile t = board.tile(col, row);
if(t != null){
int next = size - 1;
while(row < next){
if(board.tile(col, next) == null){
board.move(col, next, t);
changed = true;
break;
}
next--;
}
}
}
}
//把格子合并
for (int col = 0; col < size; col++){
size = board.size();
for (int row = size - 2; row >= 0; row--){//从第二行开始 向上检查有没有相同项
Tile t = board.tile(col, row);
if(t != null){//本行不为空 则上一行不为空
// 检查上一行是否相同
int next = row + 1;
while(next < size){//不能超出棋盘
Tile nextTile = board.tile(col, next);
if((nextTile != null)&&(t.value() == nextTile.value())){//上一行与本行相同
board.move(col, next, t);
score += t.value() * 2;
changed = true;
size--;//后续棋子移动范围减1
} else if (nextTile == null && board.tile(col, next + 1 ).value() == t.value() && next + 1 < size) {
//上一行为空但上上行与本行相同且不超出移动范围
board.move(col, next + 1, t);
score += t.value() * 2;
changed = true;
size--;//后续棋子移动范围减1
} else if (nextTile == null){//上一行为空且上上行不可移动
board.move(col, next, t);
changed = true;
}
next++;
}
}
}
}
board.setViewingPerspective(Side.NORTH);
checkGameOver();
if (changed) {
setChanged();
}
return changed;
}