【问题标题】:Find the year with the most number of people alive in Python在 Python 中找到最多人活着的年份
【发布时间】:2015-07-20 17:15:42
【问题描述】:

给定一个包含出生和结束年份的人员列表(都在19002000 之间),找出活着的人数最多的年份。

这是我有点蛮力的解决方案:

def most_populated(population, single=True):
    years = dict()
    for person in population:
        for year in xrange(person[0], person[1]):
            if year in years:
                years[year] += 1
            else:
                years[year] = 0
    return max(years, key=years.get) if single else \
           [key for key, val in years.iteritems() if val == max(years.values())]

print most_populated([(1920, 1939), (1911, 1944),
                      (1920, 1955), (1938, 1939)])
print most_populated([(1920, 1939), (1911, 1944),
                      (1920, 1955), (1938, 1939), (1937, 1940)], False)

我正在尝试在Python 中找到解决此问题的更有效方法。两者 - readabilityefficiency 都很重要。此外,由于某种原因,我的代码不会打印 [1938, 1939] 而它应该打印。

更新

输入是元组的list,其中元组的第一个元素是人出生时的yeartuple 的第二个元素是死亡年份。

更新 2

结束年份(元组的第二部分)和人活着的年份一样重要(所以如果这个人在Sept 1939 死去(我们不关心月份),他实际上在 1939 年还活着,至少一部分)。这应该可以修复结果中缺少的 1939'。

最佳解决方案?

虽然可读性有利于@joran-beasley,但对于更大的输入,@njzk2 提供了最有效的算法。感谢@hannes-ovrén 在IPython notebook on Gist提供分析

【问题讨论】:

  • years[year] = 1 对于初学者
  • @njzk2 从技术上讲,从可读性的角度来看,我认为您是正确的,但是每年[n] 都会被初始化为 0,并且仍然会正确生成“most_populated”年份。如果计数被退回,我同意你的看法,但只退回年份。
  • 假设你有 10 个人在 2000 年出生,他们都在 2080 年死去。然后还有另外 10 个人在 2080 年出生,他们在 2160 年死去。你想要最大的活着的人吗? 10 或 20 或 15 还是别的什么?您对 2080 年的情况有何假设?
  • 您的方法(对单个时间线进行建模)会奏效,但使用动态规划 的算法更为有效。 提示:Y 年初还活着的人 = Y 年前出生的总人数 - Y 年前死亡的总人数。请注意,(出生、死亡)的组合并不重要,只有日期。
  • 对于一些相同想法的更难的问题,请参阅 Google Code Jam 的 The Great Wall code.google.com/codejam/contest/2437488/dashboard#s=p2

标签: python algorithm


【解决方案1】:

我刚刚想到的另一个解决方案:

  • 创建 2 个表,birthdatesdeathdates
  • 在这些表中累积出生日期和死亡日期。
  • 浏览这些表格以累积当时在世的人数。

总复杂度为O(n)

实施

from collections import Counter

def most_populated(population, single=True):
    birth = map(lambda x: x[0], population)
    death = map(lambda x: x[1] + 1, population)
    b = Counter(birth)
    d = Counter(death)
    alive = 0
    years = {}
    for year in range(min(birth), max(death) + 1):
        alive = alive + b[year] - d[year]
        years[year] = alive
    return max(years, key=years.get) if single else \
           [key for key, val in years.iteritems() if val == max(years.values())]

更好

from collections import Counter
from itertools import accumulate
import operator

def most_populated(population, single=True):
    delta = Counter(x[0] for x in population)
    delta.subtract(Counter(x[1]+1 for x in population))
    start, end = min(delta.keys()), max(delta.keys())
    years = list(accumulate(delta[year] for year in range(start, end)))
    return max(enumerate(years), key=operator.itemgetter(1))[0] + start if single else \
           [i + start for i, val in enumerate(years) if val == max(years)]

【讨论】:

  • 使用表格而不是计数器,我们可以使用 itertools.accumulate 在一次调用中计算每年的值
  • 整洁。这是我暗示的动态规划算法。
  • 所以如果我用most_populated([(-10025, -10000), (1960, 2010)]) 调用它,对于 2 个人来说,它会进行 >10200 次迭代吗?这太可怕了!
【解决方案2】:
>>> from collections import Counter
>>> from itertools import chain
>>> def most_pop(pop):
...     pop_flat = chain.from_iterable(range(i,j+1) for i,j in pop)
...     return Counter(pop_flat).most_common()
...
>>> most_pop([(1920, 1939), (1911, 1944), (1920, 1955), (1938, 1939)])[0]

【讨论】:

  • xrange(i,j+1) for i,j in pop 在 python2 和 range(i,j+1) for i,j in pop 更好!
  • 我喜欢这个答案,因为它简短、清晰且易读,但要注意复杂性,如果您要处理的人数或年数更多。
  • @oski86,喜欢基准测试。看到不同大小的输入列表的结果也会很有趣。我猜我的 numpy 版本由于 people_alive 数组的构造而受到小 N 的影响。作为记录,我确实认为这个答案应该是公认的。我喜欢它:)
  • 实际上,我收回了这一点。该算法是迄今为止尝试过的算法中最慢的。对于非常小的人口规模来说,它恰好很快。对于较大的人群,njzk2_var2 是最快的。在此处查看基准图:gist.github.com/hovren/bf5335ba45cd7eee811e hannes_v2 算法与我的旧算法完全相同,但没有命名元组,因为这增加了一些开销。
  • 这就是我昨天想尝试的(提供更大的 rand 测试数据),但它已经是凌晨了。我不确定是否要更改已接受的答案,但我有一个想法,我将更新问题中的内容,分别为输入的小数据和大数据提供一个成功的解决方案。
【解决方案3】:

我会这样:

  • 按出生年份对人员进行排序(unborn 列表)
  • 从第一胎开始
    • 将该人放入alive 列表中
    • 使用按死亡日期的插入排序(列表保持排序,因此使用二分搜索)
    • 直到你遇到一个不是那年出生的人
  • 然后,从alive 列表中最先死亡的人开始,将其从列表中移除。
  • alive 列表的大小放入字典中
  • 增加年份
  • 循环直到unbornalive 列表为空

复杂度应该在O((m + n) * log(m))左右(每年只考虑一次,每个人只考虑两次,乘以alive列表中的插入成本)

实施

from bisect import insort

def most_populated(population, single=True):
    years = dict()
    unborn = sorted(population, key=lambda x: -x[0])
    alive = []
    dead = []
    for year in range(unborn[-1][0], max(population, key=lambda x: x[1])[1] + 1):
        while unborn and unborn[-1][0] == year:
            insort(alive, -unborn.pop()[1])
        while alive and alive[-1] == -(year - 1):
            dead.append(-alive.pop())
        years[year] = len(alive)
    return max(years, key=years.get) if single else \
           [key for key, val in years.iteritems() if val == max(years.values())]

【讨论】:

  • 由于insort,这对于大量人口来说会非常慢。
  • @DSM: insort 是O(n),这使得我最初的复杂度计算错误,但是这在复杂度上相当于考虑每个人的每一年。
  • @nkjz2:我想我会将输入数据视为一个事件队列,出生为 (t, 1),死亡为 (t, -1)。您可以在 N log N 中对其进行排序,然后简单地循环遍历每个事件,增加或减少总人口,跟踪您想要的任何内容。
  • @DSM:是的,我意识到可以做到这一点。此外,由于它是一组相对已知的日期,您可以使用桶排序 (O(n)) 或计数器,就像我在另一个答案中开始的那样。
【解决方案4】:

我们也可以使用numpy slicing,相当简洁,应该也相当高效:

import numpy as np
from collections import namedtuple

Person = namedtuple('Person', ('birth', 'death'))
people = [Person(1900,2000), Person(1950,1960), Person(1955, 1959)]

START_YEAR = 1900
END_YEAR = 2000
people_alive = np.zeros(END_YEAR - START_YEAR + 1) # Alive each year

for p in people:
    a = p.birth - START_YEAR
    b = p.death - START_YEAR + 1 # include year of death
    people_alive[a:b] += 1

# Find indexes of maximum aliveness and convert to year
most_alive = np.flatnonzero(people_alive == people_alive.max()) + START_YEAR

EDIT 似乎 namedtuple 增加了一些开销,所以为了加快速度,删除 namedtuple 并执行 for birth, death in people: 代替。

【讨论】:

  • 你真的认为people_alive[a:b] += 1O(1)
  • 这是非常真实的。不知道我在想什么。 :)
  • Timeit 10000 times 似乎返回了一个恒定的执行时间,可能在输入上有更大的测试数据时它可能工作得很好,但它需要进一步的测试。 +1 使用 numpy
  • 考虑到a:b 范围的最大长度(假设是正常人)大约为 100,您可以认为它是 O(1)。
【解决方案5】:
  1. 只需将出生和死亡年份放入字典即可。如果是出生,则将该值增加 1。反之亦然。
  2. 按键对字典进行排序,并通过读取当前存活人数进行迭代。
  3. 按照 'maxAlive' 和 'theYear' 获得最高数字的第一年

    years = {} 
    
    for p in people:
        if p.birth in years:
            years[p.birth] += 1
        else:
            years[p.birth] = 1
    
        if p.death in years:
            years[p.death] -= 1
        else:
            years[p.death] = -1
    
    alive = 0
    maxAlive = 0
    theYear = people[0].birth
    for year in sorted(years):
        alive += years[year]
        if alive > maxAlive:
            maxAlive = alive
            theYear = year
    

【讨论】:

    【解决方案6】:

    不导入任何东西,并使用一个类来提高可读性,这是我的解决方案。让我知道你的想法!我还为 getMaxBirthYear 制作了一个单独的函数,以防你在面试中并且有人希望你编写代码而不是使用内置函数(我使用了它们:))

    class Person:
      def __init__(self, birth=None, death=None):
        self.birth=birth
        self.death=death
    
    def getPopulationPeak(people):
      maxBirthYear = getMaxBirthYear(people)
      deltas = getDeltas(people, maxBirthYear)
      currentSum = 0 
      maxSum = 0
      maxYear = 0
      for year in sorted(deltas.keys()):
        currentSum += deltas[year]
        if currentSum > maxSum:
          maxSum = currentSum
          maxYear = year
      return maxYear, maxSum
    
    def getMaxBirthYear(people):
      return max(people, key=lambda x: x.birth).birth
    
    def getDeltas(people, maxBirthYear):
      deltas = dict()
      for person in people:
        if person.birth in deltas.keys():
          deltas[person.birth] += 1
        else:
          deltas[person.birth] = 1
        if person.death + 1 in deltas.keys():
          deltas[person.death + 1] -= 1
        elif person.death + 1 not in deltas.keys() and person.death <= maxBirthYear: # We can skip deaths after the last birth year
          deltas[person.death + 1] = -1
      return deltas
    
    testPeople = [
      Person(1750,1802),
      Person(2000,2010),
      Person(1645,1760),
      Person(1985,2002),
      Person(2000,2050),
      Person(2005,2080),
    ]
    
    print(getPopulationPeak(testPeople))
    

    【讨论】:

      【解决方案7】:

      这个怎么样:

      def max_pop(pop):
          p = 0; max = (0,0)
          for y,i in sorted(chain.from_iterable([((b,1), (d+1,-1)) for b,d in pop])):
              p += i
              if p > max[1]: max=(y,p)
          return max
      

      它不受年份跨度的影响,但在 |pop| 中为 nlogn (除非你推出一个基数排序,在一千年的跨度内约为 10n 并且对于 |pop|>1000 应该更快)。不能两者兼得。一个非常通用的解决方案必须首先扫描并根据测量的年份跨度和 |pop| 决定使用哪种算法。

      【讨论】:

        【解决方案8】:

        我的回答

        import java.util.LinkedHashMap;
        import java.util.LinkedList;
        import java.util.List;
        import java.util.Map;
        
        public class AlogrimVarsta {
        
            public static void main(String args[]) {
        
                int startYear = 1890;
                int stopYear = 2000;
        
                List<Person> listPerson = new LinkedList<>();
        
                listPerson.add(new Person(1910, 1940));
                listPerson.add(new Person(1920, 1935));
                listPerson.add(new Person(1900, 1950));
                listPerson.add(new Person(1890, 1920));
                listPerson.add(new Person(1890, 2000));
                listPerson.add(new Person(1945, 2000));
        
                Map<Integer, Integer> mapPersoaneCareAuTrait = new LinkedHashMap<>();
        
                for (int x = startYear; x <= stopYear; x++) {
                    mapPersoaneCareAuTrait.put(x, 0);
                }
        
                for (int x = startYear; x <= stopYear; x++) {
                    for (Person per : listPerson) {
        
                        int value = mapPersoaneCareAuTrait.get(x);
        
                        if (per.getBorn() == x) {
                            mapPersoaneCareAuTrait.put(x, value + 1);
                            continue;
                        }
        
                        if (per.getDie() == x) {
                            mapPersoaneCareAuTrait.put(x, value + 1);
                            continue;
                        }
        
                        if ((per.getDie() - per.getBorn() > per.getDie() - x) && (per.getDie() - x > 0)) {
                            mapPersoaneCareAuTrait.put(x, value + 1);
                            continue;
                        }
        
                    }
                }
        
                for (Map.Entry<Integer, Integer> mapEntry : mapPersoaneCareAuTrait.entrySet()) {
                    System.out.println("an " + mapEntry.getKey() + " numar " + mapEntry.getValue());            
                }
        
            }
        
            static class Person {
                final private int born;
                final private int die;
        
                public Person(int pBorn, int pDie) {
                    die = pDie;
                    born = pBorn;
                }
        
                public int getBorn() {
                    return born;
                }
        
                public int getDie() {
                    return die;
                }
            }
        }
        

        【讨论】:

        • 这是一个 Java 答案,OP 要求提供 Python 解决方案。请阅读How do I write a good answer?
        • 我不明白第二个 if 语句的用途,你为什么要在死亡年份加上 +1?不过,第一个 if 语句很清楚。
        【解决方案9】:
        l = [(1920, 1939), (1911, 1944),(1920, 1955), (1938, 1939)]
        union = set({})
        for i in l:
            temp = set(range(min(i), max(i) + 1))
            if len(union) == 0:
                union = temp
            union = temp & union
        print(union)
        

        【讨论】:

        • 欢迎来到 Stack Overflow。没有任何解释的代码转储很少有帮助。 Stack Overflow 是关于学习的,而不是提供 sn-ps 来盲目复制和粘贴。请edit您的问题并解释它如何回答所提出的具体问题。见How to Answer。在用现有答案回答老问题(这个问题超过六年)时,这一点尤其重要。
        【解决方案10】:

        下面的代码正是你所需要的。

        假设年份范围是 1900 - 2000

        算法步骤

        1. 构造一个包含 100 个整数的数组 X(全部初始化为零;如果包括 2000 年,则为 101 个整数)。
        2. 对于 N 个人中的每一个人,将 X[birth year - 1900] 加一,X[death year - 1900] 减一。
        3. 遍历 X,同时保持每个元素的总和。活着的人最多的年份是 1900 年加上总和最大的指数。

        代码(根据要求使用 Python)

        def year_with_max_population(people):
        population_changes = [0 for _ in xrange(1900, 2000)]
        for person in people:
            population_changes[person.birth_year - 1900] += 1
            population_changes[person.death_year - 1900] -= 1
        max_population = 0
        max_population_index = 0
        population = 0
        for index, population_change in enumerate(population_changes):
            population += population_change
            if population > max_population:
                max_population = population
                max_population_index = index
        return 1900 + max_population_index
        

        感谢'Brian Schmitz'here

        【讨论】:

        • 始终欢迎提供指向潜在解决方案的链接,但请add context around the link,以便您的其他用户知道它是什么以及为什么存在。 始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。 考虑到仅仅是指向外部站点的链接Why and how are some answers deleted? 的一个可能原因。
        猜你喜欢
        • 1970-01-01
        • 2020-06-07
        • 1970-01-01
        • 2021-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-01
        • 2021-12-07
        相关资源
        最近更新 更多