Re: Factor

Factor: the language, the theory, and the practice.

Divmods

Friday, February 2, 2024

#math

There’s a discussion on support multiple divisors in divmod() on the Python.

So instead of

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])

Along with the sample implementation in Python above, the original author provides some thoughts on whether the order of arguments should be reversed or not, and some of the comments in the thread discuss various implementation details and some other use-cases for this approach.

You can see how it might work by trying the code:

>>> new_divmod(1234567, 7, 24, 60, 60)
(2, 0, 6, 56, 7)

Okay, so how might we do this in Factor?

Well, our version of divmod is /mod and we could just run it a few times to get the result:

IN: scratchpad 1234567 60 /mod swap 60 /mod swap 24 /mod swap 7 /mod swap

--- Data stack:
7
56
6
0
2

Alternatively, we could pass the arguments as a sequence and return the result as a sequence:

IN: scratchpad 1234567 { 60 60 24 7 } [ /mod ] map swap suffix

--- Data stack:
{ 7 56 6 0 2 }

Or, perhaps, we could make a macro, taking the input argument as a sequence, but generating code to put the result onto the stack:

MACRO: /mods ( seq -- quot )
    [ '[ _ /mod swap ] ] map concat ;

And then use it:

IN: scratchpad 1234567 { 60 60 24 7 } /mods

--- Data stack:
7
56
6
0
2

Kind of an interesting idea!