个人主页变更

从2016年年初起,这个架设在ifLab服务器上的博客不再更新,个人博客地址改为: http://zhrmoe.com/ .

回看自己在大一时写的博客,有的显得十分青涩,甚至还有很多文章出现了现在看来非常低级的错误。不过作为我成长道路的记录,思考再三,决定暂时保留博客现有的内容。

希望读者在新的博客阅读愉快。

无法识别的Selector发送给实例的快速排错法

How to solve

Unrecognized selector sent to instance…应该是iOS开发中比较常见的一种问题,但是一般这种错误的报错会指向main.m,一时间很难找到是哪个对象发生了问题。这时,我们可以下一个Debug断点。

在Xcode的菜单栏中选择Debug -> Breakpoints -> Create Symbolic Breakpoint…,在弹出的标签的Symbol栏填入

-[NSObject(NSObject) doesNotRecognizeSelector:]

这时再次运行就会发现真正出现问题的地方了。

Example

今天在给WebView的一个属性赋值的时候一直报错。如果初始化WebView时不涉及这个属性的话,则不会出现问题。虽然我已经定位了问题的所在,但是如何解决依然没有头绪。添加断点后立刻发现,给这个属性传入的对象的某个属性出现了循环引用,改成weak后解决了问题。

ZHRMoe Studio, 2016.

输出流的缓冲区

Intro

这段时间,不断听到一年级的萌新们在问,fflush是什么函数。为什么有了它就可以正常地输入输出,反而就会少些什么。

首先需要说明的是,在C语言中,不应该依赖fflush函数解决I/O问题。因为它只在某些编译器上有效。很多比较主流的编译器,比如gcc,就并不支持这个这个函数。它是对C语言标准的扩充,虽然在C99中有定义,但不对所有编译器有效。

我们从文档中查到的这个函数的声明方法是int fflush(FILE *stream);——这里我们可以认为这个int类型的返回值其实是担当了bool类型的作用,用来判断这个操作是否成功。

具体的执行情况是:如果参数是输出流(output stream),fflush函数会把还没有输出的数据都传给这个流指向的文件。举例:fflush(stdout);。否则,fflush函数的行为即为未定义行为,如果发生了写入相关的错误,返回值是EOF(-1)。

下面,我们从C++的角度研究一些和缓冲区有关的问题。

Output Buffer

尝试用代码向输出流添加一条输出指令,如:os << "Set output to output buffer.";。执行的效果可能是立刻就被打印了出来,也有可能没什么反应。这里我们只是给输出流发送指令,而并不是常规意义上的cout,使得情况可能有所不同。假想以下情况:操作系统需要同时处理一系列输出流的输出到设备操作,而设备的写操作可能非常耗时(比如打印机等输出时需要耗时的操作),操作系统每接到一次输出流的指令就做一次操作显然浪费了系统资源和操作时间。于是,操作系统把这些指令暂时放在输出缓冲区(output buffer)里,接收到缓冲刷新的指令后,统一将多个类似的操作指令组合成单一的系统级写操作。这样,性能得到了极大的提升。

刷新缓冲的原因可能有:

  1. main函数的return操作让程序正常地结束
  2. 缓冲区满,如果不刷新缓存则其他的指令不能进入缓冲区
  3. 利用操纵符显式刷新缓冲区(比如熟悉的std::endl
  4. 利用操纵符设置流的状态并清空缓冲区(比如unitbuf,如果对这个东西不熟悉,那么更熟悉一些的cerr的内容都是默认设置 unitbuf 的)
  5. 一个输出流被关联到另一个流(比如cincerr都关联到cout,因此这两种操作都会使得cout的缓冲区刷新)

下面介绍几个实例:

cout << "Output with a newline, refresh buffer." << endl;

cout << "Output, refresh buffer." << flush;

cout << "Output with a blank, refresh buffer." << ends;

当然,每次输出都和缓冲区打交道是很累的。这时,我们可以借用unitbuf指令,告诉系统之后的所有的输出都自带flush操作,直到我们给某次输出nounitbuf,比如:

cout << unitbuf; //After this, every output will refresh buffer.

cout << nounitbuf; //Back to usual status.

Worthy to Say

很多情况下,萌新们辛辛苦苦写了很多代码却见不到输出。一方面可能是确实没有输出内容,还有可能是程序崩溃导致这些输出被滞留在缓冲区里面等待打印了。所以,调试的时候必须保证输出缓冲区的数据确实被刷新了,否则就会一直纠结于为什么没有输出结果这个问题上(我个人建议先写出一份可行的版本,也就是编译和运行都没有问题的,之后添加复杂的功能以满足需求)。

One More Thing

在交互较多的情况下,cin之前必须有cout信息输出到设备,这时我们可以把输入流关联到输出流,让每次输入之前都刷新输出缓冲,示例:

cin >> ival;

ZHRMoe Studio, 2015.

ifLab·iOS组2015秋季招新问卷-阶乘位数问题题解及拓展思考

【题目】

iOS组有一个异于其他组的习惯。每年进入组内的同学都将获得一个编码,但是为了凸显iOS组的高大上属性,编码并不是顺序的,而是需要通过一个特别的计算方法得到的,描述如下。

  1. 先由同学自己说出一个喜欢的数,要求大于50(即不会太小)。
  2. 计算由这个同学制定的数的阶乘,将末尾0的个数记录下来。
  3. 编码的长度为8位,结果不足8位的需要用0在前面补齐。

例如制定的数是1024,其阶乘末尾有253个0,则这名同学的编号为00000253。

需要解决的问题:

  1. 这个编码方式在实际操作的时候出现了两名同学虽然指定了不同的数却得到了相同的编号的情况,请简述原因。
  2. 请描述计算编号的解决方案。(你可以在伪代码、高级语言或自然语言中任选,能表示清楚思路即可)

在面试过程当中,我尝试询问遇到这道题的面试者在看题之后的思路。在预定情况下,这道题是为有NOIP经验的同学准备的。对于这类同学,这道题的难度并不大,但是思路决定代码的复杂程度。可惜大部分面试者对这道题的考虑只停留在如何求得阶乘以及对阶乘的处理上,这样的解法甚至不能完成样例中1024这个数据。

【解题思路】

可以肯定的是,对于1024这样的数,这个阶乘已经很大了,再去处理一个这么庞大的数的尾数问题,从时间和空间上都不划算。所以,我们可以转而思考这个数末尾的0是怎么来的。

1! = 1

2! = 2

3! = 6

4! = 24

5! = 120

  1. 末尾的0和阶乘中能够构造出的10的个数相关。
  2. 10是由2和5构造出来的。
  3. 2的个数应该远多于5的个数,所以只需要讨论5的个数。

由此,我们把这个问题转化成了在n之前的数分解质因数后有多少个5的问题。

接下来,更进一步地思考可以发现,25分解之后可以得到2个5。在这个问题中还需要同时考虑5的n次方及其倍数包含多个5。

下面可以分为两种思路:

  1. 从1循环到n,对每个数分解质因数,求出5的个数。(必须指出这种算法比算阶乘再处理的时间复杂度高,但是空间复杂度大大降低了,至少针对大数是可以算的)
  2. 每5个数出现一个5,每25个数出现2个5……如果我们已经每5个数筛一次的情况下,25个数也只出现1个5了,这时候我们可以选择不断地整除5的n次方,并把结果相加。
【示例代码】


 C++ Code 
#include <stdio.h>



int main(int argc, const char * argv[]) {

    
int right = 5, sum = 0, n;

    scanf(
"%d", &n);

    
while(right <= n) {

        sum += n / right;

        right *= 
5;

    }

    printf(
"%08d\n", sum);

    
return 0;

}

【思路拓展】

因为之前的算法一下子把一个数字减小了将近5倍,我们研究了一种新的计算方法。

依然由同学指定一个数,这一次我们先对其求阶乘,然后把10进制数变为2进制,最后求这个2进制数从末尾数有多少个连续的0,并编为编号。

乍一看这道题比刚才的题目更加复杂了,不仅涉及阶乘和末尾0的问题,还发生了一次进制转换。但是如果你对进制有一定的了解的话,就会发现,这个问题的本质是探求n之前的数分解质因数有多少个2。

让我来详细地说明一下吧:在二进制的条件下,末尾的0代表2的n次方没有出现,即存在更高位的数。这时,从末尾看的第一个1其实就是其位数所在的权。(如果不好想象可以尝试完成几个数的二进制和十进制互换,这时你将会对这里提到的内容有更深的理解)

具体代码留给读者,和上面的示例代码大同小异。

需要提示读者的是,在面对和数学有关的问题时,一般都不应该暴力模拟,而是尝试从数学本质理解问题,找到好的解决方案。

Swift中引入警告的方法

Objective-C引入警告

在OC中,如果我们需要引入警告的时候,一般会写作如下的形式:

 Objective-C Code 
    NSInteger a = 1;

#warning This is a warning.

    NSString *str = @
"This is a string.";


这时,Xcode在编译的时候会在对应行上加上警告,这对于提醒程序员哪里还需要进行后续操作或者注意事项非常有帮助。不过,Swift中暂时还没有官方的方法加入这种警告(据说苹果已经准备引入了),但是我们可以通过骗编译器的方式写警告信息。

Swift引入警告

以下方法来自Stack Overflow中的一篇回答,地址:http://stackoverflow.com/questions/24183812/swift-warning-equivalent

原文的引用信息:

To envoke this functionality with Swift in Xcode today however, you could do the following as outlined by Ben Dodson & Jeffrey Sambells.

首先在项目设置中选择Project Settings -> Build Phases -> "+" -> New Run Script Phase

然后填写如下的代码:

 Srcroot Code 
TAGS=“Warning:"

echo "searching ${SRCROOT} for ${TAGS}"

find 
"${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"


此时,每当你在代码中写以Warning:开头的注释时,系统都会自动加上警告。样例如下:

 Swift Code 
    var a: Int = 1

//Warning: This is a warning.

    var str: String = "This is a string."


如果你想改成TODO:或是其他字符,可以修改代码中TAGS后的值。


ZHRMoe Studio, 2015.

简述Java实验设计的实践过程

本来,这次实验应该写一个实验报告,描述在实验过程当中遇到的一些问题和解决方案。不过最近有很多同学都在纠结这次实验应该从哪下手,我从一个Java新手的角度帮大家理清一下思路,就当做是实验报告了。(注:由于Java水平有限,下文中的很多习惯用词可能来自Objective-C语言。)

我的设计模式灵感来源于著名的MVC模式,有兴趣的同学可以百度一下。

具体代码可以参见GitHub

一、基本结构

说是学生管理系统,就应该可以预见到,主界面应该是一个有管理功能的表格,按照常理,我们可以添加/修改/删除学生的信息。同时,一个功能完备的系统一定不是随意进入的,所以我们还需要登录模块。登录模块一般包括登陆/注册/忘记密码三部分,这三部分的注册和忘记密码在样式上基本相同,都是用户名占一行,密码和确认密码占两行。

构思好基本的界面之后,我们很有必要把它画下来,方便我们在写代码的时候理清层次。

二、数据模块
1.学生

数据模块的搭建是贯穿整个项目的。第一次的搭建很可能和项目完成的时候相差甚远。我一开始搭建数据模块的时候,自以为方法已经给的尽可能的全,但是标记第一个release的时候,和最初相比多了一倍的代码量。这里,我们强调的是代码在实现某些功能的时候,需要根据需求不断地更新基础需求。

由于我们在管理的是学生的信息,所以一开始需要写学生类。学生类应该包含一些简单的属性,你也可以根据需要在后期添加,同时写好所有属性的get/set方法,一定要注意类型。

到这里,看看表,过去了2分钟。在这2分钟里,你完成了这个项目的第一步,同时也是最关键的一步——学生类都搭建好了,管理学生还不简单吗?

但是,如果你管理的只是三四个学生,就像导师带研究生一样,你还可以简单地实例化三四个学生对象就可以了,非常可惜的是,学生管理经常出现大数据——如果你是班主任,你要管五六十个学生,你要是辅导员,就得管几百个学生。一个个实例化显然是不现实的。所以,我们可以选择构建一个线性链表管理学生。

阅读笔记:线性链表是一种具有连接存储结构的线性表。相邻的元素在逻辑和存储地址上可能都并不是相邻的。简单地说,它就是线性结构的“链表”。

读懂上面的内容,就相当于为你在数据结构的学习上做了一点点预习。当然,数据结构并不是这么简单的,线性结构(linear structure)是其中不那么抽象的一部分。考虑到我们管理学生的时候一般并不考虑学生之间的关系,只是在序上有所区别,按序排列,所以学生可以添加到一个线性列表里。

2.学生列表

现在,整个项目最复杂的类之一即将开始——你的好友【学生列表】上线了。

学生列表是在项目中的主体功能中最重要的一环。幸好,Java在表面上并没有C语言那么复杂的指针,线性链表其实只是一个泛型的数组。如果你的记忆力还好,就会想起实验三中有一个重要的类叫ArrayList,就是你了。

学生列表的类里,方法会有很多。比如添加/修改/删除某个学生,甚至是某几个学生。获取学生的列表,查找列表当中的某个学生等等。需要注意的是,一定要为每个方法都取一个容易识别的名字,名字不怕长,只怕你以后再也看不懂了。比如添加学生的方法,添加某个学生你可能会写成addStudent(ZHRMoeStudent),那么添加很多学生应该取什么名字呢?好的方法是,添加一个学生的时候,方法名就说好添加一个学生,例如addSingleStudent(ZHRMoeStudent)。(这里的ZHRMoeStudent只是我假设的学生类的名字)

当你用正常的方法声明学生列表的实例时,依然会像上次一样收到一个提示,泛型的具体类型并没有声明。这次,我们不再使用屏蔽的措施,而是正面地解决它。我们可以用如下的方式声明一个学生列表实例:
public ArrayList<ZHRMoeStudent> studentList = new ArrayList<ZHRMoeStudent>();

如果你只用了3分钟就写好了这个类,我首先要恭喜你有一个飞快的手速,但同时,我也建议你再仔细地想想有没有什么可以添加的方法,免得以后添加的时候打乱编程思路。

3.用户

登陆的时候,你想判断用户名密码是否正确,应该去找谁呢?答案就是User类。所谓的用户其实就是用这个用户系统的人,他们的属性就是用户名、密码。写好他们对应的get/set方法,看看表,2分钟。

4.用户列表

注册?忘记密码?如果你想更改用户的信息,要是只实例化了几个用户的对象,肯定是说不过去的。这样的话,我们就应该再给用户建立一个列表。方法和学生的列表类似,都是删除/添加等等。这个过程大概需要5分钟的时间

5.小结

理论上讲,数据模块的搭建只需要15分钟。虽然你之后肯定还需要再雕琢这一部分的方法,而且这种修改一定是贯穿整个项目周期始终的。但是,迈出了这重要的第一步,意义是非常重大的。还需要额外说明的是,学生列表和用户列表的两个列表属性一定是静态的,不要问我静静是谁。

三、登陆模块
1.登陆界面

终于可以画界面了!

为了让你可以更加自由地描绘界面结构,我只提供一些简单的思路。

在画界面之前,一定先在哪里画出这个界面的具体样子,这对你了解这个界面的结构和排列有非常大的帮助。然后,分区域把对应的元素规划成JPanel类的对象。

由于,登陆界面这个类是一个图形界面,所以它应该继承于JFrame

需要注意的是,密码框并不是简单的JTextField,而是JPasswordField,这种框当中的文字都会(在表面上)加密成点。

可能用到的界面元素类:

  • 区块 JPanel
  • 文字 JLabel
  • 按钮 JButton
  • 输入框 JTextField
  • 密码框 JPasswordField

同时,界面上的按钮在点击的时候应该做出反应,这时候,我们应该为每一个按钮都添加事件监听,监听的是这个界面本身。所以相应的语句应该是someButton.addActionListener(this)

然后就报错了。

因为,这个界面本身并不是一个动作的监听者,它只是一个界面而已。所以,我们让它实现ActionListener这个接口。相应的类定义是:

public class ZHRMoeLogin extends JFrame implements ActionListener {
    //这个界面的代码
}

报错变成了提示,这个提示告诉我们,接口的方法我们还没有实现。所以接下来,我们要给这些按钮实现动作事件。

public void actionPerformed(ActionEvent a) {
    if(a.getSource() == someButton) {
        //写关于这个的按钮按下时的代码
    } else if(a.getSource() == anotherButton) {
        //写关于另一个按钮按下时的代码
    }
}

需要解释的是,ActionEvent是一个动作事件,其实也就是点击了这个界面的某个元素,根据点击不同的按钮,我们可以做出不同的反应。

另一个问题是,用户名和密码需要和之前的某个具体的用户相同,这里我不详细介绍对照的方法,不过搜索一定是一个不错的选择。另外,用户名和密码为空的时候,登陆应该被阻止(一开始,我故意留下了这个漏洞方便其他界面的检查,所以是否在一开始就解决这个问题,依情况而定)。这其中的逻辑,你们可以用离散数学的分析方法做分析(顿时就高大上了)。

2.注册界面

注册需要对用户的列表进行添加操作,这里就要看你给用户列表写的方法是否详尽了。

同时,注册新用户的时候,需要输入两遍密码防止用户手残。两次密码需要对照一致才能注册,而用户名不可以是已经注册了的用户名,也要做相应的检查。这其中的逻辑,也应该细致地考虑。

3.忘记密码界面

忘记密码界面其实就是修改密码的界面。这个界面的结构和注册界面大体相似,但是不同在于,只有用户名已经存在的时候才能更改密码(即逻辑与注册界面相反)。而用户列表内的操作,可以选择先删除原来的用户,然后把用户名和新的密码重新生成一个用户加入列表当中。

四、主界面
1.主界面概述

为了让我们写出的主界面美观,代码应该下一番功夫认真排版。

主要的数据都填在了一张JTable的对象(也就是一张表)里,这张表由于需要滚动,所以要加入JScrollPane,即滚动的模块当中。

JTable数据的获取,需要通过setModel方法设置。

studentListTable.setModel(new TableModel() {
            @Override
            public int getRowCount() {
                return ...;
                //这里需要返回列表的行数
            }

            @Override
            public int getColumnCount() {
                return ...;
                //这里需要返回列表的列数
            }

            @Override
            public String getColumnName(int columnIndex) {
                return new String[]{"", "", ...}[columnIndex];
                //这里需要返回列标题的序列
            }

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return String.class;
                //这里需要返回类型
            }

            @Override
            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return true;
                //这里返回的是列表的内容是否可以修改,这里设置为是,就可以完成学生信息的修改了
            }

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                ZHRMoeStudent s = studentArray.getSingleStudent(rowIndex);
                return new Object[]{...}[columnIndex];
                //这里返回学生信息,填满列表 需要填写get方法
            }

            @Override
            public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                ZHRMoeStudent s = studentArray.getSingleStudent(rowIndex);
                switch (columnIndex) {
                    case 0:
                        ... //填写第0列的set方法,以下依此类推
                        break;
                    case 1:
                        ...
                        break;
                    case 2:
                        ...
                        break;
                    case 3:
                        ...
                        break;
                    case 4:
                        ...
                        break;
                    case 5:
                        ...
                    default:
                        break;
                }
                //修改列表元素
            }

            @Override
            public void addTableModelListener(TableModelListener l) {
                //重写方法
            }

            @Override
            public void removeTableModelListener(TableModelListener l) {
                //重写方法
            }
        });

通过这些代码,列表就能够得到数据了。

2.添加学生界面

添加学生的界面应该是可以获取学生的所有属性的。注意,有些属性例如“性别”,让用户去输入显然是可能出现“手残”的情况的。为了避免这种笑话的发生,我们可以添加两个JRadioButton分别表示男女,然后把两个选项放进一个ButtonGroup里,使得这两个选项只能单选。

这两个JRadioButton也应该是动作的监听者,用来设置性别的男女。

3.总结

图形界面是不是不好画啊,是不是要写好多东西啊?

可是,用代码写界面的确是这么复杂的,但通过一些字符就可以绘画出一个有意义的界面出来,的确非常有成就感。

四、项目维护

总的来说,到这里我们的项目中的基本功能已经实现了。但是很有可能出现某些不可预料的奇怪情况,bug。遇到bug不要着急,这是提升我们能力的好机会。好在这个项目的数据规模并不复杂,bug的原因经常显而易见。如果真的遇到了不可解决的bug,那么在实现了所有基本功能的前提下,舍弃一些极难实现的功能,也是情有可原的。否则,高端的程序员存在的意义是什么呢?

在项目过程当中遇到问题,可以找我,也可以找其他在Java方面更有研究的同学解决,希望大家可以深刻地理解设计的步骤,对未来设计更大的项目有所帮助。

五、源代码

最新Pre-Release V1.1 代码下载

*Warning:本项目源代码公开,但是项目组暂时不同意代码开源使用,使用前请和我们取得联系。

2015.4.17 C2613次列车上 一稿

2015.4.19 C2076次列车上 二稿

ZHRMoe Studio, 2015.

结合实验浅谈ArrayList用法

描述

ArrayListJava中的一种动态数组,相关的问题出现在Java实验三第5题(选做)。

改写程序清单10-6中的Course类。使用ArrayList代替数组来储存学生。不应该改变Course类的原始合约(即构造方法和方法的定义都不应该改变)。

程序清单10-6如下所示:

public class Course {

    private String courseName;
    private String[] students = new String[100];
    private int numberOfStudents;

    public Course(String courseName) {
        this.courseName = courseName;
    }

    public void addStudent(String student) {
        students[numberOfStudents] = student;
        numberOfStudents++;
    }

    public String[] getStudents() {
        return students;
    }

    public int getNumberOfStudents() {
        return numberOfStudents;
    }

    public String getCourseName() {
        return courseName;
    }

    public void dropStudent(String student) {
        //Left as an exercise in Exercise 10.9
    }
}
分析

既然需要用到ArrayList类,我们就要考察一下这个类当中有哪些需要我们关注的方法。首先我们需要初始化一个动态数组students,即构造方法。在后面的addStudent(String)dropStudent(String)两个方法中,我们分别需要在动态数组中添加和删去指定的元素。

打开ArrayList.java文件可以寻找我们需要的方法。

  • 构造

ArrayList类提供了3种构造方法,分别是:

  1. ArrayList(int initialCapacity)
  2. ArrayList()
  3. ArrayList(Collection<? extends E> c)

如果你看不懂第三个,暂时没有关系,一会你会对它有所了解的。

  • 添加

ArrayList类提供了4种添加方法,但是其中2种是布尔型的判断,只有2种可以添加元素:

  1. add(int index, E element)
  2. add(E e)

传入的是一个整形值和一个任意类型的元素或者只有一个元素(关于E类型你可以暂时这样理解)。

  • 删去

ArrayList类提供了4种删去方法,但是和添加方法一样,只有2种可以删去元素:

  1. remove(int index)
  2. remove(Object o)

同上,传入一个任意类型的元素或者是角标号。

代码及解释

根据题目要求,我们将使用第一种构造方法。新的Course类代码如下:

public class Course {

    private String courseName;
    private ArrayList students = new ArrayList(100);
    private int numberOfStudents;

    public Course(String courseName) {
        this.courseName = courseName;
    }

    public void addStudent(String student) {
        students.add(numberOfStudents, student);
        numberOfStudents++;
    }

    public ArrayList getStudents() {
        return students;
    }

    public int getNumberOfStudents() {
        return numberOfStudents;
    }

    public String getCourseName() {
        return courseName;
    }

    public void dropStudent(String student) {
        students.remove(student);
        numberOfStudents--;
    }

}

可以看到,由于添加和删除方法的限制,我们使用的都是传入元素的添加删除方法。

不过,在某些IDE当中,这些代码可能会带来一条Warning:

Unchecked call to add(int, E) as a member of raw type 'java.util.ArrayList'.

根据字面意思可以知道,是泛型类的赋值需要检测的原因(你也可以认为是类型可能会发生不匹配)。但是作为一个程序员(强迫症),一定要把Warning扼杀在摇篮里。你可以在类的声明之前添加一行代码去掉这条提示:

@SuppressWarnings("unchecked")

测试方法的代码如下:

public class CourseTest {

    public static void main(String[] args) {
        Course course = new Course("Java");
        for (int i = 1; i <= 10; ++i) {
            course.addStudent(String.format("Student%d", i));
        }
        System.out.println("Step 1:We initialized 10 students into the ArrayList.");
        System.out.println("Course Name: " + course.getCourseName() + "\nNumber of students: " + course.getNumberOfStudents() + "\nStudents List: " + course.getStudents());
        for (int i = 1; i <= 5; ++i) {
            course.dropStudent(String.format("Student%d",i));
        }
        System.out.println("\nStep 2:We removed 5 students from the ArrayList.");
        System.out.println("Course Name: " + course.getCourseName() + "\nNumber of students: " + course.getNumberOfStudents() + "\nStudents List: " + course.getStudents());
    }
}

这其中需要解释的是,String.formatString类的自带方法,意思是按照某种格式生成字符串,由于我加入了循环,生成的学生名就是Student1到Student10了。

吐槽:OC在写这个方法的时候就比较复杂了,大概是:

[NSString stringWithFormat:@"blabla"];

总结

这道题作为选作还是很有必要的,我大概看文件就要花了半个小时的时间,确实是个很考验能力的题目呢。

ZHRMoe Studio 4/10/2015.

【HDOJ-2552】三足鼎立——数学不是白学的

描述

MCA山中人才辈出,洞悉外界战火纷纷,山中各路豪杰决定出山拯救百姓于水火,曾以题数扫全场的威士忌,曾经高数九十九的天外来客,曾以一剑铸十年的亦纷菲,歃血为盟,盘踞全国各个要塞(简称全国赛)遇敌杀敌,遇佛杀佛,终于击退辽军,暂时平定外患,三人位置也处于稳态。

可惜辽誓不甘心,辽国征南大将军<耶律javac++>欲找出三人所在逐个击破,现在他发现威士忌的位置s,天外来客的位置u,不过很难探查到亦纷菲v所在何处,只能知道三人满足关系:

arctan(1/s)=arctan(1/u)+arctan(1/v)

定义 f(s,u,v)=v*u-s*u-s*v 的值为”三足鼎立”

<耶律javac++>想计算<三足鼎立>的值

输入

首先输入一个t,表示有t组数据,跟着t行:

输入s,u (s<=12^3;u<=2^20;s,u,v>0),s,u,v均为实数

输出

输出 v*u-s*u-s*v 的值,为了简单起见,如果是小数,直接取整

比如:答案是1.7,则输出1

分析

第一感觉,这题一定是个纯模拟,反正arctan是可以算的。

不过,仔细想过之后发现,这个题反而是用arctan好好的骗了我们一把——当你看到f(s,u,v) = v*u-s*u-s*v的时候是不是反映到了tan的运算法则呢?(没关系请尽情吐槽,我的数感一直都很可怕)

所以,我觉得这是一道数学题。

运算

中等数学告诉我们

tan(a+b)=(tan(a)+tan(b))/(1-tan(a)*tan(b)) (1)

tan(arctan(a))=a (2)

有了以上两个基本准则,我们针对arctan(1/s) = arctan(1/u)+arctan(1/v)进行推导:

tan(arctan(1/s))=tan(arctan(1/u)+arctan(1/v))

1/s=(1/u+1/v)/(1-1/u*v)

1/s-1/s*u*v=1/u+1/v

v*u-s*u-s*v=1

所以,我们居然推导出了这道题的结果直接为1!数感拯救世界!

代码


 C++ Code 
#include <stdio.h>



int main(int argc, const char * argv[]) {

    
int n, a, b;

    scanf(
"%d", &n);

    
while(n--) {

        scanf(
"%d %d", &a, &b);

        printf(
"1\n");

    }

    
return 0;

}

总结

数感拯救世界。假如还需要说什么别的,我觉得中等数学不是白学的。(所以我的高数简直是差劲的要死……)

ZHRMoe Studio 4/9/2015

ParseInt方法的实现

描述

这个问题来自1到4班Java第二次实验第2题,周围有好多同学一看到需求比较迷茫,所以写了这个短文(卖萌)。

  • 静态方法parseInt(char[])将数字字符构成的数组转换为一个int值。
  • 静态方法parseInt(String)将字符串转换为一个int值。
分析

先说Stringint,我们不能把一个String分开来单独处理每一位(而char[]就能轻易地做到),所以,需要调用String的方法toCharArray()处理。

那么对于一个char[]来说,我们需要逆序读取这个线性列表的每一个元素,然后考虑位上的权值,一股脑地加进需要求得的总和。

例如'12345',是由1 * 10000 + 2 * 1000 + 3 * 100 + 4 * 10 + 5 * 1组成的。考虑到这里,我们需要两个计数器solve求得结果以及right计算权值。

另外,由于是倒序处理,我们还要知道数组的长度也就是array.length,但是如果你用这个数直接循环,会得到栈溢出的错误。(错误提示如下)

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: **
at MyInteger.parseInt(MyInteger.java:**)
at MyInteger.main(MyInteger.java:**)

这是因为,角标都是从0数的嘛,所以你要用长度减1。(高手请忽略我上面几行的卖ke萌pu)

代码及解释

所以,parseInt(char[])的实现如下:

 Java Code 
public static int parseInt(char[] array) {

    
int right = 1;

    
int solve = 0;

    
for (int i = array.length - 1; i >= 0; --i) {

        solve = solve + right * ((
int)array[i] - 48);

        right *= 
10;

    }

    
return solve;

}


你可能注意到,(int)array[i]减了48,至于这是为什么,你可以把减48去掉,然后输出结果查看一下。

parseInt(String)的实现,可以用已经写好的上面的方法偷懒:

 Java Code 
public static int parseInt(String str) {

    
char[] array = str.toCharArray();

    
return parseInt(array);

}

总结

其实我从来没有写过这么科普向的解题报告,所以Java的实验真的很水,很水……

ZHRMoe Studio 4/8/2015.