# Support multiple divisors in divmod()

What do you think about idea of supporting multiple divisors in `divmod()`?

``````minutes, seconds = divmod(t, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
weeks, days = divmod(days, 7)
``````

you could write:

``````weeks, days, hours, minutes, seconds = divmod(t, 7, 24, 60, 60)
``````

Sample implementation:

``````def new_divmod(dividend, *divisors):
if not divisors:
raise TypeError('required at least one divisor')
remainders = []
for divisor in reversed(divisors):
dividend, remainder = old_divmod(dividend, divisor)
remainders.append(remainder)
return (dividend, *remainders[::-1])
``````

I am not sure about this idea. There are not so much use cases for it. On other hand, in these rare cases when you need to write similar code, the possibility to express it in one line would be nice.

It is also not clear which order of divisors is most natural: `divmod(t, 7, 24, 60, 60)` or `divmod(t, 60, 60, 24, 7)`? Big-endian or little-endian?

1 Like

IMO, the one-liner is harder to interpret than the â€śverboseâ€ť four-liner. With the current code, it is immediately easy to see what each line does.

I would not oppose the feature, though, so count me -0.

1 Like

I could have sworn this already existed. I canâ€™t work out what gave me that impression, though.

Itâ€™s basically mixed-base decomposition of a number into â€śdigitsâ€ť, which is something Iâ€™ve needed occasionally, but not particularly frequently (decomposing times is the obvious example). I agree that the one-line version is a bit tricky to read, mainly because of the question of which order the divisors go in - Iâ€™m somewhat in favour of 7,24,60,60 over 60,60,24,7, but itâ€™s not a strong preference.

Iâ€™m assuming thereâ€™s a (small) performance benefit to doing the whole thing in one call, although I doubt that would matter often enough to care.

I think Iâ€™m probably +0. Itâ€™s not a big deal, but it feels like a minor quality of life improvement.

Iâ€™d agree with 7,24,60,60. Would it make sense for that to be a tuple rather than simply additional arguments?

``````weeks, days, hours, minutes, seconds = divmod(t, (7, 24, 60, 60))
``````
2 Likes

Just a thought, Iâ€™ve certainly used that pattern before. Not sure though itâ€™s worth modifing `divmod` for. Maybe a helper in the datetime module instead?

2 Likes

Why the artificial discrimination against zero, with additional code? If you remove the

``````    if not divisors:
raise TypeError('required at least one divisor')
``````

then a call like `new_divmod(5)` works perfectly fine and gives the expected result `(5,)`.

1 Like

Naively, I would have assumed that `divmod(x, a, b, c)` means repeated application over the remainder, i.e.

``````t = 12345
divmod(t, 3600, 60) == (3, 25, 45)
``````

So the `divmod(t, 7, 24, 60, 60) ` endianness looks more natural to me, and I would find it convenient that the function not only does the repeat application, but performs the â€śinner multiplicationâ€ť of the argument for me.

1 Like

I wonder how well this idea generalises to other cases where `divmod` is used. Most commonly `divmod` is used with integers but it can be defined for any type via `__divmod__`. In SymPy (and other systems) this is used for various generalisations of the idea of Euclidean division for integers for example Euclidean division of polynomials:

``````>>> from sympy import ring, QQ
>>> R, x = ring('x', QQ)
>>> R
Polynomial ring in x over QQ with lex order
>>> divmod(x**2 - 1, x + 2)
(x - 2, 3)
``````

For nonnegative integers the definition of Euclidean division is that if `q, r = divmod(a, b)` then `a = q*b + r` and then the condition `0 <= r < b` is sufficient to uniquely define `q` and `r`. For univariate polynomials over a field the same definition applies except that the condition is changed to `deg(r) < deg(b)`.

The further generalisation of this concept to multivariate polynomials allows the notion of dividing by a set of polynomials:

``````>>> R, x, y, z = ring('x, y, z', QQ)
>>> a = x**2*y + z**3 - 1
>>> b1 = y - 3
>>> b2 = z**2 - y
>>> a.div([b1, b2])
([x**2, 0], 3*x**2 + z**3 - 1)
``````

Here division of `a` by `b1`, `b2`, â€¦ returns `q1`, `q2`, â€¦ and a remainder `r` such that `a = b1*q1 + b2*q2 + ... + r`. In general this is not necessarily uniquely defined but if the divisors `b1`, `b2`, â€¦ are a reduced Groebner basis with respect to some monomial ordering then requiring that the leading monomial of `r` be less than the leading monomials of all of the divisors (`r` is reduced wrt to the basis) makes the result unique.

I donâ€™t think that the algorithm shown for `new_divmod(a, b1, b2, ...)` would compute this correctly though.

1 Like

This approach can just be a change to `int.__divmod__`. What is unclear is how `divmod` would call `__rdivmod__` in this scenario: does it just call `tuple.__rdivmod__` (and if so what should that do)?

Yes, there are two ways:

1. Change `int.__divmod__`, `float.__divmod__`, `Decimal.__divmod__`, `Fraction.__divmod__`, and all third-party implementations of `__divmod__`. They all will contain similar boring code (but usually in C):
``````def int.__divmod__(self, other):
if type(other) is tuple:
return new_divmod(self, *other) # new_divmod() is defined above
# old code
``````
1. Implement `tuple.__rdivmod__`:
``````def tuple.__rdivmod__(self, other):
return new_divmod(other, *self) # new_divmod() is defined above
``````

I do not see obvious advantages over changing `divmod()` directly. There is a disadvantage â€“ it will not work with object which has `__divmod__` that already supports tuple. For example â€“ a `Mock` object.

The only reason is that it will allow some errors to slip through. `divmod((x, y))` will successfully return a tuple `(x, y)`. But if ignore this, yes, 1-argument `divmod()` makes sense.

Ideally anything here would be customisable via `__(r)divmod__` which would not be the case if the `divmod` function itself hard-codes a particular interpretation of the operation. It is not clear that the proposed generalisation of `divmod` is always applicable.

I think it doesnâ€™t belong as a complication of `divmod`â€™s currently clean behavior.

Mixed radix conversions have builtin operator syntax in the APL language (look in APL docs for â€śencodeâ€ť and â€śdecodeâ€ť), and in Mathematica are supplied by a MixedRadix gimmick. I recommend the Mathematica docs because they give examples of various uses.

Note that mixed radix conversions can work in both directions, and the change to `divmod` would cover only the â€śto mixed radixâ€ť case. The â€śfrom mixed radixâ€ť direction would be left unaddressed. So APL and Mathematica use a vector of bases, which can be used as-is to compute conversions in either direction (itâ€™s the operation name that changes).

5 Likes

I think this would be a small neat quality-of-life adjustment for situations where you have to work with time data, I donâ€™t see any downside. +1

Just a thought, but you could support an infinite iterable of divisors:

``````def new_divmod(dividend: int,
divisors: Iterable[int]
) -> tuple[int, tuple[int, ...]]:
remainders = []
for divisor in divisors:
if dividend == 0:
break
dividend, remainder = old_divmod(dividend, divisor)
remainders.append(remainder)
return (dividend, tuple(remainders))
``````

You would have to switch to little-endian, but I think thatâ€™s more logical to me (since it puts the b^i digit in position i of the output).

This could help when doing a base conversion:

``````x = 3492
base = 11
digits = new_divmod(x, itertools.repeat(base))  # (5, 9, 6, 2)
``````
3 Likes

@NeilGirdhar With an infinite iterable, thatâ€™ll never returnâ€¦

As long as itâ€™s not an infinite sequence of 1, eventually the number will be all the way in the remainder. As a simple example, imagine dividing a large (but finite) number by 10 repeatedly - the remainders at each step are the digits in the decimal representation of that number. With any finite integer, this representation will itself be finite.

1 Like

Ah, embarrassing. Iâ€™m in bad shape and didnâ€™t read/test it properly. Sorry.

Butâ€¦

Not with `-1`, for example.

Ah true. Iâ€™m used to doing â€śconvert to decimalâ€ť as an unsigned operation (so if you get handed a negative number, store a hyphen and negate the value).

Yes, Iâ€™d say it would be the fault of the caller if they did that.

given that timedelta already has a structured way of splitting its total seconds,
i strongly question this â€śoverpowered utilityâ€ť that turns a series of clearly related expressions into a parameter with a magical meaning one will always have to re-read in the docs

2 Likes