6

I have a dictionary of people with their respective births and deaths.

I want to figure out which year had the most people alive.

My currently algorithm appends every year lived by every person into an array and then return the highest occurence. I have a hunch that there is a much neater way to achieve this.

Here is my makeshift brute implementation:

var people = [ "Nicolas": (birth: 1900, death: 1975),
                "Vladimir": (birth: 1970, death: 2000),
                "Julius": (birth: 1950, death: 1985),
                "Alexander": (birth: 1900, death: 1920),
                "Obama": (birth: 1910, death: 1920),
                "George": (birth: 1915, death: 1920),
                "Benjamin": (birth:  1919, death: 1925)]

var yearsArray = [Int]()
var yearOccurrences: [Int:Int] = [:]

for life in people.values {
    var birth = life.birth
    var death = life.death

    for year in birth..<death {
        yearsArray.append(year)
    }
}
for year in yearsArray {
    yearOccurrences[year] = (yearOccurrences[year] ?? 0) + 1
}

(yearOccurrences as! NSDictionary).allKeysForObject(yearOccurrences.values.maxElement()!)

Expected results are 1919 and 1920.

Looking for a more concise and elegant solution in terms of either code or process

  • Look into the `NSCountedSet` class as one option. – rmaddy Oct 01 '15 at 21:07
  • 2
    Why is the result 1919? 1920 has just as many as 1919. – rmaddy Oct 01 '15 at 21:08
  • That's what your code returned but look at the data. 1920 is just as viable as 1919. – rmaddy Oct 01 '15 at 21:44
  • 3
    BTW - since you have working code and you are looking for what is essentially a code review, you should post this question at http:.//codereview.stackexchange.com instead of here. – rmaddy Oct 01 '15 at 21:59

6 Answers6

4

The actual values (years) you have to inspect are the years when the number changes, that is, only the years when someone is born of when someone dies.

Let's represent the change by a structure (you could also use a named tuple).

struct PeopleAliveChange {
   var year: Int
   var change: Int
}

where the change will be either +1 or -1.

Let's generate an array of those structures from your data, e.g. using a reduce call, but you could use a simple for iteration.

let changes = people.values.reduce([PeopleAliveChange]()) {
    aggregate, personLife in

    let birthChange = PeopleAliveChange(year: personLife.birth, change: 1)
    let deathChange = PeopleAliveChange(year: personLife.death, change: -1)

    return aggregate + [birthChange, deathChange]
}

and let's sort the changes by date (year):

let sortedChanges = changes.sort { $0.year < $1.year }

Now, we have a very nice structure that will enable us a lot of operations. To get the year where most people were alive you can for example

var numPeopleAlive = 0;

var maxPeopleAlive: Int = 0
var yearWithMaxPeopleAlive: Int? = nil

for lifeChange in sortedChanges {
    numPeopleAlive += lifeChange.change
    if (numPeopleAlive > maxPeopleAlive) {
        maxPeopleAlive = numPeopleAlive
        yearWithMaxPeopleAlive = lifeChange.year
    }
}

print("Most people were alive in \(yearWithMaxPeopleAlive)")

To handle whole intervals, we can track in this way

var maxPeopleAlive: Int = 0
var currentMaxIntervalStart: Int? = nil
var intervalsWithMostPeopleAlive: [(Int, Int)] = []

for lifeChange in sortedChanges {
    if (currentMaxIntervalStart != nil && lifeChange.change < 0) {
        intervalsWithMostPeopleAlive.append((currentMaxIntervalStart!, lifeChange.year))
        currentMaxIntervalStart = nil
    }

    numPeopleAlive += lifeChange.change

    if (numPeopleAlive > maxPeopleAlive) {
        maxPeopleAlive = numPeopleAlive
        intervalsWithMostPeopleAlive = []
        currentMaxIntervalStart = lifeChange.year
    }
}

Of course, we should also make sure that birth and death for the same person is sorted correctly (which is a problem if the person is born and dies in the same year).

This is a simple problem and can be solved in multiple ways. The important thing is that the dates of birth and death are the only ones you want to really care about.

Your solution is not actually bad. It's a valid solution.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Am I following `.reduce()` correctly? 1) Since the input array is `people.values` we lose their names entirely, right? 2) This statement: `[PeopleAliveChange]()` is both creating and defining the closure's return Array, right? 3) The Type for `aggregate` (aka `$0` here?) is inferred from that return Type? 4) The Type for `personLife` (aka $1 here?) is inferred from the tuple `(elementName: Int, elementName: Int)`? 5) Each `personLife` tuple is passed in in turn; each 1 is used to create 2 new `PeopleAliveChange` struct values that are added to the aggregate array? Thanks! – mc01 Oct 01 '15 at 22:54
  • @mc01 1. The names of people don't provide any data to the algorithm. 2. yes 3. yes 4. yes 5. yes – Sulthan Oct 01 '15 at 23:03
  • Excellent - Thanks! I still find Swift's type inference & shorthand a bit confusing, so thanks for clarifying. – mc01 Oct 01 '15 at 23:42
1

Here's one approach:

var people = [ "Nicolas": (birth: 1900, death: 1975),
    "Vladimir": (birth: 1970, death: 2000),
    "Julius": (birth: 1950, death: 1985),
    "Alexander": (birth: 1900, death: 1920),
    "Obama": (birth: 1910, death: 1920),
    "George": (birth: 1915, death: 1920),
    "Benjamin": (birth:  1919, death: 1925)]

let minYear = people.map { (key, value) in value.birth }.minElement()!
let maxYear = people.map { (key, value) in value.death }.maxElement()!

let bestYear = (minYear...maxYear).map {
    year -> (Int, Int) in

    let alive = people.filter {
        (key, value) -> Bool in
        return value.birth <= year && year < value.death
    }.count
    return (year, alive)
    }.maxElement {
        $0.1 < $1.1
    }!

print(bestYear) // (1919, 5)
Code Different
  • 90,614
  • 16
  • 144
  • 163
1

You can simplify your solution by doing the yearOccurences and maximum in the first pass through:

var people = [ "Nicolas": (birth: 1900, death: 1975),
               "Vladimir": (birth: 1970, death: 2000),
               "Julius": (birth: 1950, death: 1985),
               "Alexander": (birth: 1900, death: 1920),
               "Obama": (birth: 1910, death: 1920),
               "George": (birth: 1915, death: 1920),
               "Benjamin": (birth:  1919, death: 1925),
               "Donald": (birth:  1918, death: 1918)]

var yearsArray: [Int] = []
var yearOccurrences: [Int:Int] = [:]
var maxPop = 0

for person in people.values {
    let birth = person.birth
    let death = person.death

    for year in birth...death {
        let count = (yearOccurrences[year] ?? 0) + 1
        yearOccurrences[year] = count

        if count > maxPop {
            maxPop = count
            // Clear the years
            yearsArray = [year]
        } else if count == maxPop {
            // Append the year
            yearsArray.append(year)
        }
    }
}

print ("Max population \(maxPop) in year\(yearsArray.count > 0 ? "s" : ""): \(yearsArray)")

Max population 5 in years: [1918, 1919, 1920]

Also note- the loop from birth...death is inclusive. A person is assumed to be alive the year they died. It also solves the problem of a person dying the same year they were born (e.g. Donald above).

This has complexity of O(N*Y) where N is number of people and Y is the max lifespan of a person. Assuming Y is finite, we can reduce this to just O(N).

Funktional
  • 4,083
  • 1
  • 30
  • 27
0

Here's a shorter solution.

First, get the start and end years:

let startYear = people.map { (name, dates) in dates.birth }.minElement()!
let endYear = people.map { (name, dates) in dates.death }.maxElement()!

Create a range, map them to an array of tuples (year, number of people alive), and then sort that array:

let aliveByYear = (startYear...endYear)
    .map {
        year -> (Int, Int) in
        return (year, people.filter { $0.1.birth..<$0.1.death ~= year }.count) }
    .sort { $0.1 > $1.1 }

print("\(aliveByYear.first!.0) had the most people alive at once: \(aliveByYear.first!.1) people.")

The output is:

1919 had the most people alive at once: 5 people.

A few notes:

  • The order of years that have the same # of people alive is not stable (may change between runs)
  • ~= is the pattern matching operator (see more usage here)
  • There are 3 force operators (!) which will make this crash if people is empty
  • This solution is much less efficient than Sulthan's answer above. However, I would rewrite this answer like so, using modern Swift syntax, removing semicolons, and without the custom struct:

    var counter = 0
    var winner : (year : Int, people : Int) = (0, 0)
    
    let changes = people
        .values
        .reduce([(year : Int, change : Int)]()) { $0 + [($1.birth, 1), ($1.death, -1)] }
        .sort { $0.year < $1.year }
        .map {
            counter += $0.change
            if (counter > winner.people) {
                winner = ($0.year, counter)
            }
        }
    
    print("Most (\(winner.people)) people were alive in \(winner.year)")
    

    which outputs:

    Most (5) people were alive in 1919

Community
  • 1
  • 1
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
0

Since there is no mention of programming language. Here is my solution in Ruby:

Sol 1:

class Census
  attr_reader :data
  def initialize(data)
    @data = data
  end

  def year_with_maximum_population
    hash = Hash.new(0)
    data.each do |lifespan|
      lifespan[0].upto(lifespan[1]).each do |year|
        hash[year] += 1
      end
    end

    # find the max value in the hash
    hash.key(hash.values.max)
  end
end

Sol 2:

class Census
  attr_reader :data
  def initialize(data)
    @data = data
  end

  def year_with_maximum_population

    years = Hash.new(0)
    data.each do |data|
      years[data[0]] += 1 if years[data[0]]
      years[data[1]] -= 1 if years[data[1]]
    end

    alive = 0
    max_alive = 0
    the_year = data[0][0]

    sorted_years = years.keys.sort
    sorted_years.each do |sorted_year|
      alive += years[sorted_year]
      if alive > max_alive
        max_alive = alive
        the_year = sorted_year
      end
    end
    the_year
  end
end

And then you can call as below:

data = [
  [1900, 1975],
  [1970, 2000],
  [1950, 1985],
  [1900, 1920],
  [1910, 1920],
  [1915, 1920],
  [1919, 1925]
]
obj = Census.new(data)
obj.year_with_maximum_population

Note: Above solutions'll give first year with maximum population. Let me know if anything seems wrong.

Mayuresh Srivastava
  • 1,332
  • 17
  • 24
0

JavaScript solution: Find a peak year.

data = [
    [1970, 2000],
    [1919, 1925],
    [1900, 1975],
    [1950, 1985],
    [1900, 1920],
    [1910, 1920],
    [1915, 1920],
]

const peakYearMax = data => {
    let hash = {}
    let max = 0
    data.forEach(d=>{
        let birth = d[0]
        while(birth <= d[1]){
            hash[birth] ? hash[birth]++ : hash[birth] = 1
            if (hash[birth] > max) max = hash[birth]
            birth++
        }
    })
    return max
}

console.log(peakYearMax(data))
7urkm3n
  • 6,054
  • 4
  • 29
  • 46