Finding the LCM of a range of numbers

前端 未结 14 733
时光说笑
时光说笑 2020-12-08 05:03

I read an interesting DailyWTF post today, \"Out of All The Possible Answers...\" and it interested me enough to dig up the original forum post where it was submitted. This

相关标签:
14条回答
  • 2020-12-08 05:35

    This problem is interesting because it doesn't require you to find the LCM of an arbitrary set of numbers, you're given a consecutive range. You can use a variation of the Sieve of Eratosthenes to find the answer.

    def RangeLCM(first, last):
        factors = range(first, last+1)
        for i in range(0, len(factors)):
            if factors[i] != 1:
                n = first + i
                for j in range(2*n, last+1, n):
                    factors[j-first] = factors[j-first] / factors[i]
        return reduce(lambda a,b: a*b, factors, 1)
    


    Edit: A recent upvote made me re-examine this answer which is over 3 years old. My first observation is that I would have written it a little differently today, using enumerate for example. A couple of small changes were necessary to make it compatible with Python 3.

    The second observation is that this algorithm only works if the start of the range is 2 or less, because it doesn't try to sieve out the common factors below the start of the range. For example, RangeLCM(10, 12) returns 1320 instead of the correct 660.

    The third observation is that nobody attempted to time this answer against any other answers. My gut said that this would improve over a brute force LCM solution as the range got larger. Testing proved my gut correct, at least this once.

    Since the algorithm doesn't work for arbitrary ranges, I rewrote it to assume that the range starts at 1. I removed the call to reduce at the end, as it was easier to compute the result as the factors were generated. I believe the new version of the function is both more correct and easier to understand.

    def RangeLCM2(last):
        factors = list(range(last+1))
        result = 1
        for n in range(last+1):
            if factors[n] > 1:
                result *= factors[n]
                for j in range(2*n, last+1, n):
                    factors[j] //= factors[n]
        return result
    

    Here are some timing comparisons against the original and the solution proposed by Joe Bebel which is called RangeEuclid in my tests.

    >>> t=timeit.timeit
    >>> t('RangeLCM.RangeLCM(1, 20)', 'import RangeLCM')
    17.999292996735676
    >>> t('RangeLCM.RangeEuclid(1, 20)', 'import RangeLCM')
    11.199833288867922
    >>> t('RangeLCM.RangeLCM2(20)', 'import RangeLCM')
    14.256165588084514
    >>> t('RangeLCM.RangeLCM(1, 100)', 'import RangeLCM')
    93.34979585394194
    >>> t('RangeLCM.RangeEuclid(1, 100)', 'import RangeLCM')
    109.25695507389901
    >>> t('RangeLCM.RangeLCM2(100)', 'import RangeLCM')
    66.09684505991709
    

    For the range of 1 to 20 given in the question, Euclid's algorithm beats out both my old and new answers. For the range of 1 to 100 you can see the sieve-based algorithm pull ahead, especially the optimized version.

    0 讨论(0)
  • 2020-12-08 05:36

    Here's my javascript solution, I hope you find it easy to follow:

    function smallestCommons(arr) {
      var min = Math.min(arr[0], arr[1]);
      var max = Math.max(arr[0], arr[1]);
    
      var smallestCommon = min * max;
    
      var doneCalc = 0;
    
      while (doneCalc === 0) {
        for (var i = min; i <= max; i++) {
          if (smallestCommon % i !== 0) {
            smallestCommon += max;
            doneCalc = 0;
            break;
          }
          else {
            doneCalc = 1;
          }
        }
      }
    
      return smallestCommon;
    }
    
    0 讨论(0)
  • 2020-12-08 05:38
    print "LCM of 4 and 5 = ".LCM(4,5)."\n";
    
    sub LCM {
        my ($a,$b) = @_;    
        my ($af,$bf) = (1,1);   # The factors to apply to a & b
    
        # Loop and increase until A times its factor equals B times its factor
        while ($a*$af != $b*$bf) {
            if ($a*$af>$b*$bf) {$bf++} else {$af++};
        }
        return $a*$af;
    }
    
    0 讨论(0)
  • 2020-12-08 05:38

    In expanding on @Alexander's comment, I'd point out that if you can factor the numbers to their primes, remove duplicates, then multiply-out, you'll have your answer.

    For example, 1-5 have the prime factors of 2,3,2,2,5. Remove the duplicated '2' from the factor list of the '4', and you have 2,2,3,5. Multiplying those together yields 60, which is your answer.

    The Wolfram link provided in the previous comment, http://mathworld.wolfram.com/LeastCommonMultiple.html goes into a much more formal approach, but the short version is above.

    Cheers.

    0 讨论(0)
  • 2020-12-08 05:39

    Here's my Python stab at it:

    #!/usr/bin/env python
    
    from operator import mul
    
    def factor(n):
        factors = {}
        i = 2 
        while i <= n and n != 1:
            while n % i == 0:
                try:
                    factors[i] += 1
                except KeyError:
                    factors[i] = 1
                n = n / i
            i += 1
        return factors
    
    base = {}
    for i in range(2, 2000):
        for f, n in factor(i).items():
            try:
                base[f] = max(base[f], n)
            except KeyError:
                base[f] = n
    
    print reduce(mul, [f**n for f, n in base.items()], 1)
    

    Step one gets the prime factors of a number. Step two builds a hash table of the maximum number of times each factor was seen, then multiplies them all together.

    0 讨论(0)
  • 2020-12-08 05:44

    Here is my answer in JavaScript. I first approached this from primes, and developed a nice function of reusable code to find primes and also to find prime factors, but in the end decided that this approach was simpler.

    There's nothing unique in my answer that's not posted above, it's just in Javascript which I did not see specifically.

    //least common multipe of a range of numbers
    function smallestCommons(arr) {
       arr = arr.sort();
       var scm = 1; 
       for (var i = arr[0]; i<=arr[1]; i+=1) { 
            scm =  scd(scm, i); 
        }
      return scm;
    }
    
    
    //smallest common denominator of two numbers (scd)
    function scd (a,b) {
         return a*b/gcd(a,b);
    }
    
    
    //greatest common denominator of two numbers (gcd)
    function gcd(a, b) {
        if (b === 0) {  
            return a;
        } else {
           return gcd(b, a%b);
        }
    }       
    
    smallestCommons([1,20]);
    
    0 讨论(0)
提交回复
热议问题