软件工程实践第二次作业——个人实战

这个作业属于哪个课程福州大学 2024 秋软件工程实践
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标爬取数据、编写代码、性能分析、单元测试
其他参考文献如何使用 Python 提取 JSON 中的数据Gson的基本使用IDEA中添加junit4的三种方法(详细步骤操作)

1.Gitcode项目地址

仓库地址

2.PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2040
• Estimate• 估计这个任务需要多少时间2040
Development开发9601255
• Analysis• 需求分析 (包括学习新技术)120480
• Design Spec• 生成设计文档2020
• Design Review• 设计复审2060
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)105
• Design• 具体设计3090
• Coding• 具体编码700420
• Code Review• 代码复审3090
• Test• 测试(自我测试,修改代码,提交修改)3090
Reporting报告6070
• Test Repor• 测试报告2035
• Size Measurement• 计算工作量2015
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划2020
合计10401365

3.解题思路描述

3.1数据爬取

拿到项目第一步,数据在哪?我根据提供的网址,打开开发者工具看源码后,找到medals文件里面的script,包含所有需要的数据,然后爬下来。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后就得到了包含所有数据(也有冗余数据)的olympics_medals_data.json文件。

(话说我开学刚卸的PyCharm和IDLE,又火急火燎下回来)
在这里插入图片描述

3.2 需求分析

根据作业要求里面的三个功能,首先要确定需要的数据结构,便于后面项目进行,比如对json数据的筛选,使用Gson进行数据转换,都有帮助。

所需功能:
1.实现奖牌排行榜上的所有国家(按奖牌排序)的奖牌数输出。
2.实现输出指定日期当天的运动员(按奖牌排序)的奖牌数输出。
3.实现两个国家指定比赛项目奖牌数的比较。

由于功能1和3可以放在一起实现,更方便处理以及节省空间。
所以我决定使用两种结构,一种放某一国家的奖牌数和获得奖牌的项目的信息,一种放某一日期,所有获奖人员的姓名和奖牌数。

3.3 分析JSON结构并筛选有效数据

一开始我想直接使用olympics_medals_data.json文件的,但发现无效数据太多,而且会影响最后的效率。所以决定先对其进行有效数据的筛选。

1.首先把JSON大致看一遍,发现url放在"urls",国家对应的名称代码放在"nocList"等,发现数据在"initialMedals"里,但其实再看看就发现里面只需要"medalStandings",还有一个没用的"competitionCode": ““也不知道写那干嘛,然后"medalStandings"也只需要"medalsTable”,还有一个"eventInfo"计算总共的项目->没啥用。所以最后只需要"medalsTable”。所以直接把"medalsTable"里面的数据都放到另外一个文件filtered_medals_table.json里。
在这里插入图片描述
(项目写完了,我都不知道写个空的"competitionCode":有啥用)在这里插入图片描述

2.随后用python提取功能需要的数据分别放在两个json文件里。

		import json# 加载JSON数据
		with open('D:/desktop/filtered_medals_table.json', 'r', encoding='utf-8') as file:
	    data = json.load(file)  # data 是一个列表,而不是字典
		countries = []
		date_events = {}
		for country_data in data:  # 不再使用 'medalsTable' 作为键,因为 data 是一个列表
	    country_code = country_data['description']
	    country_name = country_data['longDescription']
	    rank = country_data['rank']
		   # 获取国家总奖牌数
	    total_medals = None
	    for medals_info in country_data['medalsNumber']:
	        if medals_info['type'] == 'Total':
	            total_medals = medals_info
	            break
	    gold = total_medals['gold']
	    silver = total_medals['silver']
	    bronze = total_medals['bronze']
	    total = total_medals['total']
	    # 创建 Country 对象结构
	    country = {
	        'countryCode': country_code,
	        'countryName': country_name,
	        'gold': gold,
	        'silver': silver,
	        'bronze': bronze,
	        'total': total,
	        'rank': rank,
	        'disciplines': []
	    }
	    # 遍历国家的项目 (disciplines)
	    for discipline_data in country_data['disciplines']:
	        discipline_name = discipline_data['name']
	        discipline_gold = discipline_data['gold']
	        discipline_silver = discipline_data['silver']
	        discipline_bronze = discipline_data['bronze']
	        discipline_total = discipline_data['total']
	        # 创建 Discipline 对象
	        discipline = {
	            'name': discipline_name,
	            'gold': discipline_gold,
	            'silver': discipline_silver,
	            'bronze': discipline_bronze,
	            'total': discipline_total
	        }
	     	   # 将 Discipline 添加到 Country
	       	 country['disciplines'].append(discipline)
	        
	        # 遍历项目的获奖者并将信息按日期存储
	        for winner_data in discipline_data['medalWinners']:
	            event_description = winner_data['eventDescription']
	            winner_name = winner_data['competitorDisplayName']
	            date = winner_data['date']
	    
	            # 创建 Event 对象
	            event = {
	                'eventName': event_description,
	                'winnerName': winner_name,
	                'nationality': country_code,
	                'gold': discipline_gold,
	                'silver': discipline_silver,
	                'bronze': discipline_bronze,
	                'total': discipline_total
	            }
	    
	            # 按日期存储事件
	            if date not in date_events:
	                date_events[date] = {
	                    'date': date,
	                    'events': []
	                }
	            date_events[date]['events'].append(event)
	    
	    # 将 Country 对象添加到 countries 列表
	    countries.append(country)
	with open('D:/desktop/countries.json', 'w', encoding='utf-8') as outfile:
	    json.dump(countries, outfile, ensure_ascii=False, indent=4)
	
	with open('D:/desktop/dates.json', 'w', encoding='utf-8') as outfile:
	    json.dump(list(date_events.values()), outfile, ensure_ascii=False, indent=4)
	
	print("数据已成功处理并保存为 countries.json 和 dates.json")
	import json
	
	# 读取原始 JSON 文件
	with open('D:/desktop/filtered_medals_table.json', 'r', encoding='utf-8') as file:
	    data = json.load(file)
	
	date_events = {}
	
	# 遍历每个国家的数据
	for country_data in data:
	    country_code = country_data['description']
	
	    # 遍历国家的项目 (disciplines)
	    for discipline_data in country_data['disciplines']:
	        # 遍历项目的获奖者
	        for winner_data in discipline_data['medalWinners']:
	            competitor_type = winner_data['competitorType']  # 判断是否为团体赛
	            if competitor_type == "T":
	                continue  # 跳过团体赛
	    
	            winner_name = winner_data['competitorDisplayName']
	            date = winner_data['date']
	            medal_type = winner_data['medalType']
	    
	            # 初始化当天的日期结构
	            if date not in date_events:
	                date_events[date] = {
	                    'date': date,
	                    'competitors': {}
	                }
	    
	            # 如果该选手已经在日期里,累加奖牌数
	            if winner_name not in date_events[date]['competitors']:
	                date_events[date]['competitors'][winner_name] = {
	                    'name': winner_name,
	                    'nationality': country_code,
	                    'medalNumber': {
	                        'gold': 0,
	                        'silver': 0,
	                        'bronze': 0,
	                        'total': 0
	                    }
	                }
	    
	            # 根据奖牌类型累加奖牌
	            if medal_type == "ME_GOLD":
	                date_events[date]['competitors'][winner_name]['medalNumber']['gold'] += 1
	            elif medal_type == "ME_SILVER":
	                date_events[date]['competitors'][winner_name]['medalNumber']['silver'] += 1
	            elif medal_type == "ME_BRONZE":
	                date_events[date]['competitors'][winner_name]['medalNumber']['bronze'] += 1
	    
	            # 更新奖牌总数
	            date_events[date]['competitors'][winner_name]['medalNumber']['total'] += 1
	
	# 对每个日期下的选手按金牌、银牌、铜牌排序,并为他们添加排名
	for date, event_data in date_events.items():
	    competitors_list = list(event_data['competitors'].values())
	    competitors_list.sort(key=lambda x: (-x['medalNumber']['gold'], -x['medalNumber']['silver'], -x['medalNumber']['bronze']))
	    
	    # 添加排名
	    for rank, competitor in enumerate(competitors_list, start=1):
	        competitor['rank'] = rank
	    
	    # 更新 competitors 数据
	    date_events[date]['competitors'] = competitors_list
	
	# 将处理好的 DateEvents 数据保存为 JSON 文件
	with open('D:/desktop/dates.json', 'w', encoding='utf-8') as outfile:
	    json.dump(list(date_events.values()), outfile, ensure_ascii=False, indent=4)
	
	print("数据已成功处理并保存为 dates.json")

不要问为什么dates.json处理了两次,因为需求看错了(白花两小时我真的哭死)
在这里插入图片描述

3.最后得到两个处理完的json文件–countries.json和dates.json。结构如下
在这里插入图片描述在这里插入图片描述

3.4项目技术

其实原本想尝试c++的,但是由于没什么c++的项目经验就用Java了。
项目用maven引用Gson工具来处理json数据转换。
只需要在项目里的pop.xml里面加入依赖就好了。

<dependencies>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.9</version> <!-- 版本可以根据需要调整 -->
    </dependency>
</dependencies>

4.接口设计和实现过程

4.1代码中类的组织和关系

1.首先两个类使用Gson对两个json文件进行数据转换。
2.使用三个类(其实还有一个内置类),对应所需要的数据结构。
3.由于作业要求接口封装,我就把两个基本功能放在两个类中了 。
4.加上一个主程序OlympicSearch类

在这里插入图片描述

4.2 代码中的函数组织

有了数据和类的结构,函数就非常好处理了,在封装的地方安安分分写函数就好了。
比如在DailyResults类里面writeDailyResults传入List<DateWinners.Competitor>比较好而不是直接传List< DateWinners >.

在这里插入图片描述

5.关键代码展示

5.1 对json进行数据转化成List

            Gson gson = new Gson();
            FileReader reader = new FileReader("D:/desktop/countries.json");

            // 反序列化到 Country 对象列表
            Type countryListType = new TypeToken<List<Country>>() {}.getType();
            List<Country> countries = gson.fromJson(reader, countryListType);

            // 输出所有国家的奖牌信息
            for (Country country : countries) {
                System.out.println(country);
            }
 Gson gson = new Gson();
            FileReader reader = new FileReader("D:/desktop/dates.json");
            // 反序列化到 DateWinners 对象列表
            Type datewinnersListType = new TypeToken<List<DateWinners>>() {}.getType();
            List<DateWinners> datewinnerss = gson.fromJson(reader, datewinnersListType);

            // 输出所有国家的奖牌信息
            for (DateWinners datewinners : datewinnerss) {
                System.out.println(datewinners);
            }

5.2主函数中得到数据组成的列表

public class OlympicSearch {
    static String countriesfile="D:/desktop/countries.json";
    static String datesfile="D:/desktop/dates.json";
    private static List<Country> loadCountries() throws Exception {
        Gson gson = new Gson();
        FileReader reader = new FileReader(countriesfile);
        Type countryListType = new TypeToken<List<Country>>() {}.getType();
        return gson.fromJson(reader, countryListType);
    }
    private static List<DateWinners> loadDateWinners()throws Exception{
        Gson gson = new Gson();
        FileReader reader = new FileReader(datesfile);
        Type dateListType = new TypeToken<List<DateWinners>>() {}.getType();
        return gson.fromJson(reader, dateListType);
    }
    public static List<Country> sortCountriesByMedals(List<Country> countries) {
        countries.sort((c1, c2) -> {
            if (c1.getGold() != c2.getGold()) return Integer.compare(c2.getGold(), c1.getGold());
            if (c1.getSilver() != c2.getSilver()) return Integer.compare(c2.getSilver(), c1.getSilver());
            return Integer.compare(c2.getBronze(), c1.getBronze());
        });
        return countries;
    }

5.3 实现命令行程序

实际上就是对主程序的args进行处理

            if (args.length != 2) {
                System.err.println("Usage: java -jar OlympicSearch.jar <inputFile> <outputFile>");
                System.exit(1);
            }
            String inputFile = args[0];
            String outputFile = args[1];

除了这三个,其他的函数就是正常的逻辑关系处理了。在这里插入图片描述

6.性能改进

6.1去除冗余数据

其实最大的改进就是把json文件里面的冗余数据去除,详细的请看 3.3 分析JSON结构并筛选有效数据。

6.2 数据结构的处理

前面说过我重新处理了一遍dates.json文件,因为在最开始处理的时候不知道怎么样的数据结构最合适,在真正写到输出的时候,我确定了更优良的数据结构,防止了多余的数据冗余。

6.3传入参数的差异

前面在4.2 代码中的函数组织中,我传入的列表选择了List<DateWinners.Competitor> competitors,这样只需要在主程序提前判断处理了输入的日期,然后只需要传需要日期的DateWinners数据就好了,不然传过去的是所有日期的全部数据,效率就会很低。

 String specifiedDate = input[1];
 boolean found = false;
 DailyResults dailyResults = new DailyResults();
 for (DateWinners dateWinners : dateWinnersList) {
	String t=dateWinners.getDate();
	t=""+t.charAt(5)+t.charAt(6)+t.charAt(8)+t.charAt(9);
	if (t.equals(specifiedDate)){
		found = true;
	dailyResults.writeDailyResults(specifiedDate,dateWinners.getCompetitors(),outputFile);
	break;
	}
}

7.单元测试

使用 了JUnit 进行单元测试。

这里导入依赖库在这里插入图片描述

package junit.test;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.*;
import java.util.List;

class CountryStatsTest {

    private List<Country> countries;

    @BeforeEach
    void setUp() {
        // 模拟国家数据
        Country country1 = new Country("USA", 10, 5, 3, 18, 1);
        Country country2 = new Country("China", 9, 6, 4, 19, 2);
        countries = List.of(country1, country2);
    }

    @Test
    void testOutputCountryMedals() throws IOException {
        String outputPath = "country_output.txt";
        CountryStats countryStats = new CountryStats();
        countryStats.outputCountryMedals(countries, outputPath);

        BufferedReader reader = new BufferedReader(new FileReader(outputPath));
        assertTrue(reader.readLine().contains("rank1:USA"));
        assertTrue(reader.readLine().contains("gold:10"));
        reader.close();

        new File(outputPath).delete();
    }

    @Test
    void testEmptyCountryList() throws IOException {
        String outputPath = "empty_country_output.txt";
        CountryStats countryStats = new CountryStats();
        countryStats.outputCountryMedals(List.of(), outputPath);

        BufferedReader reader = new BufferedReader(new FileReader(outputPath));
        assertNull(reader.readLine()); // 应该没有输出
        reader.close();

        new File(outputPath).delete();
    }
}

package junit.test;
//import Olympic.src.DailyResults.java;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.*;
import java.util.List;

class DailyResultsTest {

    private List<DateWinners.Competitor> competitors;

    @BeforeEach
    void setUp() {
        // 模拟竞赛者数据
        DateWinners.Competitor competitor1 = new DateWinners.Competitor("C.Gauff", "Moldova", new DateWinners.Competitor.MedalNumber(2, 0, 0, 2), 1);
        DateWinners.Competitor competitor2 = new DateWinners.Competitor("PAN Zhanle", "China", new DateWinners.Competitor.MedalNumber(1, 0, 0, 1), 2);
        competitors = List.of(competitor1, competitor2);
    }

    @Test
    void testWriteDailyResults() throws IOException {
        String outputPath = "test_output.txt";
        DailyResults dailyResults=new DailyResults();

        DailyResults.writeDailyResults("2024-08-09", competitors, outputPath);

        BufferedReader reader = new BufferedReader(new FileReader(outputPath));
        assertTrue(reader.readLine().contains("winner:C.Gauff"));
        assertTrue(reader.readLine().contains("nationality:Moldova"));
        reader.close();

        // 清理测试文件
        new File(outputPath).delete();
    }

    @Test
    void testEmptyCompetitors() throws IOException {
        String outputPath = "test_empty_output.txt";
        DailyResults.writeDailyResults("2024-08-09", List.of(), outputPath);

        BufferedReader reader = new BufferedReader(new FileReader(outputPath));
        assertNull(reader.readLine()); // 应该没有输出
        reader.close();

        new File(outputPath).delete();
    }
}

8.异常处理

对于可能的异常,我对函数都进行了异常捕获,并将对应的提示内容输出,在抛出异常后,由外层函数处理并提示输出。

9.心得体会

本次设计体验了一整个项目的设计编写和调试,包括爬数据,处理数据,编写代码,测试等,工作量还是很大的。虽然单人项目开发颇具挑战,但也给我带来了全方位的能力提升,之前从来没有完整的掌握整个项目流程,都是和同学合作,还是收获很多的。就是有点累hh,收工睡觉。

...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值