-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expand math.gcd() and math.lcm() to accept multiple arguments #83829
Comments
If we have to find the gcd of three or more numbers, now we should use math.gcd should take "n" number of arguments,like: gcd(a,b,c,....) gcd(4,6,8) //returns 2 |
What problems it will create? |
It will take more time to write the statement itself and there are chances of getting mistakes as the statement is complicated. |
You could (should?) use reduce, then: >>> functools.reduce(math.gcd, (6, 30, 40, 60, 20, 40))
2 |
+1 This isn't hard for us to do and may be occasionally useful. |
Should it take variable number of positional arguments or a single iterable argument as max() and min()? What should it return for 0 arguments? |
Python as programming language provides builtin blocks. This is a good example of using functools.reduce(). But if you can provide an algorithm for calculating the GCD and LCM more efficient than sequential applying gcd() and lcm() for two arguments, it would be an argument for implementing it in the stdlib. |
Good question: the obvious extension to the current function would be to allow it to take multiple scalar arguments. But I don't much like the mismatch that introduces with I definitely don't want to give
That one's easy. It should return |
What's slightly more interesting is the return value for a single argument |
This is almost all down to pragmatics for me. For sum() and prod(), if there are only two operands then there are trivial other ways to spell that (+ and *). So it makes most sense for them to accept iterables instead. Essentially, e.g., nobody would ever _want_ to write sum(2, 3) instead of 2+3. max() and min() differ in that there is no other simple way to write the 2-argument forms. max(x, y) _is_ commonly wanted - but so is max(iterable). It's a weird function signature, but nearly always does what's intended. gcd() (and lcm()!) differ from both in that they're nearly always wanted with 2 arguments. "0, 1, or >= 3 arguments" are possibilities, but rarely wanted. It would be a PITA to need to write, e.g., gcd((x, y)) instead of gcd(x, y) for the overwhelmingly most common 2-argument case. So they should be *args functions - provided we want to cater directly to "0, 1, or >= 3 arguments" at all. Fine by me if we don't. I'm only +0 on generalizing beyond "exactly 2 arguments". For the rest, I agree with Mark. In particular, gcd() must be 0. Serhiy, a "bulk" gcd can be more efficient than nested function calls by exploiting a common case: it can get out early when the gcd-so-far becomes 1 (since gcd(1, anything) == 1, it doesn't matter what the remaining arguments are). For "random" integers, it's already the case that gcd(i, j) == 1 over 60% of the time. |
I think the behavior of gcd() == 0 is correct, but it should be documented, because it isn't completely obvious. Arguments for gcd() == 0:
Other considerations:
In short, while I agree with the behavior, I think there should be a clarifying sentence in the docs saying "returns 0 if all of the arguments are 0". |
Correction: gcd(itertools.chain(iterables)) == gcd(*map(gcd, iterables)) |
Correction correction: returning zero preserves the invariant below. from math import gcd as GCD
from functools import reduce
from itertools import starmap, chain
def gcd(*args):
return reduce(GCD, args, 0)
iterables = [[10, 20, 30],
[],
[0, 0, 0],
[5],
[-15, -10000000]]
a = gcd(*chain.from_iterable(iterables))
b = gcd(*starmap(gcd, iterables))
assert a == b == 5 |
It should take variable number of positional arguments.
|
I think this might make sense because gcd() could have some optimizations for n > 2, such as sorting the numbers and starting by the smallest elements. However, I don't think gcd() and lcm() with more than two arguments are frequent use cases. |
Can I put together a PR for this issue? |
@ananthakrishnan: Sure, go ahead. Let's make the process a bit more streamlined than last time around: see if you can put together a PR that passes all the CI checks before asking for review. If you get stuck, make a work-in-progress PR and ask for help in a comment on that PR rather than via email. A couple of pointers:
def gcd(*args):
g = 0
for arg in args:
g = gcd_2arg(g, operator.index(arg))
return g where gcd_2arg is the original 2-argument gcd (in C, _PyLong_GCD). We can always optimise later. |
This is my code for math.gcd: static PyObject *
math_gcd(PyObject *module, PyObject *args, Py_ssize_t n)
{
PyObject *g = 0, *item, *in;
Py_ssize_t i;
for (i = 0; i < n; i++) {
item = args[i];
in = PyNumber_Index(item);
if (in == NULL) {
return NULL;
}
g = _PyLong_GCD(g, in);
}
Py_DECREF(in);
return g;
} This is showing an error: error: incompatible types when assigning to type ‘PyObject * {aka struct _object *}’ from type ‘PyObject {aka struct _object}’ |
@ananthakrishnan: Do you know how arrays and pointers work in C? |
Yes I know. |
Thanks for the hint.Made changes. |
Sorry, Ananthakrishnan, but I think this problem is too difficult to you. Adding math.lcm() taken 2 weeks and produced 200 messages. It is simpler to implement this feature myself to me. |
I'm a beginner.Not everyone is perfect at begenning. I think you should have at the same situations once,like the situations that I'm facing now. |
I'm always ready to help a beginner, but this is the first time that it takes so much effort. It would be easier if you started with the simple problems you could handle. Even in simple cases you would be able to get tips that you can understand and gradually improve your skills. |
If expand math.gcd() to accept multiple arguments, it is worth to do the same with math.lcm(). |
Merged; thank you to all involved. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: