Рефакторил сегодня древние уголки кода и наткнулся на две функции: add_month
и sub_month
, которые принимают дату и соответственно либо увеличивают ее на календарный месяц, либо уменьшают. Главное, зачем они очевидно нужны — корректная обработка перехода в следующий и предыдущий год.
И вот, глядя на их код, у меня родилась задачка, которую я хочу предложить в качестве развлечения читающим меня питонистам.
Итак, нужно написать универсальную функцию с такой спецификацией:
def move_month(month, year, delta):
'''
Принимая номер месяца (1-12), год и дельту (+1 или -1),
возвращает пару (месяц, год) с соответственно следующим или
предыдущим месяцем.
'''
# ...
return new_month, new_year
Основной фан в том, чтобы сделать это элегантно и коротко, при этом совершенно не обязательно хорошо читаемо. Я, собственно, и пишу эту задачку здесь, потому что свое решение из-за нечитаемости в боевой код включать не буду, а совсем ему дать пропасть жалко :-).
В качестве бонуса напишите так, чтобы delta
могла быть не только единичной, но и произвольной целой.
Комментарии: 39
И коротко (3 операции) и читаемо:
new_month = (month+delta-1)%12+1
new_year += (month+delta-1)//12
Пример на Groovy, вместе с тестами:
new_year = (year * 12 + month + delta - 1) // 12
new_month = (year * 12 + month + delta) - (new_year * 12)
Для произвольной целой и +1/-1 вроде так.
@Денис:
Ваш код дает не совсем корректные результаты:
наврал:
Денис, перед return надо вставить
а то валится на
С Python'ом туго, потому я на руби поизвращаюсь:
Ба! Не ожидал такого количества так быстро! Но, в общем, да, ключ в том, что операция деления по модулю умеет обрабатывать переполнение, а целочисленная часть деления выдает нужную дельту для года.
Хотя я, вот, не знал, что они есть уложенные в одну функцию
divmod
, спасибо :-)Кстати, некоторые, кто применял деление к месяцу, забыли, что его надо сначала на -1 подвинуть, чтобы от нуля был.
Понравилось прикольное решение odin'а с
operator.add
:Но только тут ведь можно без if'а обойтись, убрав единицу у месяца под divmod'ом:
odin, ваш код ужасен. Используйте списковые выражения вместо map - гораздо читабельнее будет и без operator.add можно обойтись.
В условии задачки было как раз, что читабельность нас не интересует :-). Это все чистая зарядка для мозгов, никакой практичности.
Еще вариант на Ruby:
Кстати, в Питоне-то тоже можно без
operator.add
обойтись в одну строку:И что, все предложенные примеры корректно обрабатывают обратные переходы (при deltha == month, deltha == month -1)?
Тесты в студию!
Предлагаю посты кот. не проходят тесты - удалять :)
Сорри, я это имел в виду:
при deltha == -month, deltha == -month-1
В питоне не силен... на js:
криво, зато причудливо...
На правах небольшого подкола:
у нас в ПХП есть стандартая никсовая функция mktime()
которая вычисляет даты с любыми смещениями, с учетом високосности и прочих радостей.
неужели ее в чудо-питоне у вас нет?
Почему же нет. time.mktime. Вот чего нет к сожалению так это strtotime (или все же есть?). Можно было б сделать strtotime("+1 month").
@nasos, каюсь, не самый лучший вариант. а зачем тут списковые выражения?
Да, со сдвигом месяца на единицу я забыл.
Но при этом все варианты, где подсчёты повторяются (например, два раза считается (month+delta-1), мне кажутся не корректными.
И разумеется в production стоит использовать какую нибудь полезную библиотеку (как в случае с mktime). Но разминка хороша. :)
2Денис: У в Ruby on Rails есть библиотека для работы с датами.
Но так не интересно :-)
Проверка остается в качестве упражнения читателю :-). На самом деле, первоначально неработающих, кажется, только два — Дениса и anonymous'а, где месяц к 0-based не приводится.
Поскольку она стандартная юниксовая, она, конечно, в Питоне есть. Но я, признаться, не знал, что она умеет приводить в правильную сторону 0 и 13 месяцы :-). Спасибо! С ней другая кривость — она выдает секунды, которые снова надо конвертировать в нормальную дату, а это уже некрасиво.
Это, видимо, про то, что я приводил в 2:21. Хотя я бы не сказал, что отказ от
map
сделал это выражение читаемее :-)согласен, всё-таки, месяц и год несколько не однородные данные, и по смыслу подобный способ их обработки не совсем подходящий
В питоне есть одна интересная библиотека для работы с датами — называется dateutil (python-dateutil):
Так по короче будет (первый комент можно убрать)
Работает для таких вещей как
Что у многих возвращает 0 в месяце
Мои 5 копеек:
Логовас, круче чем у вас не смог придумать)))
а правильно это сделать, имхо, так:
@hidded
отличная библиотека, очень нравится с ней работать...
def move_month(month, year, delta):
return (month + delta) % 12 or 12, year + (month + delta - 1) / 12
Немного не в тему :)
2odin:
Зачем импортировать лишний модуль?
operator.add легко заменить на:
lambda x,y :x.__add__(y)
В конце концов, python это и делает при операциях с числами - обращается к их(чисел как объектов) соответствующим методам. ;)
Я, разумеется, ни разу не хочу никого поучать.
Виталий, если уж там lambda, то тогда и
__add__
не нужен, можно более естественно:lambda x, y : x + y
.Иван, точно!
Чересчур увлекся идеей, да и выспаться не помешало бы. :)
Домашка по четвергам?
2Виталий:
как говорится, на вкус и цвет... мне было лень писать лямбду, тем более, что есть готовая реализация, ну и Иван ниже правильно высказался.
=)
ИМХО Логовас победил :)
лямбды они вообще малочитаемы, а такие...