В чём суть
В статье предлагается найти промежуток дат, за который можно было заработать больше всего на разнице в курсе доллара к рублю за последние 5 лет.

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

Как я к этому пришёл
Недавно начал читать отличную книгу CLRS (Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. Introduction to Algorithms, Third Edition.). Как не трудно догадаться, в книге изложены фундаментальные знания computer science.
Один из разделов книги как раз приводит реализацию алгоритма для решения этой проблемы. Для закрепления знаний и удовлетворения собственного интереса, я решил применить его на данных о курсе доллара к рублю за последние 5 лет.
Суть проблемы
Как видно из графика за последние 5 лет курс доллара менялся очень сильно. И было бы разумно предположить, что наверняка самой выгодной сделкой было бы купить в абсолютный минимум и продать в последующий локальный максимум (который порой не так легко обнаружить «на глаз»).

Давайте проверим это утверждение.
Решение
Самым простым решением было бы «пойти в лоб» и перебором найти суммы всех комбинаций субмассивов и сравнить их на поиск максимального, однако это решение займёт по времени O(n^2) не очень нас устраивает, так как есть уверенность, что можно сделать это оптимальнее.
Решением будем рекурсивно разбивать входной массив на 2 подмассива через середину и искать максимальную сумму в каждой и частей, а так же на их пересечении. Данный процесс будет требовать у нас O(n lg n), что вполне эффективно.
Давайте распишем пример:
Данные мы получим из www.oanda.com/currency/historical-rates в .csv файле в формате
"2011-10-02","32.1914",,,,
"2011-10-01","32.0809",,,,
"2011-09-30","31.9086",,,,
"2011-09-29","31.7294",,,,
"2011-09-28","32.0421",,,,
"2011-09-27","32.2233",,,,
"2011-09-26","32.0980",,,,
"2011-09-25","32.0673",,,,
...
Далее нам надо считать данные и подготовить их для обработки:
courses = [] reader = csv.reader(open('data.csv', 'rb'), delimiter=',', quotechar='"') prev = 0 for row in reader: #получить текущий элемент cur = float(row[1]) #в самом начале разницы в курсе не должно быть if prev == 0: prev = cur #сохраним разницу в списке value = [row[0], prev - cur, row[1]] prev = cur courses.insert(0, value)
Данные в файле в обратном порядке относительно времени, поэтому вставляем их инверсно. Во время обработки данных мы ищем разницу в ежедневном курсе previous — current. Нашей задачей как раз и будет поиск максимальной суммы этих разностей.
def find_max_subarray(a, low, high): #выход из рекурсии if high == low: return low, high, a[low][1] else: #разбиваем массив на 2 части mid = (low + high) / 2 #находим максимум в левой части left_low, left_high, left_sum = find_max_subarray(a, low, mid) #находим максимум в правой части right_low, right_high, right_sum = find_max_subarray(a, mid + 1, high) #находим кросс-максимум cross_low, cross_high, cross_sum = find_crossing(a, low, mid, high) if (left_sum >= right_sum) & (left_sum >= cross_sum): return left_low, left_high, left_sum elif (right_sum >= left_sum) & (right_sum >= cross_sum): return right_low, right_high, right_sum else: return cross_low, cross_high, cross_sum
И функция поиска кросс-максимума (проходящего через mid — середину деления).
def find_crossing(a, low, mid, high): max_left = 0 max_right = 0 left_sum = -1e308 sum = 0 #уходим влево от центра for i in range(mid, low, -1): sum = sum + a[i][1] if sum > left_sum: left_sum = sum max_left = i max_right = 0 right_sum = -1e308 sum = 0 #уходим вправо от центра for j in range(mid + 1, high): sum = sum + a[j][1] if sum > right_sum: right_sum = sum max_right = j return max_left, max_right, left_sum + right_sum
Ну и выведем результат, который нас интересует
min, max, sum = find_max_subarray(courses, 0, len(courses)-1) print courses[min] print courses[max] print sum
В результате я получил
['2008-07-16', 0.07420000000000115, '23.1269']
['2009-02-05', 0.12059999999999604, '36.1257']
13.1194
И немного поигрался, сдвинув нижнюю дату на 2011 год
['2011-05-05', 0.006199999999999761, '27.2657']
['2011-09-26', 0.12530000000000285, '32.0980']
4.9576
Весь код можно найти на gist.github.com/1257729
Спасибо за внимание!
