网上谈论到当今社会男女比例失调,有人说到一个问题,某些地方政策是,夫妻俩生育一个孩子后,如果是女的就可以再生一个,如果是男的,就不能继续了。这导致了男女比例失调。 然而,立马有人反对说,这件事对全社会影响比例非常小。

Argue is cheap, show me the code!

我做了一个简单的模拟器,模拟这个事情,看结果就完了。 具体设定如下

  • 每个人到20岁开始寻找伴侣,到60生命结束
  • 新生儿男女比例都是1/2
  • 生下女儿则两年后继续尝试,生下儿子则停止尝试(在此,我假定生育完一个孩子后要隔一年再来战过。)
  • 如果生下五个女儿,也停止尝试(当然,如果夫妻有人死了自然不能继续生育了)
  • 假定理想条件,年纪大优先找到另一半,但45岁后不再尝试

如此模拟200年(这个时间我随便写的,修改个变量就是2000年也很容易),输出:

  • 终身未娶/未嫁和成功完婚的数量
  • 在世的男女比例
  • 在世未婚的男女比例

为了代码方便,使用了这样一些假定如下以hard code放里面了。

  • 假定开始时间是2014年
  • 开始时刚出生的男女人数相同

模拟器构造了几个队列,分别表示男孩,女孩,夫妻和FA的筒子们,每年对生孩子,结婚和死亡分别统计。生下的孩子性别用随机数roll个,每年统计下各个队列里面的信息即可。构造非常简单,它对我的最大困扰其实是自己手工实现个链式队列。

那么,你觉得这个政策影响大么?

附:直接给一个相关代码和参考结果吧,你看符合你预期吧。

import random
from collections import deque
import matplotlib.pyplot as plt
from tqdm import tqdm


class Person:
    """表示一个人的类,包含性别、出生年份、婚姻状态等属性"""

    def __init__(self, gender, birth_year):
        self.gender = gender  # 'M' for male, 'F' for female
        self.birth_year = birth_year
        self.married = False
        self.spouse = None  # 配偶引用
        self.children = 0  # 孩子总数
        self.last_child_year = None  # 最后一个孩子出生的年份
        self.daughters = 0  # 女儿数量

    def age(self, current_year):
        """计算当前年龄"""
        return current_year - self.birth_year

    def is_alive(self, current_year):
        """判断是否在世(假设60岁为寿命上限)"""
        return self.age(current_year) <= 60

    def can_marry(self, current_year):
        """判断是否可以结婚(20-45岁之间且未婚)"""
        return (
            not self.married
            and self.is_alive(current_year)
            and self.age(current_year) >= 20
            and self.age(current_year) < 45
        )

    def can_have_children(self, current_year):
        """判断是否可以生育孩子"""
        # 必须已婚且双方都在世
        if (
            not self.married
            or not self.is_alive(current_year)
            or self.spouse is None  # 防止spouse为None时访问is_alive方法
            or not self.spouse.is_alive(current_year)
        ):
            return False

        # 如果已经有儿子或5个女儿,则停止生育
        if self.children > 0 and self.daughters < self.children:
            return False
        if self.daughters >= 5:
            return False

        # 需要在上一个孩子出生后等待2年
        if self.last_child_year is not None and current_year - self.last_child_year < 2:
            return False

        # 女性年龄限制(20-45岁)
        if self.gender == "F" and (
            self.age(current_year) < 20 or self.age(current_year) > 45
        ):
            return False

        return True


class Couple:
    """表示一对夫妻的类"""

    def __init__(self, husband, wife, marriage_year):
        self.husband = husband
        self.wife = wife
        self.marriage_year = marriage_year
        # 更新双方的婚姻状态
        husband.married = True
        husband.spouse = wife
        wife.married = True
        wife.spouse = husband

    def is_active(self, current_year):
        """判断夫妻双方是否都在世"""
        return self.husband.is_alive(current_year) and self.wife.is_alive(current_year)

    def can_have_children(self, current_year):
        """判断夫妻是否可以生育(委托给妻子判断)"""
        return self.wife.can_have_children(current_year)

    def have_child(self, current_year):
        """生育一个孩子,50%概率为男孩或女孩"""
        # 50% chance for each gender
        gender = "M" if random.random() < 0.5 else "F"

        # 更新父母的孩子信息
        self.husband.children += 1
        self.wife.children += 1
        self.wife.last_child_year = current_year

        if gender == "F":
            self.husband.daughters += 1
            self.wife.daughters += 1

        # 返回新生儿
        return Person(gender, current_year)


class Simulation:
    """人口模拟类,模拟人口变化、婚姻和生育"""

    def __init__(self, initial_population=1000, years=200, start_year=2014):
        self.current_year = start_year
        self.end_year = start_year + years

        # 初始化人口队列
        self.males = deque()  # 未婚男性
        self.females = deque()  # 未婚女性
        self.couples = deque()  # 已婚夫妻
        self.forever_alone_males = deque()  # 超过45岁未婚男性
        self.forever_alone_females = deque()  # 超过45岁未婚女性

        # 统计历史总人口
        self.total_males_ever = 0
        self.total_females_ever = 0
        # 统计历史 forever alone
        self.historical_forever_alone_males = 0
        self.historical_forever_alone_females = 0

        # 创建初始人口,男女比例相等
        for _ in range(initial_population // 2):
            # 随机年龄在0-60岁之间
            male_birth = self.current_year - random.randint(0, 60)
            female_birth = self.current_year - random.randint(0, 60)

            self.males.append(Person("M", male_birth))
            self.females.append(Person("F", female_birth))
            self.total_males_ever += 1
            self.total_females_ever += 1

        # 统计数据
        self.yearly_stats = []

    def run(self):
        """运行模拟"""
        for year in tqdm(
            range(self.current_year + 1, self.end_year + 1), desc="Simulating years"
        ):
            self.current_year = year

            # 处理死亡
            self.process_deaths()

            # 处理婚姻
            self.process_marriages()

            # 处理生育
            self.process_births()

            # 收集统计数据
            self.collect_stats()

        self.display_results()

    def process_deaths(self):
        """处理人口死亡(移除超过60岁的人)"""
        # 移除已经达到寿命上限的人
        self.males = deque([m for m in self.males if m.is_alive(self.current_year)])
        self.females = deque([f for f in self.females if f.is_alive(self.current_year)])
        self.forever_alone_males = deque(
            [m for m in self.forever_alone_males if m.is_alive(self.current_year)]
        )
        self.forever_alone_females = deque(
            [f for f in self.forever_alone_females if f.is_alive(self.current_year)]
        )

        # 更新夫妻列表,只保留双方都在世的夫妻
        active_couples = deque()
        for couple in self.couples:
            if couple.is_active(self.current_year):
                active_couples.append(couple)
        self.couples = active_couples

    def process_marriages(self):
        """处理婚姻匹配,年龄大的优先"""
        # 按年龄排序(年龄大的优先)
        marriageable_males = sorted(
            [m for m in self.males if m.can_marry(self.current_year)],
            key=lambda x: self.current_year - x.birth_year,
            reverse=True,
        )
        marriageable_females = sorted(
            [f for f in self.females if f.can_marry(self.current_year)],
            key=lambda x: self.current_year - x.birth_year,
            reverse=True,
        )

        # 匹配夫妻
        while marriageable_males and marriageable_females:
            husband = marriageable_males.pop(0)
            wife = marriageable_females.pop(0)

            # 从单身池中移除
            if husband in self.males:
                self.males.remove(husband)
            if wife in self.females:
                self.females.remove(wife)

            # 创建新的夫妻
            self.couples.append(Couple(husband, wife, self.current_year))

        # 将超过45岁的人加入"永远单身"列表
        for m in list(self.males):
            if m.age(self.current_year) >= 45 and not m.married:
                self.males.remove(m)
                self.forever_alone_males.append(m)
                self.historical_forever_alone_males += 1

        for f in list(self.females):
            if f.age(self.current_year) >= 45 and not f.married:
                self.females.remove(f)
                self.forever_alone_females.append(f)
                self.historical_forever_alone_females += 1

    def process_births(self):
        """处理生育"""
        for couple in self.couples:
            if couple.can_have_children(self.current_year):
                child = couple.have_child(self.current_year)
                if child.gender == "M":
                    self.males.append(child)
                    self.total_males_ever += 1
                else:
                    self.females.append(child)
                    self.total_females_ever += 1

    def collect_stats(self):
        """收集年度统计数据"""
        # 计算总人口
        total_males = (
            len(self.males)
            + len(self.forever_alone_males)
            + sum(1 for c in self.couples if c.husband.is_alive(self.current_year))
        )
        total_females = (
            len(self.females)
            + len(self.forever_alone_females)
            + sum(1 for c in self.couples if c.wife.is_alive(self.current_year))
        )

        # 计算未婚人口
        unmarried_males = len(self.males) + len(self.forever_alone_males)
        unmarried_females = len(self.females) + len(self.forever_alone_females)

        # 存储统计数据
        self.yearly_stats.append(
            {
                "year": self.current_year,
                "total_males": total_males,
                "total_females": total_females,
                "unmarried_males": unmarried_males,
                "unmarried_females": unmarried_females,
                "couples": len(self.couples),
                "forever_alone_males": len(self.forever_alone_males),
                "forever_alone_females": len(self.forever_alone_females),
            }
        )

    def display_results(self):
        """显示最终结果"""
        final_stats = self.yearly_stats[-1]

        print("\n=== Final Statistics ===")
        print(f"Year: {final_stats['year']}")
        print(
            f"Total population: {final_stats['total_males'] + final_stats['total_females']}"
        )
        print(
            f"Gender ratio (M:F): {final_stats['total_males']} : {final_stats['total_females']} = {final_stats['total_males'] / final_stats['total_females']:.2f}"
        )
        print(
            f"Unmarried gender ratio (M:F): {final_stats['unmarried_males']} : {final_stats['unmarried_females']} = {final_stats['unmarried_males'] / final_stats['unmarried_females'] if final_stats['unmarried_females'] > 0 else 'inf':.2f}"
        )
        print(f"Forever alone males: {final_stats['forever_alone_males']}")
        print(f"Forever alone females: {final_stats['forever_alone_females']}")
        print(f"Active couples: {final_stats['couples']}")

        # 新增:输出历史总人口及比例
        print("\n--- Historical Population (All Ever Born) ---")
        print(f"Total males ever born: {self.total_males_ever}")
        print(f"Total females ever born: {self.total_females_ever}")
        if self.total_females_ever > 0:
            print(f"Historical gender ratio (M:F): {self.total_males_ever} : {self.total_females_ever} = {self.total_males_ever / self.total_females_ever:.2f}")
        else:
            print("Historical gender ratio (M:F): inf")
        print(f"Historical Forever Alone Males: {self.historical_forever_alone_males}")
        print(f"Historical Forever Alone Females: {self.historical_forever_alone_females}")

        # 绘制性别比例随时间变化的图表
        self.plot_results()

    def plot_results(self):
        """绘制结果图表"""
        years = [stat["year"] for stat in self.yearly_stats]
        gender_ratios = [
            (
                stat["total_males"] / stat["total_females"]
                if stat["total_females"] > 0
                else 0
            )
            for stat in self.yearly_stats
        ]
        unmarried_ratios = [
            (
                stat["unmarried_males"] / stat["unmarried_females"]
                if stat["unmarried_females"] > 0
                else 0
            )
            for stat in self.yearly_stats
        ]

        plt.figure(figsize=(12, 6))

        # 绘制总体性别比例图
        plt.subplot(1, 2, 1)
        plt.plot(years, gender_ratios, "b-", label="Gender Ratio (M:F)")
        plt.axhline(y=1.0, color="r", linestyle="--", label="Equal Ratio")
        plt.title("Gender Ratio Over Time")
        plt.xlabel("Year")
        plt.ylabel("Ratio (M:F)")
        plt.legend()

        # 绘制未婚人口性别比例图
        plt.subplot(1, 2, 2)
        plt.plot(years, unmarried_ratios, "g-", label="Unmarried Ratio (M:F)")
        plt.axhline(y=1.0, color="r", linestyle="--", label="Equal Ratio")
        plt.title("Unmarried Gender Ratio Over Time")
        plt.xlabel("Year")
        plt.ylabel("Ratio (M:F)")
        plt.legend()

        plt.tight_layout()
        plt.savefig("gender_ratio_simulation.png")
        plt.show()


if __name__ == "__main__":
    # 运行模拟
    sim = Simulation(initial_population=10000, years=200, start_year=2014)
    sim.run()

参考结果:

=== Final Statistics ===
Year: 2214
Total population: 14688
Gender ratio (M:F): 7330 : 7358 = 1.00
Unmarried gender ratio (M:F): 2383 : 2411 = 0.99
Forever alone males: 0
Forever alone females: 0
Active couples: 4947

--- Historical Population (All Ever Born) ---
Total males ever born: 32943
Total females ever born: 32884
Historical gender ratio (M:F): 32943 : 32884 = 1.00
Historical Forever Alone Males: 1354
Historical Forever Alone Females: 1263
Categories: Life

Yu

Ideals are like the stars: we never reach them, but like the mariners of the sea, we chart our course by them.

19 Comments

reizhi · March 27, 2014 at 23:50

Google Chrome 33.0.1750.154 Google Chrome 33.0.1750.154 Windows 8.1 x64 Edition Windows 8.1 x64 Edition

感觉不能这么无限制生下去,无论是男是女最多2胎

    yu · March 28, 2014 at 10:12

    Google Chrome 33.0.1750.152 Google Chrome 33.0.1750.152 GNU/Linux x64 GNU/Linux x64

    @reizhi 我那个模拟器模拟上几千年后,结果是人类灭绝的。
    因为两个人生下子代个数的期望E = 1 * 1/2 + 2 * 1/4 + 3 * 1/8 + 4 * 1 / 16 + 5 * 1 / 16 约为1.93 < 2 所以从趋势上说是整体下滑的。 而现实中,即便每家都可以生两胎,依然会整体下滑,比如意外啦,比如不想要两个孩子,乃至一个都不要的,都是可能的。 为什么还有人口膨胀的现象呢?因为当无法给子代良好的环境,却以为&期望基因多组合几次,能多弄几个份额就好 -- 就好像猪肉,不好吃没太大问题,抗不住它量大。 所以无论是超生还是计生,个人觉得都没太多用,没有良好的教育环境,没有舒适可靠的生活,人命就会变贱,膨胀自然而然,反之则反之。

      reizhi · March 28, 2014 at 14:11

      Google Chrome 33.0.1750.154 Google Chrome 33.0.1750.154 Windows 8.1 x64 Edition Windows 8.1 x64 Edition

      @Yu Jing 果然是死理性派

海蓝 · March 6, 2014 at 10:00

Google Chrome 29.0.1547.57 Google Chrome 29.0.1547.57 Windows 7 x64 Edition Windows 7 x64 Edition

难道不是因为重男轻女才导致失衡的吗

    yu · March 6, 2014 at 11:41

    Google Chrome 33.0.1750.146 Google Chrome 33.0.1750.146 GNU/Linux x64 GNU/Linux x64

    @海蓝 造成男女失衡的原因可以有很多,只是说,从某个角度看,是否可能造成比较大的影响。这只是个数学题目而已。

    至于具体是什么造成比例失衡什么的,这个是人文类问题,我就不去抢别人的话筒了

      海蓝 · May 8, 2014 at 17:15

      Google Chrome 29.0.1547.57 Google Chrome 29.0.1547.57 Windows 7 x64 Edition Windows 7 x64 Edition

      @Yu Jing 好吧 原来是技术派

orbea jersey · March 5, 2014 at 10:42

Sogou Explorer Sogou Explorer Windows 7 x64 Edition Windows 7 x64 Edition

哈哈,楼主想得真多,现在的社会本来就男女比例失调了啊

rock racing cyclisme · February 26, 2014 at 10:33

Google Chrome 30.0.1599.101 Google Chrome 30.0.1599.101 Windows 7 x64 Edition Windows 7 x64 Edition

有的也是家庭因素,有重男轻女的现在,才导致失衡。

爱浮夸 · February 21, 2014 at 18:09

Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 Windows 7 Windows 7

感觉这东西还是交给天去算吧。

Leniy · February 21, 2014 at 08:07

Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 Windows 7 Windows 7

第一队列,生一胎的。男女概率均等。
生二胎的,男女概率均等。

    Leniy · February 21, 2014 at 08:07

    Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 Windows 7 Windows 7

    @Leniy 只要不出生前根据性别堕胎,那么每一次出生均是独立事件。男女概率不受影响。

    生男生女是均等事件,控制谁可以生二胎是另一个事件,二者不相关。

      yu · February 21, 2014 at 12:22

      Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 Windows 8.1 x64 Edition Windows 8.1 x64 Edition

      @Leniy

      当时说这问题的是某个博士生,我也没当时就搞明白。。(好水)

        Vespa · February 21, 2014 at 13:24

        Sogou Explorer Sogou Explorer Windows XP Windows XP

        @Yu Jing 我多年前在中学生读物上看到过这个东西。。

          yu · February 21, 2014 at 13:27

          Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 Windows 8.1 x64 Edition Windows 8.1 x64 Edition

          @Vespa 我读书少了….
          小时候政治课就看到这个题目的,一直没有思考过以为是正确的,寒假才正儿八经的数学上考虑这个是否正确。。囧

            Vespa · February 21, 2014 at 13:33

            Sogou Explorer Sogou Explorer Windows XP Windows XP

            @Yu Jing 不过这种问题仿真我是绝对不会用C/C++来做的,只是验证一个东西付出的代码量太大了。。

              yu · February 21, 2014 at 15:01

              Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 GNU/Linux x64 GNU/Linux x64

              @Vespa 其实就是几个队列而已,当时寒假在家没有网啥都干不了,熟悉下手感而已

              Leniy · February 23, 2014 at 08:24

              Internet Explorer 11.0 Internet Explorer 11.0 Windows 7 Windows 7

              @Vespa 用来练手还是不错的

            Vespa · February 23, 2014 at 16:46

            Sogou Explorer Sogou Explorer Windows XP Windows XP

            @Yu Jing 模型假设是不是有点冗余。。有些假设并不涉及问题本质。所以才会出现队列这种数据结构。。

              yu · February 23, 2014 at 17:04

              Google Chrome 32.0.1700.107 Google Chrome 32.0.1700.107 GNU/Linux x64 GNU/Linux x64

              @Vespa 对”这样是否会导致男女比例失衡?”这个问题本身说,我的确多统计了一些信息。
              不过在写的时候,当时还有一些其它语境,比如”终身未娶/未嫁和成功完婚的数量”,因为我当时本想嘲讽某人”这政策的推广是你Forever Alone的重要原因之一”(好吧,然后自己算了下不得不思考当时为啥脑子没了)

Leave a Reply to Vespa Cancel reply

Your email address will not be published. Required fields are marked *