黑马程序员—(面试题)—交通灯系统的逻辑实现

来源:互联网 发布:ip反查域名在线工具 编辑:程序博客网 时间:2024/06/06 13:24
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

交通灯系统

一、题目背景及要求

        这是一道来自一位传智播客毕业班学员7000 offer的面试题,据说那家公司正好接到了一个来自法国一个实际开发项目,所以以此类问题给这位同学考试,要求时间三天内上交,此题后经张孝祥老师指导完成。要求模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:

、异步随机生成按照各个路线行驶的车辆。

例如:

由南向而来去往北向的车辆 ---- 直行车辆

由西向而来去往南向的车辆 ---- 右转车辆

由东向而来去往南向的车辆 ---- 左转车辆

。。。

 

2、信号灯忽略黄灯,只考虑红灯和绿灯。

 

3、应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。

 

4、具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。

注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。

 

5、每辆车通过路口时间为秒(提示:可通过线程Sleep的方式模拟)。

 

6、随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。

 

7、不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

二、局部思路和代码的实现

       所谓万事开头难,当我们的代码知识相对贫瘠的时候,一个整套系统是很难入手的东西,我们需要一些切入点去进行思考,咱们首先可以先忽略一些与具体编码无关的现实因素思考,因为这里首先可能会接触到很多之前没学过的技术和知识,而关于对整个系统俯视性的统筹和优化我们可以往后放,这并不是初学者上来就应该去做的,我们需要一些东西来支撑我们的继续思考的动力。因为这并不是我们的面试题,所以从解决这个问题的时间上来说,我们是相对宽裕的。

        我们可以先将其中的重要部分进行面向对象的分析,这套系统都包含什么内容?个别部分的答案可能不难想到:各个方向的交通灯,各个方向的路线,各个方向的车,它们之间运行的逻辑也同样属于常识:红灯停,绿灯行。当然这些只是我们第一时间能想到的,实际的运行逻辑要复杂一些,但是我们可以先不考虑更多,我们尽量去从我们相对薄弱的代码知识中寻找可以关联的熟悉的记忆。

        这里我们可以认为公路是一个集合,而不断的增加删减的元素,则是其中的车辆,而在简易的交通灯系统中,车辆除了在这个公路上穿梭以外,就不具备什么特有属性了,尽管实际上判断信号灯的是车辆,但是如果把车辆作为一个类的话,无疑会让我们的代码复杂很多。所以在这里车辆就不用作为一个我们编辑出来的类的对象存在了。而集合可以接收的参数类型,我们可以简单的定义为String类对象。而它的需要定时删减,而删减的条件是需要经过对灯的颜色和前面是否有车来判断的。如果我们忽略灯的作用,那么就剩下前面是否有车的判断了,那不就是生产消费者的模型了么?在这里我们似乎找到一些信心。而在一般的生产者消费者模式中,往往用改变布尔标定来作为生产消费线程的等待与唤醒的条件,而如果我们把目光集中在路口的那一辆车的话,就是来一辆走一辆的节奏了,那么就可以通过判断集合是否为空来判断是否进车和出车了。

       而灯是一个可以映射出布尔类型属性的实例对象,因为忽略黄灯的原因,使我们不用更多类的特性来反应的颜色,涉及到整个系统,这大大减少了我们的开发难度,然后它需要定时间变绿变红。

       现在咱们可以做一个小样出来了,一条路一个灯一堆车的简单模型来帮助我们建立信心,让我们先从在已经熟悉的生产消费者模型的基础上加入一个灯类的这种相对简单的挑战开始:

import java.util.*;import java.util.concurrent.*;import java.util.concurrent.locks.*;class Road{//公路类private List<String> road=new ArrayList<String>();//建立集合用来装车private int count=;//车的计数器private ReentrantLock lock = new ReentrantLock();//用来同步公路类的锁Condition conditionadd = lock.newCondition(); //用来控制添加车辆线程Condition conditionremove = lock.newCondition(); //用来控制减少线程private Lamp lamp;Road(Lamp lamp){this.lamp=lamp;}public void addcar(){//写添加车辆方法lock.lock();//同步锁try {Thread.sleep(1000);//每秒驶入辆车while(road.size()!=0)//如果集合内有元素,代表着路口有车conditionadd.await();//则此线程等待road.add("car"+count++);//没车则增加车System.out.println(road.get(0)+"驶入路口");//输出车号conditionremove.signal();//唤醒驶出路口线程} catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();//无论线程等待还是被执行了,都要解锁}}public void removecar(){//驶出路口lock.lock();//上锁try {Thread.sleep(1000);//每隔秒驶出路口一辆车while(road.size()==0)//假如路上没车,驶出路口线程等待conditionremove.await();if(lamp.getLamped()){//被唤醒后,判断灯的颜色System.out.println(road.remove(0)+"驶出路口");//删除集合元素的同时打印此元素conditionadd.signal();//唤醒驶入车线程}} catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();//开锁}}}class Removecar implements Runnable{//同步接口的移车实现类Road r;Removecar(Road r){//在构造方法中增加引用好确保使用同一个对象this.r=r;}public void run() {for(int i=0;i<100;i++){//添加100辆车r.addcar();}}}class Addcar implements Runnable{//实现了同步接口的添加车辆类Road r;Addcar(Road r){this.r=r;}public void run() {for(int i=0;i<100;i++){r.removecar();//减少100辆车}}}class Lamp{private boolean lamped=true;//初始化灯的状态ReentrantLock lock=new ReentrantLock();//同步锁public void green(){//变绿灯方法this.lamped=true;}public void red(){//变红灯方法this.lamped=false;}public  boolean getLamped(){return this.lamped;}public void setlamped(){//变灯方法lock.lock();if(lamped){//假如绿灯则变红灯this.red();System.out.println("红灯啦");}else{//假如红灯则变绿灯this.green();System.out.println("绿灯啦");}lock.unlock();}}class SetLamp implements Runnable{//控制灯改变的同步类private Lamp lamp;//增加灯类引用SetLamp(Lamp lamp){this.lamp=lamp;}public void run() {lamp.setlamped();//执行改变灯色方法}}public class ThreadDemo{public static void main(String[] args) {Lamp lamp=new Lamp();Road road=new Road(lamp);//建立共有资源Addcar writer=new Addcar(road);//引用共有资源Removecar reader=new Removecar(road);//引用共有资源ScheduledExecutorService pool=Executors.newScheduledThreadPool(1);//构建一个线程池,里面一个线程pool.scheduleAtFixedRate(new SetLamp(lamp), 10, 10, TimeUnit.SECONDS);//每10秒执行变灯方法一次,执行0次new Thread(writer).start();//执行驶入方法new Thread(reader).start();//执行驶出方法}}

       代码的结构比较粗糙,主要原因是因为是笔者自己写的,放在这里是为了后面的抛砖引玉用的。这里我使用了一个新Executors类,因为灯的类是后加的,而为了让代码稍微的简单点儿,所以灯类中关于线程的处理没有沿用公路类中的一些相对的原始的方法。

       大家在看完后,是否心里有了些底了呢?因为上面涉及的新知识不算多,只是代码比较繁冗,但是我们至少看到了完成它可能性,用简单粗暴的说法,我们可以如法炮制弄出实际中的12条路线,12个交通灯来,然后再解决12交通灯变灯规律问题。系统就差不多完成了。但是如果你这么写,别说7000的offer估计3000的都拿不到。那么接下来咱们结合着张老师的整体解决方案来分析下代码优化问题。

 

三、产生整个交通系统

       如果照整个路口12个互相关联的交通灯去设计的话,灯类肯定是要大改特改的,而路的改动倒没那么大了。而灯类的控制方法就是最主要的重点:首先如果一个方向只有一条车道的前提下,通过东南西北四个方向,再通过方向的两两组合,咱们是可以排列组合出12个方向的。(从4个元素取出两个元素进行排列4!/(4-2))而其中4个方向为右转方向,所以不受交通灯的控制,还有8个而它们的方向两两相对的,所以按一定顺序不停改变交通灯状态的是4组灯,而在改变它们要同时改变对面的等,这里注意一点,左转灯的对面不是右转灯。。比如从东向南路线的灯的面对方向可不是东向南啊。。

        下面通过张老师给的图看看顺序和交通灯的组合:


        其中2同to,而同一种颜色和粗细的线条的路线互为相反方向,成对出现,而线上所标的数字,则代表着它们的改变状态的顺序,以这个顺序依次变绿然后变红,而4个右转灯咱们可以设定为常绿。

下面我们来看看代码:

package com.isoftstone.interview.traffic;/** * 每个Lamp元素代表一个方向上的灯,总共有12个方向,所有总共有12个Lamp元素。 * 有如下一些方向上的灯,每两个形成一组,一组灯同时变绿或变红,所以, * 程序代码只需要控制每组灯中的一个灯即可: * s2n,n2s     * s2w,n2e * e2w,w2e * e2s,w2n * s2e,n2w * e2n,w2s * 上面最后两行的灯是虚拟的,由于从南向东和从西向北、以及它们的对应方向不受红绿灯的控制, * 所以,可以假想它们总是绿灯。 * @author 张孝祥 www.it315.org * *//**/public enum Lamp {/*每个枚举元素各表示一个方向的控制灯*/S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),/*下面元素表示与上面的元素的相反方向的灯,它们的“相反方向灯”和“下一个灯”应忽略不计!*/N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),/*由南向东和由西向北等右拐弯的灯不受红绿灯的控制,所以,可以假想它们总是绿灯*/S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);private Lamp(String opposite,String next,boolean lighted){this.opposite = opposite;this.next = next;this.lighted = lighted;}/*当前灯是否为绿*/private boolean lighted;/*与当前灯同时为绿的对应方向*/private String opposite;/*当前灯变红时下一个变绿的灯*/private String next;public boolean isLighted(){return lighted;}/** * 某个灯变绿时,它对应方向的灯也要变绿 */public void light(){this.lighted = true;if(opposite != null){Lamp.valueOf(opposite).light();}System.out.println(name() + " lamp is green,下面总共应该有6个方向能看到汽车穿过!");}/** * 某个灯变红时,对应方向的灯也要变红,并且下一个方向的灯要变绿 * @return 下一个要变绿的灯 */public Lamp blackOut(){this.lighted = false;if(opposite != null){Lamp.valueOf(opposite).blackOut();}Lamp nextLamp= null;if(next != null){nextLamp = Lamp.valueOf(next);System.out.println("绿灯从" + name() + "-------->切换为" + next);nextLamp.light();}return nextLamp;}}

       首先我们可以从包名可以看出来专业的性,它是根据软通国际的域名和面试的内容来写的,这无疑是可以博得用人单位好感的一种表示。接下来我们可以看出来它在其中的属性,有灯的状态,此灯对面的灯(需要同步红绿),此灯的下一个灯,而后面两个看似应该是交通灯类型的属性却采用了String类型,因为如果采用了Lamp类型则无法在枚举的构造方法中去列出了,这里存在着后建立先引用的问题,而枚举类又存在着神奇的valueOf方法,可以返回带指定名称的指定枚举类型的枚举常量。而执行灯色变化方法大概逻辑是这样的,首先是绿灯方法,当前灯绿的同时,变绿对面的灯,而红灯方法则是变红当前的灯和对面的灯的同时让下一个顺位的灯执行变绿方法(也就是说下一组等会同时变绿)。

        而在执行灯颜色的改变之前,我们还加入了对面的灯和下一个灯在构造方法的位置上的为非空的判断,综上所述比我之前写的只是单纯的改变灯的状态的方法要复杂多了。

下面我们来看关于灯的控制类:

package com.isoftstone.interview.traffic;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class LampController {private Lamp currentLamp;public LampController(){//刚开始让由南向北的灯变绿;currentLamp = Lamp.S2N;currentLamp.light();/*每隔10秒将当前绿灯变为红灯,并让下一个方向的灯变绿*/ScheduledExecutorService timer =  Executors.newScheduledThreadPool(1);timer.scheduleAtFixedRate(new Runnable(){public  void run(){System.out.println("来啊");currentLamp = currentLamp.blackOut();}},10,10,TimeUnit.SECONDS);}}

        这个类用到的Executors类型在上一个我写的例子中已经讲过,不同的用法是源自它用的是一个匿名内部类,我那时候受到的灯类引用的掣肘,所以写到外面去了,所以现在看起来还是相对简单的。

下面看看关于张老师写的比较优化的公路代码:

package com.isoftstone.interview.traffic;import java.util.ArrayList;import java.util.List;import java.util.Random;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;/** * 每个Road对象代表一条路线,总共有12条路线,即系统中总共要产生12个Road实例对象。 * 每条路线上随机增加新的车辆,增加到一个集合中保存。 * 每条路线每隔一秒都会检查控制本路线的灯是否为绿,是则将本路线保存车的集合中的第一辆车移除,即表示车穿过了路口。 * @author 张孝祥 www.it315.org * */public class Road {private List<String> vechicles = new ArrayList<String>();private String name =null;public Road(String name){this.name = name;//模拟车辆不断随机上路的过程ExecutorService pool = Executors.newSingleThreadExecutor();pool.execute(new Runnable(){public void run(){for(int i=1;i<1000;i++){try {Thread.sleep((new Random().nextInt(10) + 1) * 1000);} catch (InterruptedException e) {e.printStackTrace();}vechicles.add(Road.this.name + "_" + i);}}});//每隔一秒检查对应的灯是否为绿,是则放行一辆车ScheduledExecutorService timer =  Executors.newScheduledThreadPool(1);timer.scheduleAtFixedRate(new Runnable(){public void run(){if(vechicles.size()>0){boolean lighted = Lamp.valueOf(Road.this.name).isLighted();if(lighted){System.out.println(vechicles.remove(0) + " is traversing !");}}}},1,1,TimeUnit.SECONDS);}}

        在这段代码中,我们用Executors.newSingleThreadExecutor();方法创建了一个线程,然后执行了ScheduledThreadPoolExecutor中的execute方法来启动线程,由于添加车的方法是一次写进去的,所以不需要循环执行,而添加车的时间改为了随机数,让代码结果更加合理。而接下来的减少车辆的线程的建立于交通灯是大同小异的,都是采用的定时运行的线程池类型,这里不同于交通灯的10秒后开始运行,这里1秒后就开始试图减少车辆了,每一个减车的循环是间隔1秒的,而张老师这里没有用集合非空作为判断依据,主要是因为它不像笔者把路口作为对象,而是更大范围内的适用,在这里张老师优化结果所体现的大局观就可见一斑了。

下面就开始运行了:

package com.isoftstone.interview.traffic;public class MainClass {/** * @param args */public static void main(String[] args) {/*产生12个方向的路线*/String [] directions = new String[]{"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"};for(int i=0;i<directions.length;i++){new Road(directions[i]);}/*产生整个交通灯系统*/new LampController();}}

        这里没有太多好说的,之前已经把改运行的都写进构造函数里了,所以这部分的代码就比较简单了。
        我感觉大家在看完我写的小例之后就会发现:其实这道题真正的难点就是在于交通灯的设计,而公路就是生产消费者的模型,我们准确的设计好面向对象,写好方法,然后我们将其封装至线程中,开启线程就好了。不过说来容易,做起来还是需要我们练就更好本事才行。
        不知道大家现在是否还会对这个传说中7000offer的面试题感到傍徨,希望我的文章能够给你们带来一些信心,在这条寂寞的路上,与君共勉。

---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------详细请查看:www.itheima.com
0 0
原创粉丝点击