
Int, because why not) and a biological gender (for this exercise, I assumed Boolean). To make a population of 100_000 people, I used List builder, and made a random people, each with a different surname. Then for each iteration, I shuffle my population, limit it, and randomly assign males to females. Then each pair have a random number of children, from 0 to 5, what makes the new population. Then I print the iteration number and the number of distinct surnames.import kotlin.random.Random class Person(val surname: Int, val male: Boolean) fun newPerson(surname: Int) = Person( surname = surname, male = Random.nextBoolean() ) fun main() { var population = List(100_000) { newPerson(it) } for (i in 1..100_000) { val (males, females) = population.shuffled() .take(100_000) .partition { it.male } val pairs = males.zip(females) population = pairs.flatMap { (m, _) -> val childrenNum = Random.nextInt(6) List(childrenNum) { newPerson(m.surname) } } println("Iteration: $i") println("Surnames num: ${population.distinctBy { it.surname }.size}") } }
import kotlin.random.Random class Person(val surname: Int, val male: Boolean) fun newPerson(surname: Int) = Person( surname = surname, male = Random.nextBoolean() ) fun main() { var population = List(100_000) { newPerson(it) } var surnamesLost = mapOf<Int, Int>() var prevSurnamesCount = population.size for (i in 1..1_000_000) { val (males, females) = population.shuffled() .take(100_000) .partition { it.male } val pairs = males.zip(females) population = pairs.flatMap { (m, _) -> val childrenNum = Random.nextInt(6) List(childrenNum) { newPerson(m.surname) } } val surnamesCount = population.distinctBy { it.surname }.size val surnamesLostCount = prevSurnamesCount - surnamesCount if (surnamesLostCount > 0) { surnamesLost += i to surnamesLostCount } prevSurnamesCount = surnamesCount if (i % 1000 == 0) { println(i) println(surnamesCount) println(surnamesLost) } if (surnamesCount <= 1) { break } } println(surnamesLost) // {1=58396, 2=14483, 3=6725, 4=3858, 5=2636, 6=1904, 7=1430, // 8=1127, 9=872, 10=743, 11=660, 12=543, 13=451, 14=403, 15=351, 16=304, 17=286, // 18=241, 19=197, 20=225, 21=182, 22=163, 23=155, 24=152, 25=126, 26=107, 27=110, // 28=106, 29=91, 30=101, 31=79, 32=86, 33=90, 34=62, 35=65, 36=79, 37=54, 38=66, // 39=52, 40=49, 41=45, 42=37, 43=57, 44=55, 45=37, 46=31, 47=50, 48=39, 49=36, // 50=41, 51=35, 52=27, 53=43, 54=31, 55=30, 56=19, 57=26, 58=29, 59=17, 60=22, // 61=20, 62=19, 63=23, 64=22, 65=20, 66=22, 67=18, 68=22, 69=18, 70=22, 71=14, // 72=19, 73=22, 74=19, 75=8, 76=15, 77=16, 78=21, 79=16, 80=10, 81=22, 82=15, // 83=16, 84=10, 85=13, 86=16, 87=9, 88=9, 89=8, 90=11, 91=10, 92=17, 93=9, 94=8, // 95=8, 96=6, 97=4, 98=8, 99=6, 100=2, 101=10, 102=6, 103=10, 104=6, 105=11, 106=6, // 107=6, 108=4, 109=2, 110=4, 111=6, 112=11, 113=9, 114=17, 115=7, 116=8, 117=5, // ... // 3421=1, 3431=1, 3802=1, 4168=1, 4807=1, 4870=1, 5375=1, 5598=1, 6755=1, 6887=1, // 6919=1, 8170=1, 8180=1, 8460=1, 8788=1} }
surnamesLost variable, without recalculating it every time.println( surnamesLost.toList().sumOf { (i, num) -> i * num }.toFloat() / surnamesLost.toList().sumOf { (i, num) -> num } ) // 10.163172
val survivalRateAfter10rounds = surnamesLost .filter { (i) -> i > 10 } .mapKeys { (i) -> i - 10 } .toList() println( survivalRateAfter10rounds.sumOf { (i, num) -> i * num }.toFloat() / survivalRateAfter10rounds.sumOf { (i, num) -> num } ) // 96.74638
val survivalRateAfter100rounds = surnamesLost .filter { (i) -> i > 100 } .mapKeys { (i) -> i - 100 } .toList() println( survivalRateAfter100rounds.sumOf { (i, num) -> i * num }.toFloat() / survivalRateAfter100rounds.sumOf { (i, num) -> num } ) // 627.6806
var expectedSurvivalRates = listOf<Double>() for (iterations in 0..1000) { val survivalRate = surnamesLost .filter { (i) -> i > iterations } .mapKeys { (i) -> i - iterations } .toList() expectedSurvivalRates += survivalRate.sumOf { (i, num) -> i * num }.toDouble() / survivalRate.sumOf { (i, num) -> num } } println(expectedSurvivalRates) // [10.163171631716317, 21.93593794886527, 32.097775820303895, 41.19257134522826, 50.16815340736695, 58.42950890603591, 66.8324102047667, 74.76022109978081, 82.31929376728355, 90.0440466376163, 96.74638515674984, 103.72303853617964, 110.62009255112703, 117.64578660685677, 124.41951469583049, 131.15144361721445, 137.65008642212408, 143.71581498687135, 151.07675860594398, 158.12322595179094, 165.47330960854092, 173.3570892723181, 179.54050533993228, 185.9010035259018, 193.67329545454547, 200.1209796400118, 206.4935740514076, ..., 3714.4891304347825, 3713.4891304347825]

Estimated expected survival time, based on the numbers of survived rounds so far.
val expectedSurvivalRatesDiff = expectedSurvivalRates .zipWithNext { prev, next -> next - prev }

The growth of estimated expected survival time, based on the number of survived rounds so far. The graph is not beautiful, because it is based on a single simulation.