Pair programming - Wang & Hong Yu Zheng

[Pair programming operations - Tianjin subway lines]

First, the task:

Assist in the realization of a subway travel route planning command-line program.

Second, the design information

  • Development of language: JAVA
  • Algorithm: Dijkstra
  • Functional design framework

Third, the analysis and implementation requirements

  • Requirement 1

  • When the program starts, automatically get information to the map

  Need to implement a program automatically loads subway.txt support files, need to obtain a corresponding custom file by reading the metro program startup parameter -map (designated subway.txt), to obtain information subway map. An example of a calling application is as follows:

java subway -map subway.txt

 

  • Requirement 2

  • The query specifies subway line through the site

    On the application, you need to support a new command-line argument -a, the user wants to specify the subway line query.

    At a given subway line, the program needs to start from the starting line of the site, all sites sequentially output through the metro line until the terminus. The output file using the -oarguments. An example of a calling application is as follows:

    java subway -a 1号线 -map subway.txt -o station.txt

    Station.txt contents of the file under the actual output  

    复制代码

    地铁:1号线
    刘园
    西横堤
    果酒厂
    本溪路
    勤俭道
    洪湖里
    西站
    西北角
    西南角
    二纬路
    海光寺
    鞍山道
    营口道
    小白楼
    下瓦房
    南楼
    土城
    陈塘庄
    复兴门
    华山里
    财经大学
    双林
    李楼

    复制代码

  • Demand 3

  • Calculate the shortest route from the departure point to the destination station between the site and outputs the number of paths and

If you want to take the subway, he hopes to pass the minimum number of stations to reach the destination from the starting point, so that you can -b to the command line parameters plus two subway stations are named as the departure and destination, such as the user wants to know Honghuli the shortest route between Fuxing Road, is how he can use the following command to let the program will result in a write routine.txt.

subway.exe -b 洪湖里 复兴路 -map subway.txt -o routine.txt

The program calculates the shortest distance between the departure to the destination station (after the minimum number of sites) route, and outputs the number of passes and the path to the site (including the start and the destination site). If desired transfer, the transfer line is output in the next line of the transfer station. The output file using the -oarguments.

An example of a calling application is as follows:

java subway -b 华苑 乐园道 -map subway.txt -o routine.txt

Routine.txt contents of the file under the actual output  

复制代码

起始站:华苑
3号线
王顶堤
红旗南路
6号线
迎风道
南翠屏
水上公园东路
肿瘤医院
天津宾馆
文化中心
乐园道

共经过 10 站

复制代码

Fourth, the logic implementation

  • Data preprocessing

  •   Subway line data recorded in subway.txt file presented in the following format:

复制代码

1:1号线:刘园,西横堤,果酒厂,本溪路,勤俭道,洪湖里,西站,西北角,西南角,二纬路,海光寺,鞍山道,营口道,小白楼,下瓦房,南楼,土城,陈塘庄,复兴门,华山里,财经大学,双林,李楼
2:2号线:曹庄,卞兴,芥园西道,咸阳路,长虹公园,广开四马路,西南角,鼓楼,东南角,建国道,天津站,远洋国际中心,顺驰桥,靖江路,翠阜新村,屿东城,登州路,国山路,空港经济区,滨海国际机场
3:3号线:小淀,丰产河,华北集团,天士力,宜兴埠,张兴庄,铁东路,北站,中山路,金狮桥,天津站,津湾广场,和平路,营口道,西康路,吴家窑,天塔,周邓纪念馆,红旗南路,王顶堤,华苑,大学城,高新区,学府工业区,杨伍庄,南站
4:5号线:北辰科技园北,丹河北道,北辰道,职业大学,淮河道,辽河北道,宜兴埠北,张兴庄,志成路,思源道,建昌道,金钟河大桥,月牙河,幸福公园,靖江路,成林道,津塘路,直沽,下瓦房,西南楼,文化中心,天津宾馆,肿瘤医院,体育中心,凌宾路,昌凌路,中医一附院,李七庄南
5:6号线:南孙庄,南何庄,大毕庄,金钟街,徐庄子,金钟河大桥,民权门,北宁公园,北站,新开河,外院附中,天泰路,北运河,北竹林,西站,复兴路,人民医院,长虹公园,宜宾道,鞍山西道,天拖,一中心医院,红旗南路,迎风道,南翠屏,水上公园东路,肿瘤医院,天津宾馆,文化中心,乐园道,尖山路,黑牛城道,梅江道,左江道,梅江公园,梅江会展中心,解放南路,洞庭路,梅林路
6:9号线:天津站,大王庄,十一经路,直沽,东兴路,中山门,一号桥,二号桥,张贵庄,新立,东丽开发区,小东庄,军粮城,钢管公司,胡家园,塘沽,泰达,市民广场,太湖路,会展中心,东海路

复制代码

  • After reading the data file, is first ":" as the separator, the data of each row is divided into three parts, the first part of the label for each subway, subway second part of each name, a third portion to site information for each of the subway.
  • 程序将识别所有的转乘站点,作为一个结点,这样可以避免在计算最短路径过程中,将所有站点的距离都计算一遍。并且,由于北京地铁的特殊性,有些地铁线路为环状线路,我们需要把成环的线路检测出来,并且以map的形式记录其编号和线路总长度在这里。
  • 为了方便后续的路径计算,我们以距离为模拟将每个站点计算出其编号,公式为(地铁线路标号*1000 + 当条地铁线第几个站点 +1)。若该地铁为环状地铁线路,则将 其编号乘以(-1),以便在计算最短路径时候可以知道这是一条环线,并采用不同的最短计算方式。

  • 数据加载预处理的方法函数如下:

  public void loadLineFile()

// 加载地铁线路数据
    public void loadLineFile(String strSubwayFileName) {
        File fSubway = new File(strSubwayFileName);
        BufferedReader reader = null;

        try {
            InputStreamReader isr = new InputStreamReader(new FileInputStream(strSubwayFileName), "UTF-8");
            reader = new BufferedReader(isr);
            String tempString = null;

            while ((tempString = reader.readLine()) != null) {
                if (tempString.startsWith("\uFEFF")) {
                    tempString = tempString.substring(1, tempString.length());
                }
                listSubwayInfo.addElement(tempString);
            }
            System.out.println("成功加载地铁线路文件!\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        parseSubwayStationsData();
    }

  void parseSubwayStationsData()

  void parseSubwayLineData()

 // 地铁数据处理
    void parseSubwayStationsData() {
        for (String strSubwayLine: listSubwayInfo) {
            parseSubwayLineData(strSubwayLine);
        }
    }

    void parseSubwayLineData(String strSubwayLine) {

        String[] arrLineAndStations = strSubwayLine.split(":");     // 划分地铁线路和该线路所有站点
        if (arrLineAndStations.length != 3) {
            System.out.println("地铁数据错误" + strSubwayLine);
            return;
        }
        
        int nLine = getLineNumber(arrLineAndStations[0]);
        stationInfo.put(nLine,arrLineAndStations[1]);
        NtostationInfo.put(arrLineAndStations[1],nLine);
        if (nLine == -1) {
            System.out.println("地铁线路号数据错误" + strSubwayLine);
        }
        String[] arrStrStationNames = arrLineAndStations[2].split(",");
        if(arrStrStationNames[0].equals(arrStrStationNames[arrStrStationNames.length-1]))
        {
            
            for (int i=0; i < arrStrStationNames.length-1; i++) {
                
                String strStationName = arrStrStationNames[i];
                int nStationId = -(nLine*1000 + i+1);
                circleInfo.put(nLine,arrStrStationNames.length-1 );
                Station station = new Station();
                station.stationName = strStationName;
                station.setStationId.add(nStationId);
                mapStationIdtoStation.put(nStationId, station);
                if (!mapNametoStation.containsKey(strStationName)) {
                    mapNametoStation.put(strStationName, station);
                } else {
                    // 如果站点名字存在,证明是中转站
                    Station stationExistedTransferStation = mapNametoStation.get(strStationName);
                    stationExistedTransferStation.setStationId.add(nStationId);
                    mapTransferStationNametoDistance.put(stationExistedTransferStation.stationName, nMaxDistance);
                }
            }
        }
        for (int i=0; i < arrStrStationNames.length; i++) {
            String strStationName = arrStrStationNames[i];
            int nStationId = nLine*1000 + i+1;
            Station station = new Station();
            station.stationName = strStationName;
            station.setStationId.add(nStationId);
            mapStationIdtoStation.put(nStationId, station);
            if (!mapNametoStation.containsKey(strStationName)) {
                mapNametoStation.put(strStationName, station);
            } else {
                // 如果站点名字存在,证明是中转站
                Station stationExistedTransferStation = mapNametoStation.get(strStationName);
                stationExistedTransferStation.setStationId.add(nStationId);
                mapTransferStationNametoDistance.put(stationExistedTransferStation.stationName, nMaxDistance);
            }
        }
    }

迪杰斯特拉算法求最短路径

  •   操作步骤

  1. 初始时,S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为”起点s到该顶点的距离”[例如,U中顶点v的距离为(s,v)的长度,然后s和v不相邻,则v的距离为∞]。
  2. 从U中选出”距离最短的顶点k”,并将顶点k加入到S中;同时,从U中移除顶点k。
  3. 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(s,v)的距离可能大于(s,k)+(k,v)的距离。
  4. 重复步骤(2)和(3),直到遍历完所有顶点。
  • 同时,在计算最短路径的同时,要保存每个结点最后被更新前的上一个结点,这样可以方便我们后续输出其完整路径。

以下为迪杰斯特拉算法计算最短路径的方法函数:

  Path Dijkstra()

   // 最优路径规划
    Path Dijkstra(String strStartStationName, String strEndStationName) {
        // todo: 进行一些合法性检查
        Station stationStart = mapNametoStation.get(strStartStationName);
        Station stationEnd = mapNametoStation.get(strEndStationName);
        if(stationStart==null || stationEnd==null)
        {
            System.out.println("起始站或终点输入不正确,请检查输入数据!");
            return null;
        }
        mapTransferStationNametoDistance.put(strEndStationName, nMaxDistance);
        mapTransferStationNametoDistance.put(strStartStationName, nMaxDistance);
        Path pathStart = new Path();
        pathStart.nFDistance = 0;
        pathStart.stationLastStationInPath = stationStart;
        Path Dijkstra = new Path();
        Dijkstra.nFDistance = nMaxDistance;

        Stack<Path> stackAllPaths = new Stack<>();
        stackAllPaths.push(pathStart);

        Set<String> TStationNameSet = new TreeSet<>();
        for(String strname: mapTransferStationNametoDistance.keySet()) {
              TStationNameSet.add(strname);           
                   }
        for(String strname: mapTransferStationNametoDistance.keySet()) {
              finalMap.put(strname,"null");
                   }
        while (!stackAllPaths.empty()) {
            Path pathCurrent = stackAllPaths.pop();
            if (pathCurrent.nFDistance > Dijkstra.nFDistance) {
                continue;
            }
            int nBDistance = getStationsDistance(pathCurrent.stationLastStationInPath, stationEnd);
            if (nBDistance == 0) {      // 到达终止节点
                if (pathCurrent.nFDistance < Dijkstra.nFDistance) {
                    Dijkstra = pathCurrent;
                }
                continue;
            }
            int minDistance = 1000000;
            String nextStation = null;    
            TStationNameSet.remove(pathCurrent.stationLastStationInPath.stationName);
            for (String strTStationName: mapTransferStationNametoDistance.keySet()) {
                Station stationTransfer = mapNametoStation.get(strTStationName);
                int nDistanceDelta = getStationsDistance(pathCurrent.stationLastStationInPath, stationTransfer);
                int nTStationDistance = pathCurrent.nFDistance + nDistanceDelta;
                if (nTStationDistance >= mapTransferStationNametoDistance.get(strTStationName)) {
                    continue;
                }
                finalMap.put(strTStationName,pathCurrent.stationLastStationInPath.stationName);
                mapTransferStationNametoDistance.put(strTStationName, nTStationDistance);      
            }
            for(String strTStationName: mapTransferStationNametoDistance.keySet()) {
                int Distance = mapTransferStationNametoDistance.get(strTStationName);
                if(Distance<minDistance&& TStationNameSet.contains(strTStationName)) {
                minDistance = Distance;
                nextStation = strTStationName;              
                }
            }
            Station stationTransfer = mapNametoStation.get(nextStation);
            Path pathNew = new Path();
            pathNew.nFDistance = minDistance;
            pathNew.stationLastStationInPath = stationTransfer;
            stackAllPaths.push(pathNew);
        }
        System.out.println(finalMap);
        return Dijkstra;
    }

  

打印路径 &打印指定地铁线路

  • 在计算最短路径的时候,已经保存了每个站点最后被更新时的上一个结点,这个时候,只要我们循环从最后一个结点往回找,就可以得到最终”倒序“的路径了,这里有一个小技巧,为了避免再写一个linkedhashmap 的倒序,这里我们可以直接在输入起点和终点的时候,程序将起点和终点交换,这样就可以得到(倒序(倒序)=正序)的效果了。

  • 同时在打印两个中间转站点之间的站点时,当两个站点的id差值为正的时候,就将step设置为正1,每次加1,并通过得到的编号输出站点名;同样,当两个站点的id 差值为负的时候,就将step设置为-1即可,最后,通过计算出的地铁线路编号和地铁名称的map,输出地铁名称以及地铁线路的信息,最后将其输入到txt文件中去。

stationsInLine()

Vector<String> stationsInLine(Station stationStart, Station stationEnd) {
        Vector<String> listStations = new Vector<String>();
        int nLineNumber = getLineNumber(stationStart, stationEnd);

        int nStartId = 0;
        int nEndId = 0;
        for (int nId: stationStart.setStationId) {
            if (Math.abs(nId-(nLineNumber*1000))<1000) {
                nStartId = nId;
            }
        }
        for (int nId: stationEnd.setStationId) {
            if (Math.abs(nId-(nLineNumber*1000))<1000) {
                nEndId = nId;
            }
        }
        if (nStartId == nEndId) {
            return listStations;
        }
        int nStep = 1;
        if (nEndId < nStartId) {
            nStep = -1;
        }
        int nIndexId = nStartId + nStep;
        while (nIndexId != nEndId) {
            String strSName = mapStationIdtoStation.get(nIndexId).stationName;
            listStations.addElement(strSName);
            nIndexId += nStep;
        }
        String strName = mapStationIdtoStation.get(nEndId).stationName;
        listStations.addElement(strName);
        return listStations;
    }

void printPath()

   void printPath(String start,String end, String strOutFileName) {
        String strFormatedPath = formatPath(start,end);

        toFile(strFormatedPath, strOutFileName);
    }

formatPath()

 String formatPath(String start,String end) {
        StringBuffer strRst = new StringBuffer();
        String  StartStationName=end;
        int i=1;

        int nCurrentLine = -1;

        System.out.print(finalMap);

        strRst.append(String.format("\n起始站:%s\r\n", end));
        while(end != finalMap.get(end)) {
            Station stationStart = mapNametoStation.get(end);
            Station stationEnd = mapNametoStation.get(finalMap.get(end)); 
            end = finalMap.get(end);           
            int nLineNum = Math.abs(getLineNumber(stationStart, stationEnd));           
            if (nLineNum != nCurrentLine) {
                nCurrentLine = nLineNum;
                strRst.append(String.format("%s\r\n", stationInfo.get(nLineNum)));
            }
            for (String strStationName: stationsInLine(stationStart, stationEnd)) {
                i++;
                strRst.append(String.format("%s\r\n", strStationName));
            }
            System.out.println(i);
        }
        strRst.append(String.format("\n共经过 %d 站\r\n", i));
        return strRst.toString();
    }

void toFile() 

 void toFile(String strContent, String strOutFile) {
        try {
            File file = new File(strOutFile);
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file.getName(), false);

            fileWriter.write(strContent.toString());
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    };

  printLineInfo()

 void printLineInfo(String LineName, String strOutFile) {
        StringBuffer strRst = new StringBuffer();
        strRst.append(String.format("地铁:%s\r\n", LineName));
        if(NtostationInfo.get(LineName)==null)
        {
            System.out.println("不存在该条地铁线路,请检查输入!\n");
            return;
        }
        int nLineNum = NtostationInfo.get(LineName);
        for (int i = 1; i < 90; i++) {
            int nStationId = nLineNum * 1000 + i;

            if (mapStationIdtoStation.containsKey(nStationId)) {
                strRst.append(mapStationIdtoStation.get(nStationId).stationName + "\r\n");
            } else {
                break;
            }
        }
        toFile(strRst.toString(), strOutFile);
    }

  

五、测试

  • 1.map 后没有参数信息
java subway -b 洪湖里 复兴路 -map

  img

  • 2.缺少输出文件信息
java subway -b 洪湖里 复兴路 -map subway.txt -o

    img

  • 3.缺少起点或终点信息
java subway -b 华苑  -map subway.txt -o routine

 img

  • 4.查询的地铁线路不存在
java subway -a 天府广场 -map subway.txt -o station.txt

 img

  • 5.查询的地铁起点或终点站不存在
java subway -b 天府广场 复兴路 -map subway.txt -o routine.txt

  img

  • 6.查询指定地铁线路并输出到station.txt
java subway -a 3号线 -map subway.txt -o station.txt

 输出的station.txt文件内容:

复制代码

地铁:3号线
小淀
丰产河
华北集团
天士力
宜兴埠
张兴庄
铁东路
北站
中山路
金狮桥
天津站
津湾广场
和平路
营口道
西康路
吴家窑
天塔
周邓纪念馆
红旗南路
王顶堤
华苑
大学城
高新区
学府工业区
杨伍庄
南站

复制代码

  • 7.查询的起点终点在同一条地铁线上
java subway -b 刘园 西北角 -map subway.txt -o routine.txt

输出的routine.txt文件内容: 

复制代码

起始站:刘园
1号线
西横堤
果酒厂
本溪路
勤俭道
洪湖里
西站
西北角

共经过 8 站

复制代码

  • 8.查询的起点终点不在同一条地铁线上
java subway -b 果酒厂 咸阳路 -map subway.txt -o routine.txt

输出的routine.txt文件内容: 

复制代码

起始站:果酒厂
1号线
本溪路
勤俭道
洪湖里
西站
6号线
复兴路
人民医院
长虹公园
2号线
咸阳路

共经过 9 站

复制代码

  • 9.查询不存在的地铁线路
java subway -a 4号线 -map subway.txt -o station.txt

 img

  • 10.查询多换乘路线
java subway -b 天士力 土城 -map subway.txt -o routine.txt

输出的routine.txt文件内容: 

复制代码


起始站:天士力
3号线
宜兴埠
张兴庄
铁东路
北站
中山路
金狮桥
天津站
9号线
大王庄
十一经路
直沽
5号线
下瓦房
1号线
南楼
土城

共经过 14 站

复制代码

六、项目完成时间统计

PSP 2.1 Personal Software Process Stages Time
Planning 计划
· Estimate · 估计这个任务需要多少时间 3weeks
Development 开发
· Analysis · 需求分析 (包括学习新技术) 2days
· Design Spec · 生成设计文档 2days
· Design Review · 设计复审 (和同事审核设计文档) 1day
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 1day
· Design · 具体设计 2days
· Coding · 具体编码 4days
· Code Review · 代码复审 2days
· Test · 测试(自我测试,修改代码,提交修改) 2day
Reporting 报告
· Test Report · 测试报告 1day
· Size Measurement · 计算工作量 1day
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 2days
合计 20days

七.总结

  这是我第一用java编写数据结构的算法,也是我第一次用数据结构做一个有实际意义的小项目。通过本次项目,我复习回顾了java,并且对其应用有了更深的理解。同时,熟悉了idea的使用,熟悉了结对编程方式,感受到了结对编程模式的优势。

​ 在本次天津地铁路径的规划中,只将换乘站和起点终点作为需要计算的结点,从而免去了计算所有站点所带来的时间负杂度。但是,在开发过程中,也遇到了不少麻烦,比如说结对编程过程中如何高效沟通,提高我们小队的开发效率,比如在测试过程中如何保证测试用例足够全面,以确保我们的程序达到了预期需求等等。但是后来通过我们小组的不断沟通与上网查询、学习,这些问题都比较理想的解决了。

​ 总之,通过本次项目实验,让我体会到了作为一个程序员,理论和实践相结合的重要性,同时还要学会不断学习,细心观察,强化程序的健壮性和实用性。

github代码地址:https://github.com/Wanghongyu156/ck

Guess you like

Origin www.cnblogs.com/wanghongyu523/p/11808111.html