Project 5 — 天气与学生分组决策模拟

73次阅读
没有评论

程序要求

本项目实现了一个简化的“世界”模拟:

  • 世界包含若干个学生小组(初始为 20 个,每组 30 名学生)。
  • 天气每天按一定概率转换(5 种天气状态构成的马尔可夫过程)。
  • 每名学生有一个个体化参数 p(0.1–0.99),在不同天气下根据一定的随机策略决定“去玩”或“不去玩”。
  • 每天有 40% 的概率随机选择一个小组将其一分为二(当组员数小于 10 时不再拆分)。
  • 模拟运行 30 天,并逐日输出每个小组的统计信息以及“去玩”的学生 ID 列表。

该工程为 C++ 项目,使用 make 构建,默认在 macOS 下使用 g++ 编译器。


目录结构

  • Decision.C / Decision.h – 学生决策逻辑(基于个体参数 p 与天气)
  • Group.C / Group.h – 学生小组(收集小组成员决策、报告统计、随机拆分)
  • Student.C / Student.h – 学生实体(ID 与个体化决策器)
  • Weather.C / Weather.h – 天气状态与转移(5 种状态、逐日更新)
  • World.C / World.h – 世界:持有全部小组、每日推进模拟
  • main.C – 程序入口:构造 World 并运行模拟
  • makefile – 构建脚本(目标可执行文件名:main)

构建与运行

在项目目录下执行:

make            # 编译生成可执行文件 ./main
./main          # 运行模拟(输出 30 天逐日统计)

清理构建产物:

make clean

说明:makefile 默认使用 g++ 与参数 -g -w(带调试信息、忽略警告)。


程序输出示例(节选)

Day 1:
Group 1: 15 students, 8 play, 7 not play
   ID to play: 3 4 5 8 9 11 13 15
Group 2: 30 students, 9 play, 21 not play
   ID to play: 32 34 37 38 44 45 53 55 57
...
  • 每天打印 Day N:,随后对每个小组打印:
    • 本组总人数、去玩人数、未去玩人数
    • ID to play: 后跟“去玩”的学生 ID 列表
  • 输出含有随机性;程序在 main.C 中以当前时间播种 rand(),每次运行结果不同。

关键概念与规则

  • 天气状态(Weather 类,内部用 1–5 表示):

    1. 晴 且 潮湿(sunny & humid)
    2. 晴 且 不潮湿(sunny & not humid)
    3. 雨 且 有风(raining & windy)
    4. 雨 且 无风(raining & not windy)
    5. 阴天(overcast)

    每日根据上一日状态按既定概率转移(见 Weather.C)。

  • 学生决策(Decision 类):

    • 每名学生初始化一个 p ∈ [0.1, 0.99](离散到 0.01 步长)。
    • 在不同天气下通过多次掷“百分骰”与 p 相关的阈值比较,返回 0(去玩)或 1(不去玩)。
    • 特例:阴天(overcast,状态 5)时“必须去玩”(直接返回 0)。
  • 小组拆分(Group::Split):

    • 每天 40% 概率选择一个小组尝试拆分;组员数不足 10 则不拆。
    • 拆分为两半:原组保留前一半,新组得到后一半成员。
  • 模拟周期(World::DoSimulation):

    • 固定 30 天;如需修改,可在 World.C 中调整 for (int day = 1; day <= 30; day++)

可配置项与修改指引

  • 初始小组数量:
    • 位于 main.CWorld w(20);(将 20 改为期望的初始小组数)。
  • 每组初始人数:
    • 位于 World.C 构造函数中:Group(i * 30 + 1, 30),第二个参数为人数。
  • 模拟天数:
    • 位于 World.CDoSimulation() 日循环上限(默认 30)。
  • 学生个体参数范围:
    • 位于 Student.C 构造函数:0.10.99(可自行调整生成逻辑)。
  • 天气转移概率:
    • 位于 Weather.CUpdateWeather(),可直接编辑转移分支与阈值。

修改源代码后,重新 make 即可生效。


开发与维护提示

  • 本项目使用手动内存管理(new/delete),在 WorldGroupStudent 中均实现了相应的释放逻辑;如进行深度改动,请留意指针所有权与析构次序。
  • Group::Split 中将后一半成员转移到新组,并重建本组的 Decisions 数组;请确保任何新增字段在拆分过程中一并维护。
  • 输出格式在 Group::Report() 中定义,如需对齐 / 本地化 / 定制展示,可在此处修改。

依赖与环境

  • 操作系统:macOS(其他 Unix-like 环境通常也可直接构建)
  • 编译器:g++(C++ 编译器),无需额外第三方库

源代码一览

以下为所有主要源文件与构建脚本的内容,便于阅读。

makefile

CC=g++
CCFLAG=-g -w

prog5: main.o Decision.o Student.o Group.o Weather.o World.o
    $(CC) $(CCFLAG) -o main main.o Decision.o Student.o Group.o Weather.o World.o

main.o: main.C World.h
    $(CC) $(CCFLAG) -c -o main.o main.C

World.o: World.C World.h Group.h Weather.h
    $(CC) $(CCFLAG) -c -o World.o World.C

Group.o: Group.C Group.h Student.h
    $(CC) $(CCFLAG) -c -o Group.o Group.C

Student.o: Student.C Student.h Decision.h
    $(CC) $(CCFLAG) -c -o Student.o Student.C

Decision.o: Decision.C Decision.h
    $(CC) $(CCFLAG) -c -o Decision.o Decision.C

Weather.o: Weather.C Weather.h
    $(CC) $(CCFLAG) -c -o Weather.o Weather.C

clean:
    rm -rf *.o main

main.C

#include 
<cstdlib>
#include 
<ctime>
#include "World.h"
int main() {srand(time(0));
  World w(20); // 20 groups
  w.DoSimulation();
  return 0;
}

World.h

#ifndef WORLD_H
#define WORLD_H

#include "Group.h"
#include "Weather.h"

class World {
private:
  Group** groups;
  int count;
  Weather* w;
public:
  World(int c);
  ~World();
  void DoSimulation();};

#endif

World.C

#include "World.h"
#include 
<iostream>
#include 
<cstdlib>

World::World(int c) : count(c) {groups = new Group*[c];
  for (int i = 0; i < c; i++) {groups[i] = new Group(i * 30 + 1, 30); // each group has 30 students
  }
  w = new Weather();}

World::~World() {for (int i = 0; i < count; i++) {delete groups[i];
  }
  delete[] groups;
  delete w;
}

void World::DoSimulation() {for (int day = 1; day <= 30; day++) {
    std::cout << "Day " << day << ":" << std::endl;

    // 40% chance to split a random group
    if (rand() % 100 < 40 && count > 0) {int groupIndex = rand() % count;
      Group* newGroup = groups[groupIndex]->Split();
      if (newGroup != nullptr) {
        // Resize groups array to add new group
        Group** temp = new Group*[count + 1];
        for (int i = 0; i < count; i++) {temp[i] = groups[i];
        }
        temp[count] = newGroup;
        delete[] groups;
        groups = temp;
        count++;
      }
    }

    // Update weather
    w->UpdateWeather();
    int weather = w->GetWeather();

    // Make decisions and report for each group
    for (int i = 0; i < count; i++) {std::cout << "Group " << (i + 1) << ": ";
      groups[i]->GroupDecision(weather);
      groups[i]->Report();}
    std::cout << std::endl;
  }
}

Group.h

#ifndef GROUP_H
#define GROUP_H

// #include 
<cstddef>
#include "Student.h"

class Group {
private:
  Student** students;
  int count;
  int* Decisions;
public:
  Group(int startingID, int c);
  ~Group();
  void GroupDecision(int weather);
  void Report();
  Group* Split();
  int GetCount() { return count;}
};

#endif

Group.C

#include "Group.h"
#include 
<iostream>
#include 
<cstdlib>

Group::Group(int startingID, int c) : count(c) {students = new Student*[c];
  Decisions = new int[c];
  for (int i = 0; i < c; i++) {students[i] = new Student(startingID + i);
  }
}

Group::~Group() {for (int i = 0; i < count; i++) {delete students[i];
  }
  delete[] students;
  delete[] Decisions;}

void Group::GroupDecision(int weather) {for (int i = 0; i < count; i++) {Decisions[i] = students[i]->MakeDecision(weather);
  }
}

void Group::Report() {
  int playCount = 0;
  for (int i = 0; i < count; i++) {if (Decisions[i] == 0) playCount++;
  }

  std::cout << count << " students, " << playCount << " play, " 
        << (count - playCount) << " not play" << std::endl;

  std::cout << "   ID to play: ";
  bool first = true;
  for (int i = 0; i < count; i++) {if (Decisions[i] == 0) {if (!first) std::cout << " ";
      std::cout << students[i]->GetID();
      first = false;
    }
  }
  std::cout << std::endl;
}

Group* Group::Split() {if (count < 10) return nullptr;

  int newCount = count / 2;
  int remainingCount = count - newCount;

  // Create new group with second half of students
  Group* newGroup = new Group(0, newCount); // ID will be reassigned

  // Transfer students to new group
  for (int i = 0; i < newCount; i++) {delete newGroup->students[i]; // delete the temporary student
    newGroup->students[i] = students[remainingCount + i];
    students[remainingCount + i] = nullptr; // mark as transferred
  }

  // Resize current group
  Student** temp = new Student*[remainingCount];
  for (int i = 0; i < remainingCount; i++) {temp[i] = students[i];
  }
  delete[] students;
  students = temp;
  count = remainingCount;

  // Update decisions array
  delete[] Decisions;
  Decisions = new int[count];

  return newGroup;
}

Student.h

#ifndef STUDENT_H
#define STUDENT_H

#include "Decision.h"

class Student {
private:
  int ID;
  Decision* decision;
public:
  Student(int k);
  ~Student();
  int MakeDecision(int weather);
  int GetID();};

#endif

Student.C

#include "Student.h"
#include 
<cstdlib>

Student::Student(int k) : ID(k) {double p = 0.1 + (rand() % 90) / 100.0; // random p between 0.1 and 0.99
  decision = new Decision(p);
}

Student::~Student() {delete decision;}

int Student::MakeDecision(int weather) {return decision->MakeDecision(weather);
}

int Student::GetID() {return ID;}

Decision.h

#ifndef DECISION_H
#define DECISION_H

class Decision {
private:
  double x;
public:
  Decision(double p);
  int MakeDecision(int weather);
};

#endif

Decision.C

#include "Decision.h"
#include 
<cstdlib>

Decision::Decision(double p) : x(p) {}

int Decision::MakeDecision(int weather) {double dice = rand() % 100;
  double threshold = x * 100.0;

  if (dice < threshold) return 1;

  if (weather == 5) return 0; // overcast must play

  if (weather < 3) {dice = rand() % 100;
    if (dice >= threshold / 2.0) return 1;

    if (weather == 1) { // humid
      dice = rand() % 100;
      if (dice < threshold) return 0; // play
      else return 1; // not play
    } else {dice = rand() % 100;
      if (dice < threshold / 2.0) return 1; // not play
      else return 0; // play
    }
  } else {dice = rand() % 100;
    if (dice < threshold) return 1; // not play

    if (weather == 3) { // windy
      dice = rand() % 100;
      if (dice < threshold) return 0; // play
      else return 1; // not play
    } else {dice = rand() % 100;
      if (dice < threshold / 2.0) return 1; // play
      else return 0; // not play
    }
  }
}

Weather.h

#ifndef WEATHER_H
#define WEATHER_H

class Weather {
private:
  int current;
public:
  Weather();
  int GetWeather();
  void UpdateWeather();};

#endif

Weather.C

#include "Weather.h"
#include 
<cstdlib>

Weather::Weather() {current = rand() % 5 + 1; // 1-5
}

int Weather::GetWeather() {return current;}

void Weather::UpdateWeather() {double dice = rand() % 100;

  switch (current) {
    case 5: // overcast
      if (dice < 20) break; // remain
      else if (dice < 40) current = 1; // sunny and humid
      else if (dice < 60) current = 2; // sunny and not humid
      else if (dice < 80) current = 3; // raining and windy
      else current = 4; // raining and not windy
      break;

    case 1: // sunny and humid
      if (dice < 10) break; // remain
      else if (dice < 40) current = 2; // sunny and not humid
      else if (dice < 80) current = 5; // overcast
      else current = 3; // raining and windy
      break;

    case 2: // sunny and not humid
      if (dice < 20) break; // remain
      else if (dice < 50) current = 1; // sunny and humid
      else if (dice < 80) current = 5; // overcast
      else if (dice < 90) current = 3; // raining and windy
      else current = 4; // raining and not windy
      break;

    case 3: // raining and windy
      if (dice < 10) break; // remain
      else if (dice < 40) current = 5; // overcast
      else if (dice < 60) current = 1; // sunny and humid
      else if (dice < 90) current = 4; // raining and not windy
      else current = 2; // sunny and not humid
      break;

    case 4: // raining and not windy
      if (dice < 20) break; // remain
      else if (dice < 40) current = 5; // overcast
      else if (dice < 70) current = 1; // sunny and humid
      else if (dice < 90) current = 3; // raining and windy
      else current = 2; // sunny and not humid
      break;
  }
}

正文完
 2
评论(没有评论)