Comments 117
А статья довольно интересная)
Гипотеза Эйлера утверждает, что для любого натурального числа n>2 никакую n-ю степень натурального числа нельзя представить в виде суммы (n-1) n-х степеней других натуральных чисел.
Потребовалось несколько раз перечитать гипотезу, чтобы понять, что требуется. По прошествию нескольких лет после института начинаешь отвыкать от подобных формулировок.
Круто, что вы взялись за оптимизацию решения задачи.
для k=6 гипотеза Эйлера остаётся открытой
давайте, парни, хватит биткойны майнить
else
do_something_else();
return val2;
И пол-дня будет разбираться, что за хрень происходит, потому что никак не ожидал от себя подобной детсадовской ошибки.
Второе — на каком языке?
Вы заключаете условия на питоне в скобки?
А стиль автора — это один из общепринятых стилей C++. Называется Allman. Он вполне распространённый, и был стандартным стилем Microsoft ещё задолго до появления C#. Его преимущество — сразу видно какая скобка какой соответствует, сканируя взглядом только начала строк.
— Папа, а что это тут все дяди с синяками и некоторые убитые?
— Это хабр, сынок.
Стиль без фигурных скобок опасен. Именно из него произошел apple goto fail bug, ищите в инете
1. Открывающая и закрывающая скобки расположены на одном уровне. Это довольно естественно. В GUI, перейдя курсором на закрывающую скобку, легче найти подсвеченную открывающую и глазами визуально выделить блок.
2. Кода влезает меньше, но он не такой плотный, опять же легче ориентироваться в нем. И меньшее количество умещенного кода на экране стимулирует разбивать большие функции.
Стайлгайдов, вообще-то, >1, и рекомендации порой сильно отличаются. Из широко известной в узких кругах книги про верёвку и ногу:
Выравнивайте скобки вертикально по левой границе.
Иногда поиск отсутствующей фигурной скобки превращается в крупную проблему. Если вы вынесете скобки туда, где их хорошо видно, то их отсутствие будет сразу же заметно:
while ( some_condition )
{
// внутренний блок
}
Я в самом деле не люблю так называемый стиль Кэрнигана и Ричи:
if (condition) {
code();
}else{
more_code();
}
Здесь не только трудно проверить скобки на парность, но и отсутствие зрительного разделения за счет строк, содержащих лишь открытые скобки, ведет к ухудшению читаемости.
Честно, вот не понимаю, как это уродство без переноса скобки вообще могло появиться на свет — единственная теория, что мало было строк на первых терминалах и их пытались экономить всеми силами. Но вот как оно кому-то может нравиться сейчас, когда нет, вроде, никаких проблем уже давно с количеством строк на мониторе??..
Честно, вот не понимаю, как это уродство без переноса скобки вообще могло появиться на свет — единственная теория, что мало было строк на первых терминалах и их пытались экономить всеми силами. Но вот как оно кому-то может нравиться сейчас, когда нет, вроде, никаких проблем уже давно с количеством строк на мониторе??..
Человек хорошо концентрируется на том коде, который у него перед глазами без использования прокрутки. Поэтому сохранять вертикальное место важно.
В любом случае, отступы прекрасно помогают (опять же, лично для меня) выделять уровни кода, дополнительная строка с открывающей скобкой мне ни к чему от слова «вообще».
Почему тогда они закрывающую всегда переносят? Тоже оставить на той же строке, ещё больше можно сэкономить.
Вы в полушаге от синтаксиса Python. :)
Вообще, это шутка была. А если серьезно, то я довольно много пишу на Python и с ошибками в отступах если и сталкивался, то не помню когда. Точно так же можно ошибиться с расстановкой фигурных скобок. Для решения таких проблем как раз и придуманы тесты. Которые, кстати, стоит писать независимо от языка.
Не спорю. Мне просто не удалось сразу таких нагуглитьВ Go Вы обязаны использовать camel style (K&R) — иначе код не скомпилируется вообще (компилятор считает, что каждая строка заканчивается ';', а компиляция конструкций вида if(..); {… } не предусмотрена). Многие из-за этого сильно ругают Go, кстати ;)
Во времена, когда отсутствовали IDE, перенос скобок помог бы быстро отследить незакрытые блоки и исправить ошибку. Но сейчас же, IDE с валидатором кода уведомит программиста, где он опечатался.
Перенос скобки, лично для меня, прерывает поток чтения и интерпретации кода.
То, что майкрософт ввела перенос скобок в стайл гайд целого языка, это вообще не показатель и не аргумент.
https://google.github.io/styleguide/cppguide.html
Однако, раньше у них было строгое требование — «помещайте открывающуюся скобку в конце строки и никак иначе», сейчас же уже менее строго — «сами решайте, но проект лучше читаем, когда в нем единый стиль» (при этом весь код примеров форматирован K&R).
В коде, написанном на С- и С++-подобных языках всенепременнейше рекомендуется писать открывающую фигурную скобку на отдельной строчке: это создает визуальное вертикальное разделение между предшествующим такой скобке кодом (зачастую: заголовок цикла, if
с условием и т.п.) и собственно телом compound statement. Наличие такого вертикального разделения астрономически повышает читаемость кода.
Вопрос "вертикального размазывания кода" уже давно не актуален. Код должен содержать огромное количество грамотно расставленного вертикального спейсинга. Преимущества в удобочитаемости кода легко затмевают потери в количестве умещающихся на экране строк.
За "египетские скобки" в современном коде надо бить по рукам так же, как и за явное приведение результата malloc в C.
PS: Как же легко программисту бросить вызов интересной задачей)
PSS: Понимаю что извращение, но под рукой есть только оракл, запустил на нем вычисления)
SEARCHRANGE: 0.068 сек. (210 000 i/ms)
SEARCHMAPUSE: 0.141 сек. (78 000 i/ms)
SEARCHBITSETVECTOR: 0.047 сек ( 277 000 i/ms)
Там считается и показывается скорость перебора: i/ms это количество переборов за миллисекунду — это более правильный замер.
N = 86000, UInt128, boost скорость будет такая:
SEARCHRANGE: 250 000 i/ms
SEARCHMAPUSE: 33 000 i/ms
SEARCHBITSETVECTOR: 81 000 i/ms
N = 1000000, UInt128, boost скорость будет такая:
SEARCHRANGE: 330 000 i/ms
SEARCHMAPUSE: 10 000 i/ms
SEARCHBITSETVECTOR: 16 500 i/ms
До полного подсчёта остальных я так и не дошёл.
Да, ни 6 ни 7 пока нет насколько я знаю.
Я зациклился именно на скорости
И за какое же время Ваша программа находит все решения (кратные 144^5=...) для N <= 7131 (корень пятой из 2^64, т.е. все числа помещаются в uint64)?
за какое время ваша программа нашла результат
Это плохая метрика для этой задачи. Потому что в такой метрике победит программа которая сразу проверит именно это решение.
Проблема в том, что честный подсчет не укладывается в лимит времени.
Предполагаемый способ решения: подсчитать список таких чисел заранее (их оказалось немного), забить в табличку и отправить на проверку вариант, который ищет по табличке.
ПС. Сколько потеряно времени на обсуждение скобочек.
До полного подсчёта остальных я так и не дошёл
В смысле, не дошли до следующих совпадений, таких как
54^5+168^5+220^5+266^5=288^5
?
Всброшу интересную задачу. (Сам еще не решил)
Есть бесконечное поле в клеточку — между точками клеточек резистор в 1 Ом.
Какое будет сопротивление между точками
A(0,0) и B(2,1)
Странно, что Вы это не использовали. Можно было бы использовать инты и для больших значений — просто забить на переполнение, а всё подошедшее проверять честным расчётом на uint128. Вероятность случайных совпадений очень мала, поэтому "честные" проверки не займут много времени.
#include <iostream>
#include <algorithm>
#include <chrono>
using std::cout;
using std::endl;
using work_num = long long;
using index_num = unsigned int;
constexpr index_num elements_count = 250;
inline work_num pow5(index_num n) {
const work_num pow2 = n * n;
return pow2 * pow2 * n;
}
int main() {
auto start = std::chrono::steady_clock::now();
work_num powers[elements_count];
for (index_num i = 0; i < elements_count; ++i) {
powers[i] = pow5(i);
}
// a^5 + b^5 + c^5 + d^5 = e^5
for (index_num a = 1; a < elements_count; ++a) {
for (index_num b = a + 1; b < elements_count; ++b) {
for (index_num c = b + 1; c < elements_count; ++c) {
for (index_num d = c + 1; d < elements_count; ++d) {
work_num sum = powers[a] + powers[b] + powers[c] + powers[d];
if (std::binary_search(powers, powers + elements_count, sum)) {
work_num* e_ptr = std::lower_bound(powers, powers + elements_count, sum);
auto end = std::chrono::steady_clock::now();
cout << a << " " << b << " " << c << " " << d << " " << (e_ptr - powers) << endl;
cout << "Time: " << std::chrono::duration<double, std::milli>(end - start).count() << "ms" << endl;
exit(0);
}
}
}
}
}
auto end = std::chrono::steady_clock::now();
cout << "Time: " << std::chrono::duration<double, std::milli>(end - start).count() << "ms" << endl;
}
Работает на 28% быстрее оригинала. Вывод — для решения задачи нужно пользоваться наиболее подходящими средствами языка. В данном случае, ни вектор ни map не нужны, а лучше всего подходит бинарный поиск по отсортированном массиву. А рекурсию лучше заменить на вложенные циклы.
Ещё не стоит забывать что это алгоритм почтичто «в лоб», мне думается, что можно придумать гораздо более быстрые варианты.
Когда я терял своё время с этой задачей, мне думались варианты с отсечениями решений по разным модулям. Часть чисел просто не может быть пятой степенью по определённом модулю. т.е. для некоторого числа M и некоторых Y, 0 < Y < M, не существует такого X, что X^5 % M == Y. Для решения уравнения a^5+b^5+c^5+d^4=e^5, можно эффективно перебирарать e, затем a, b, c, а уже d искать в хеш-таблице.
Вот их код:
// go straight to the first appropriate x3, mod 30
if (int err30 = (x0 + x1 + x2 + x3 - rs) % 30)
x3 += 30 - err30;
if (x3 >= x2)
break;
auto sum = s2 + pow5[x3];
rs это pivot в массиве степеней.
А в коде C++ перебираются 3 числа (x0, x1, x2) и затем почему-то делается сдвиг x3 для коррекции остатка, хотя без фиксации пятого числа это не имеет смысла — какой бы остаток mod 30 не был у x3 — всегда найдется 5-я степень с таким же остатком mod 30, как и суммы x0^5+x1^5+x2^5+x3^5, без этой коррекции.
И в чем смысл этого rs, мне совсем не понятно.
В C, например, при 1 2 2 1 и 1 1 2 2 — не появляются в переборе ( 1 2 2 1 будет пропущен), сумма слагаемых ведь от перестановки не меняется (не появится и 1 1 2 2 — так как начинается с 1 2 3 4). А С++ код не будет проверять и 1 2 2 1 — там не появляется одинаковых чисел совсем. То есть 1 2 3 4, 1 2 3 5, 1 2 4 5. 1 3 4 5 и т.д. Из за этого там количество циклов катастрофически меньше. Но мне не очень понятно почему они так сделали. В принципе встречаются равенства со степенями где есть одинаковые слагаемые.
Добавление rs по модулю 30 двигает этот rs к позиции на которой может быть степень числа. То есть он прыгает только по значениям которые могут быть для такой суммы. Суть примерно как у проверки на чётность. Из проверки исключаются все заранее невалидные.
В коде C++ есть стока
if (pow5[rs] == sum)
Т.е. rs используется для поиска e в уравнении a^5+b^5+c^5+d^5=e^5
Но без фиксации d не получится зафиксировать остаток mod30 для е, и наоборот — пока мы не знаем остаток mod30 для e — мы не знаем какой будет остаток mod30 для d. Мне кажется, что C++ код неправильно использует остаток mod30.
Моё замечание касается вставки
if (int err30 = (x0 + x1 + x2 + x3 - rs) % 30)
x3 += 30 - err30;
до того, как начнется подбираться rs — он участвует в вычислении err30, но значение rs в этот момент не зависит от того, какой окажется итоговая сумма.
(boost::unordered_map оказался быстрее std::unordered_map примерно на 10%)
Сказка о потерянном времени