Home > Software design >  Elegant thresholed sum in python
Elegant thresholed sum in python

Time:11-02

A friend of mine was asked the following at a job interview for junior python dev.

A building has one elevator. Employees wait in an ordered line to take the lift up. The elevator has a maximum mass capacity. How many trips is the elevator going to do? The masses of the passengers are provided in an (ordered) list.

The following C-style loop will do the job, true, but isn't there a concise (hopefully a one-liner) for such a canonical problem?

#!/usr/bin/env python3

MAX_MASS = 280  # kg

def how_many(masses):
    trips = 0
    total_mass = 0
    for m in masses:
        if total_mass   m > MAX_MASS:
            trips = trips   1
            total_mass = 0

        total_mass = total_mass   m

    return trips


if __name__ == '__main__':
    passangers = [19, 22, 34, 33, 82, 91, 77, 31, 87]  # ordered!
    trips = how_many(passangers)
    print(trips)

A low-level language like C would use std::accumulate() to do it in a couple of lines. I would be truly surprised if python doesn't support some list comprehension or have a math library providing the solution in a single invocation.

CodePudding user response:

I guess you could abuse textwrap.

from textwrap import wrap

def how_many(masses):
    return len(wrap(' '.join('.' * (m-1) for m in masses), MAX_MASS-1))

(Might produce a wrong result when there's an employee with mass 1 kg, but that seems rather unlikely.)

CodePudding user response:

You can use itertoosl.accumulate to shorten this a bit:

from itertools import accumulate as acc

def trips(masses, mm):
    i = res = 0
    while i < len(masses):
        res  = 1
        i = next((i for i, a in enumerate(acc(masses[i:]), i) if a > mm), len(masses))
    return res
    

Algorithmically, the slices are a bother (O(K) in time and space), but you can replace masses[i:] with islice(masses, i, None).

If you allow recursion, you can get this down to:

def trips(masses, mm):
    if not (n := len(masses)):
        return 0
    return 1   trips(masses[next((i for i, a in enumerate(acc(masses)) if a > mm), n):], mm)

trips([19, 22, 34, 33, 82, 91, 77, 31, 87], 280)
# 3

Some docs:

CodePudding user response:

Here's a solution using reduce:

from functools import reduce

def how_many(masses, MAX_MASS):
    def my_func(acc, m):
        trips, tmp = acc
        return (trips   1, m) if tmp   m > MAX_MASS else (trips, tmp   m)

    return reduce(my_func, masses, (1, 0))[0]

The call to reduce returns a tuple, so we take the first element which is the number of trips.

CodePudding user response:

Using split_before from module more_itertools to split when the mass becomes too much; and using ilen from the same module to count the splits.

from more_itertools import split_before, ilen

def count_trips(seq, max_mass):
    def acc(x):
        if acc.mass   x <= max_mass:
            acc.mass  = x
            return False
        else:
            acc.mass = 0
            return True
    acc.mass = 0
    return ilen(split_before(seq, acc))

print(count_trips([19, 22, 34, 33, 82, 91, 77, 31, 87], 280))
# 2   ( [[19, 22, 34, 33, 82], [91, 77, 31, 87]] )
  • Related