2008-10-09 2 views
18

Я прочитал интересный пост DailyWTF сегодня, "Out of All The Possible Answers...", и он заинтересовал меня достаточно, чтобы выкопать оригинал forum post, где он был отправлен. Это заставило меня задуматься, как я бы решить эту интересную проблему - оригинальный вопрос ставится на Project Euler как:Поиск LCM диапазона чисел

2520 наименьшее число, которое можно разделить на каждой из чисел от 1 до 10 без остатка.

Какое наименьшее число, которое равномерно делится на все цифры от 1 до 20?

реформировать это как вопрос программирования, как бы вы создать функцию, которая может найти наименьшее общее кратное для произвольного списка чисел?

Я невероятно плох с чистой математикой, несмотря на мой интерес к программированию, но я смог решить это после небольшого Googling и некоторых экспериментов. Мне любопытно, какие другие подходы могут использовать пользователи SO. Если вы так склонны, отправьте код ниже, надеюсь, вместе с объяснением. Обратите внимание, что, хотя я уверен, что библиотеки существуют для вычисления GCD и LCM на разных языках, меня больше интересует то, что отображает логику больше, чем вызов библиотечной функции :-)

Я больше всего знаком с Python, C, C++ и Perl, но любой язык, который вы предпочитаете, приветствуется. Бонусные баллы для объяснения логики для других людей с математической точки зрения, таких как я.

EDIT: После отправки я нашел этот же вопрос Least common multiple for 3 or more numbers, но он ответил с тем же основным кодом, который я уже разобрался и нет никакого реального объяснения, так что я чувствовал, что это достаточно разные, чтобы оставить открытой.

+0

кстати - это ответ 10581480? Я считаю, что это правильный ответ для конкретной проблемы :) – warren 2009-02-12 19:12:03

+1

@warren - Я был взволнован вашим ответом, так как какое-то время я думал, что вы правы (только что нашел этот вопрос), и калькулятор lcm, который я написал в Хаскелл дал совершенно другой ответ, и 10581480, казалось, был делимым на [1..20]. Я, должно быть, допустил ошибку где-то, так как ваш ответ неравно делится на 11 или 16. Правильный ответ 232792560. – 2010-01-28 10:17:56

+0

@Matt Ellen - хороший звонок, не уверенный, как я пропустил 11 в своем ответе :) – warren 2010-01-28 14:27:33

ответ

11

Эта проблема интересна тем, что вам не требуется найти LCM произвольного набора чисел, вам предоставляется последовательный диапазон. Вы можете использовать вариант Sieve of Eratosthenes, чтобы найти ответ.

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: Недавно upvote заставил меня пересмотреть этот ответ, который составляет более 3 лет. Мое первое наблюдение заключается в том, что я бы написал это немного по-другому сегодня, используя, например, enumerate.

Второе замечание состоит в том, что этот алгоритм работает только в том случае, если начало диапазона равно 2 или меньше, поскольку оно не пытается пропустить общие факторы ниже начала диапазона. Например, RangeLCM (10, 12) возвращает 1320 вместо правильного 660.

Третье замечание состоит в том, что никто не пытался ответить на этот ответ на любые другие ответы. Моя кишка сказала, что это улучшится по сравнению с решением LCM с грубой силой, поскольку диапазон стал больше. Тестирование доказало мою правильность, по крайней мере, это однажды.

Поскольку алгоритм не работает для произвольных диапазонов, я переписал его, чтобы предположить, что диапазон начинается с 1. Я удалил вызов до reduce в конце, поскольку было легче вычислить результат по мере создания факторов , Я считаю, что новая версия функции является более правильной и понятной.

def RangeLCM2(last): 
    factors = 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 

Вот некоторые временные сравнения с оригиналом и решения, предложенного Joe Bebel, который называется RangeEuclid в моих тестах.

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

Для диапазона от 1 до 20, данные в вопросе, алгоритм Евклида выбивает как мои старые и новые ответы. В диапазоне от 1 до 100 вы можете видеть, что алгоритм на основе сита продвигается вперед, особенно оптимизированная версия.

+0

благодарю Марка! это именно тот интересный ответ, который я имел в виду :-) – Jay 2008-10-09 04:27:39

-1

В расширении комментария @ Alexander я хотел бы указать, что если вы можете указать числа на свои простые числа, удалите дубликаты, а затем размножайтесь, вы получите ответ.

Например, 1-5 имеют основные коэффициенты 2,3,2,2,5. Удалите дублированный «2» из списка коэффициентов «4», и у вас есть 2,2,3,5. Умножая их вместе, получается 60, что является вашим ответом.

Ссылка Wolfram, представленная в предыдущем комментарии, http://mathworld.wolfram.com/LeastCommonMultiple.html идет в гораздо более формальный подход, но короткая версия выше.

Cheers.

+2

Неясно, что "удалять дубликаты " имею в виду. Вы удаляете «2» из 2, потому что он захвачен факторизацией 4. Это не просто семантика. Справедливость делает это немного лучше. , . Вы хотите взять только значение мощности MAX каждого шага. Тогда вы знаете, что низшие силы - это факторы. – Baltimark 2009-02-12 19:44:32

+0

@ Baltimark - отличная точка – warren 2012-07-19 18:18:53

2

LCM одного или нескольких чисел является произведением всех отдельных простых множителей во всех числах, каждое простое с степенью максимума всех степеней, на которое это число появляется в числах, которые принимаются LCM of.

Скажите 900 = 2^3 * 3^2 * 5^2, 26460 = 2^2 * 3^3 * 5^1 * 7^2. Максимальная мощность 2 равна 3, максимальная мощность 3 равна 3, максимальная мощность 5 равна 1, максимальная мощность 7 равна 2, а максимальная мощность любого старшего штриха равна 0. Таким образом, LCM: 264600 = 2^3 * 3^3 * 5^2 * 7^2.

+0

Спасибо за ответ, похоже, что вы и Уоррен предоставили такой же базовый ответ. Я надеялся, что люди отправят образцы, демонстрирующие различные подходы к решению этого в коде, было бы здорово, если бы вы могли опубликовать алгоритмический подход, используя этот techqnique! – Jay 2008-10-09 03:39:21

1

Алгоритм в Haskell. Это язык, который я считаю в настоящее время для алгоритмического мышления. Это может показаться странным, сложным и непривлекательным - добро пожаловать в Haskell!

primes :: (Integral a) => [a] 
--implementation of primes is to be left for another day. 

primeFactors :: (Integral a) => a -> [a] 
primeFactors n = go n primes where 
    go n [email protected](p : pt) = 
     if q < 1 then [] else 
     if r == 0 then p : go q ps else 
     go n pt 
     where (q, r) = quotRem n p 

multiFactors :: (Integral a) => a -> [(a, Int)] 
multiFactors n = [ (head xs, length xs) | xs <- group $ primeFactors $ n ] 

multiProduct :: (Integral a) => [(a, Int)] -> a 
multiProduct xs = product $ map (uncurry (^)) $ xs 

mergeFactorsPairwise [] bs = bs 
mergeFactorsPairwise as [] = as 
mergeFactorsPairwise [email protected]((an, am) : _) [email protected]((bn, bm) : _) = 
    case compare an bn of 
     LT -> (head a) : mergeFactorsPairwise (tail a) b 
     GT -> (head b) : mergeFactorsPairwise a (tail b) 
     EQ -> (an, max am bm) : mergeFactorsPairwise (tail a) (tail b) 

wideLCM :: (Integral a) => [a] -> a 
wideLCM nums = multiProduct $ foldl mergeFactorsPairwise [] $ map multiFactors $ nums 
0

Вот мой Python колото на него:

#!/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) 

Шаг один получает простые множители числа. Второй шаг строит хеш-таблицу максимального количества раз, когда каждый фактор был замечен, а затем умножает их все вместе.

5

Однострочный в Haskell.

wideLCM = foldl lcm 1 

Это то, что я использовал для моего собственного проекта Эйлера Задача 5.

19

Ответ не требует каких-либо фантазий ног вообще с точкой зрения факторинга или степеней простых чисел, и, безусловно, не требует Сита Эратосфена.

Вместо этого, вы должны вычислить LCM одной пары путем вычисления НОД, используя алгоритм Евклида (который не требует факторизации, а на самом деле значительно быстрее):


def lcm(a,b): 
    gcd, tmp = a,b 
    while tmp != 0: 
     gcd,tmp = tmp, gcd % tmp 
    return a*b/gcd 

, то вы можете найти общее LCM мой уменьшая массив, используя вышеупомянутую LCM() функции:


reduce(lcm, range(1,21)) 
2
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; 
} 
9

Там очень быстрое решение для этого, до тех пор, диапазон составляет от 1 до N.

Ключевое наблюдение состоит в том, что если n (< N) имеет разложение на простые множители p_1^a_1 * p_2^a_2 * ... p_k * a_k, то это будет способствовать те же факторы, МДК, как p_1^a_1 и p_2^a_2, ... p_k^a_k. И каждая из этих мощностей также находится в диапазоне от 1 до N. Таким образом, нам нужно рассмотреть только самые высокие чистые простые силы меньше, чем N.

, например, для 20 мы имеем

2^4 = 16 < 20 
3^2 = 9 < 20 
5^1 = 5 < 20 
7 
11 
13 
17 
19 

Умножив все эти простые силы вместе мы получаем требуемый результат

2*2*2*2*3*3*5*7*11*13*17*19 = 232792560 

Таким образом, в псевдокоде:

def lcm_upto(N): 
    total = 1; 
    foreach p in primes_less_than(N): 
    x=1; 
    while x*p <= N: 
     x=x*p; 
    total = total * x 
    return total 

Теперь вы можете настроить внутреннюю петлю на горе rk несколько иначе, чтобы получить большую скорость, и вы можете предварительно вычислить функцию primes_less_than(N).

EDIT:

В связи с недавним upvote я decideded, чтобы вернуться к этому, чтобы увидеть, как сравнение скорости с другими перечисленными алгоритмами пошли.

Timing для диапазона 1-160 с 10к итераций, против Джо Beibers и Марк выкупы методы заключаются в следующем:

Джос: 1.85s Marks: 3.26s Mine: 0.33s

Вот журнал -log график с результатами до 300.

A log-log graph with the results

Код для моего теста можно найти здесь:

import timeit 


def RangeLCM2(last): 
    factors = 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 


def lcm(a,b): 
    gcd, tmp = a,b 
    while tmp != 0: 
     gcd,tmp = tmp, gcd % tmp 
    return a*b/gcd 

def EuclidLCM(last): 
    return reduce(lcm,range(1,last+1)) 

primes = [ 
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 
31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 
127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 
179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 
233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 
353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 
419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 
547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 
607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 
661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 
739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 
811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 
877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 
947, 953, 967, 971, 977, 983, 991, 997 ] 

def FastRangeLCM(last): 
    total = 1 
    for p in primes: 
     if p>last: 
      break 
     x = 1 
     while x*p <= last: 
      x = x * p 
     total = total * x 
    return total 


print RangeLCM2(20) 
print EculidLCM(20) 
print FastRangeLCM(20) 

print timeit.Timer('RangeLCM2(20)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(20)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(20)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(40)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(40)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(40)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(60)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(60)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(60)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(80)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(80)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(80)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(100)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(100)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(100)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(120)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(120)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(120)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(140)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(140)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(140)', "from __main__ import FastRangeLCM").timeit(number=10000) 

print timeit.Timer('RangeLCM2(160)', "from __main__ import RangeLCM2").timeit(number=10000) 
print timeit.Timer('EuclidLCM(160)', "from __main__ import EuclidLCM").timeit(number=10000) 
print timeit.Timer('FastRangeLCM(160)', "from __main__ import FastRangeLCM").timeit(number=10000) 
3

В Haskell:

listLCM xs = foldr (lcm) 1 xs 

Что вы можете передать список, например:

*Main> listLCM [1..10] 
2520 
*Main> listLCM [1..2518] 
266595767785593803705412270464676976610857635334657316692669925537787454299898002207461915073508683963382517039456477669596355816643394386272505301040799324518447104528530927421506143709593427822789725553843015805207718967822166927846212504932185912903133106741373264004097225277236671818323343067283663297403663465952182060840140577104161874701374415384744438137266768019899449317336711720217025025587401208623105738783129308128750455016347481252967252000274360749033444720740958140380022607152873903454009665680092965785710950056851148623283267844109400949097830399398928766093150813869944897207026562740359330773453263501671059198376156051049807365826551680239328345262351788257964260307551699951892369982392731547941790155541082267235224332660060039217194224518623199770191736740074323689475195782613618695976005218868557150389117325747888623795360149879033894667051583457539872594336939497053549704686823966843769912686273810907202177232140876251886218209049469761186661055766628477277347438364188994340512556761831159033404181677107900519850780882430019800537370374545134183233280000 
0

Это, вероятно, самый чистый, самый короткий ответ (как с точки зрения строк кода), что я до сих пор.

def gcd(a,b): return b and gcd(b, a % b) or a 
def lcm(a,b): return a * b/gcd(a,b) 

n = 1 
for i in xrange(1, 21): 
    n = lcm(n, i) 

Источник: http://www.s-anand.net/euler.html

0

Вот мой ответ на JavaScript. Сначала я подошел к этому из простых чисел и разработал хорошую функцию многоразового кода, чтобы найти простые числа, а также найти основные факторы, но в конце концов решил, что этот подход был проще.

В моем ответе нет ничего уникального, что не опубликовано выше, это просто в Javascript, который я не видел конкретно.

//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

Вот мой Javascript решение, я надеюсь, что вы найдете легко следовать:

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; 
} 
Смежные вопросы