Я новичок в спорте кода гольф. Я пытаюсь создать лестницу целых чисел, используя наименьшее количество уникальных символов в C ++.
Допустим, нам дано целое число 4.
Мы сгенерируем следующую лестницу:
1
1 2
1 2 3
1 2 3 4
Короче говоря, моя программа будет читать положительное целое число из стандартного ввода и печатать эту лестницу на выходе. Я пытаюсь сделать это с наименьшим количеством уникальных символов .
Моя программа выглядит следующим образом:
#include<iostream>
int i;
int ii;
int iii;
int iiii;
main() {
std::cin >> i;
for(ii++; ii <= i; ii++) {
int iii = iiii;
for(iii++; iii <= ii; iii++) {
std::cout << iii << " ";
}
std::cout << std::endl;
}
}
Вот проверка, которую я использовал для проверки количества уникальных символов в моей программе:
#include <cstdio>
#include <cstring>
using namespace std;
int check[300],diffcnt=0,cnt=0,t;
char c;
double score;
int main(){
memset(check,0,sizeof(check));
FILE *in=fopen("ans.cpp","r");
while(fscanf(in,"%c",&c)!=EOF){
cnt++;
if(!check[c]){
check[c]=1;
if(c=='\r'||c=='\n') continue;
diffcnt++;
}
}
if(diffcnt<25) printf("100\n");
else if(diffcnt<30){
printf("%.3lf\n",20.0*100.0/cnt+20.0*(29-diffcnt));
}
else{
score=20.0;
for(int x=95;x<cnt;x++) score*=0.9;
printf("%.3lf\n",score);
}
printf("Unique Characters: %d\n", diffcnt);
printf("Total Characters: %d\n", cnt);
return 0;
}
Желательно использовать менее 25 уникальных символов для завершения этой программы (исключая символы новой строки, но включая пробелы). В настоящее время моя программа использует 27. Я не уверен, как оптимизировать его дальше.
Может ли кто-нибудь посоветовать мне, как его оптимизировать (с точки зрения количества используемых уникальных символов)? Обратите внимание, что можно использовать только C ++.
источник
Ответы:
Я считаю, что мне удалось удалить символ = из вашего кода, хотя теперь он значительно медленнее
Это не очень красиво, но злоупотребляя целочисленным переполнением, мы можем вернуться к 0 без использования =
Также нам пришлось немного сменить охрану. К сожалению, из-за включения я не смог избавиться от всех символов новой строки (хотя и близко), так что это может быть следующим путем для расследования.
Изменить: пока не хватает времени, но если вы включите и используете strstream и различные другие библиотеки, я думаю, что вы можете удалить символ «тоже», снова используя целые числа, чтобы получить правильный символ для пробела и передать его в strstream
источник
#include<std>
и устранить все:
с. Не очень хорошая практика кодирования, но это не относится к делу.using namespace std;
что бы использовать дополнительный p для: так что net 0g
, так что чистый убыток, я думаю. Если бы это было золото код, мы могли бы уменьшить количество байтов путем переименованияii
,iii
иiiii
другие имена одного письма (выбрать любые другие уже используемые буквы), но это не то , что эта проблема о, так что я думаю , нет. Мне интересно, будет ли какая-то польза от использования,getc
иputc
вместоcin
/cout
, придется попробовать.signed char
. Если вы компилируете с включенной оптимизацией, этот код может не работать с современными компиляторами, если только вы не используете,gcc -fwrapv
чтобы переполнение со знаком было четко определено как обход дополнения 2. Clang-fwrapv
тоже поддерживает . (unsigned
целочисленные типы в том числеunsigned char
имеют четко определенное поведение (циклическое изменение) в ISO C ++). Это зависит от ABI,char
является лиsigned char
илиunsigned char
, так чтоchar
может быть в порядке.Наконец-то я получил 24 уникальных персонажа, объединив ответы @ExpiredData и @someone. Кроме того, использование короткого типа данных вместо int помогло ускорить мою программу, поскольку для переполнения короткого типа данных требуется меньше времени.
Мой код выглядит следующим образом.
источник
char iiiii;
последней инициализации переменной.23 уникальных персонажа, использующих Digraphs. (25 без). Нет UB.
Используйте синтаксис скобочных инициализаторов C ++ 11, чтобы инициализировать целое число нулями,
int var{};
избегая=
и0
. (Или в вашем случае избегая глобальныхiiii
). Это дает вам источник нулей, отличных от глобальных переменных (которые статически инициализируются нулями, в отличие от локальных).Текущие компиляторы принимают этот синтаксис по умолчанию, без необходимости включать какие-либо специальные опции.
(Целочисленный трюк с циклическим переносом - это весело и хорошо для игры в гольф с отключенной оптимизацией, но переполнение со знаком - неопределенное поведение в ISO C ++. Включение оптимизации превратит эти циклы с циклическим переносом в бесконечные циклы, если только вы не скомпилируете с помощью gcc / clang,
-fwrapv
чтобы хорошо переполнить целое число со знаком -определенное поведение: 2 дополнения.Интересный факт: ISO C ++
std::atomic<int>
имеет четко определенные 2 дополнения!int32_t
должен быть дополнением 2, если определено вообще, но поведение переполнения не определено, поэтому он все еще может быть typedef дляint
илиlong
на любой машине, где один из этих типов равен 32 битам, без дополнения и дополнения 2.)Не полезно для этого конкретного случая:
Вы также можете инициализировать новую переменную как копию существующей, используя либо фигурные скобки, либо (с непустым инициализатором) парены для прямой инициализации .
int a(b)
илиint a{b}
эквивалентныint a = b;
Но
int b();
объявляет функцию вместо переменной, инициализированной в ноль.Кроме того, вы можете получить ноль с
int()
илиchar()
, то есть инициализацию нуля анонимного объекта.Мы можем заменить ваши
<=
сравнения<
сравнениями простым логическим преобразованием : сделайте приращение счетчика цикла сразу после сравнения, а не внизу цикла. IMO, это проще, чем альтернативы, предложенные людьми, например, использование++
в первой части a,for()
чтобы превратить 0 в 1.Мы могли бы сыграть в гольф,
for(int r{}; r++ < n;)
но IMO, что людям труднее читать. Мы не оптимизируем общее количество байтов.Если бы мы уже использовали
h
, мы могли бы сохранить'
или"
для места.Предполагая среду ASCII или UTF-8, пространство
char
значение со значением 32. Мы можем легко создать его в переменной, а затемcout << c;
И другие значения, очевидно, могут быть созданы из последовательности
++
и удвоения, основываясь на битах их двоичного представления. Эффективно сдвигая 0 (ничего) или 1 (++) в LSB, прежде чем удвоить в новую переменную.Эта версия использует
h
вместо'
или"
.Это намного быстрее, чем любая из существующих версий (не полагаясь на длинный цикл), и не содержит неопределенного поведения . Он компилируется без предупреждений с
g++ -O3 -Wall -Wextra -Wpedantic
и сclang++
.-std=c++11
необязательно. Это легальный и портативный ISO C ++ 11 :)Это также не зависит от глобальных переменных. И я сделал его более понятным для человека с помощью имен переменных, которые имеют значение.
Количество уникальных байтов: 25 , исключая комментарии, которые я удалил
g++ -E
. И исключая пробел и перевод строки, как ваш счетчик. Я использовалsed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
этот аскубунту для подсчета вхождений каждого персонажа, иwc
подсчитал, сколько уникальных символов у меня было.Только 2
f
персонажа изfor
.while
Вместо этого мы могли бы использовать циклы, если бы мы использовалиw
.Мы могли бы переписать циклы в стиле ассемблера,
i < r || goto some_label;
чтобы написать условный переход внизу цикла или что-то еще. (Но используюor
вместо||
). Нет, это не работает.goto
является оператором, подобнымif
и не может быть подкомпонентом выражения, как это может быть в Perl. В противном случае мы могли бы использовать его для удаления(
и)
символов.Мы могли бы торговать
f
наg
сif(stuff) goto label;
аfor
, и обе петли всегда выполняются по крайней мере 1 итерации , поэтому мы должны были бы только одну петлю-ветви в нижней части, как обычный ассемблереdo{}while
структуры петли. Предполагая, что пользователь вводит целое число> 0 ...Диграфы и триграфы
К счастью, триграфы были удалены с ISO C ++ 17, поэтому нам не нужно использовать
??>
вместо}
если мы играем в гольф для самой последней версии C ++.Но только триграфы конкретно: ISO C ++ 17 все еще имеет орграфы как
:>
для]
и%>
для}
. Таким образом, за счет использования%
мы можем избежать{
и}
, и использовать%:
для#
чистого сохранения на 2 меньше уникальных символов.А в C ++ есть ключевые слова оператора, например,
not
для!
оператора илиbitor
для|
оператора. С помощьюxor_eq
for^=
вы можете обнулить переменнуюi xor_eq i
, но она содержит несколько символов, которые вы не использовали.Ток
g++
уже игнорирует триграфы по умолчанию даже без-std=gnu++17
; Вы должны использовать-trigraphs
их, или-std=c++11
что-то для строгого соответствия стандарту ISO, который их включает.23 уникальных байта:
Попробуйте онлайн!
Окончательная версия использует
'
одиночную кавычку вместоh
или"
для пробела. Я не хотел переписыватьchar c{}
материал, поэтому я удалил его. Печать символа более эффективна, чем печать строки, поэтому я использовал это.Гистограмма:
Разделитель пространства (до сих пор не решен)
В уже удаленном ответе Johan Du Toit предложил использовать, в частности, альтернативный разделитель
std::ends
. Это символ NULchar(0)
, и он печатается как нулевая ширина на большинстве терминалов. Таким образом, результат будет выглядеть1234
, а не1 2 3 4
. Или, что еще хуже, отделенный мусором от всего, что не рухнуло'\0'
.Если вы можете использовать произвольный разделитель, когда цифру
0
легко создатьcout << some_zeroed_var
. Но никто не хочет10203040
, это даже хуже, чем без разделителя.Я пытался придумать способ создания
std::string
холдинга" "
без использованияchar
строкового литерала. Может быть, что-то добавить к этому? Может быть, с орграфом для[]
установки первого байта в значение32
после создания единицы длиной 1 через один из конструкторов?Йохан также предложил
std::ios
функцию-член fill (), которая возвращает текущий символ заполнения. По умолчанию для потока установлено значениеstd::basic_ios::init()
и' '
.std::cout << i << std::cout.fill();
заменяет,<< ' ';
но использует.
вместо'
.С
-
, мы можем взять указательcout
и использование->fill()
для вызова функции - члена:std::cout << (bitand std::cout)->fill()
. Или нет, мы не использовалиb
ни того, ни другого, поэтому могли бы&
вместо его лексического эквивалентаbitand
.Вызов функции-члена без
.
или->
Поместите это в класс и определите
operator char() { fill(); }
Затем
ss s{}
до цикла иstd::cout << i << s;
внутри цикла. Отлично, он собирает и работает нормально, но мы должны были использоватьp
иh
дляoperator char()
, для чистой потери 1. По крайней мере , мы избегали ,b
чтобы функций - членовpublic
, используяstruct
вместоclass
. (И мы можем переопределить наследованиеprotected
в случае, если это когда-нибудь поможет).источник
cout.fill()
fromstd::ios
, но мы ранее не использовали.
Может быть, мы можем как-то вызвать его, взяв указатель и используя->fill()
функцию-член? Что-нибудь возвращает указатель наcout
какой-либо другой поток?<< (bitand std::cout)->fill()
компилирует, но использует-
. (Несмотря на имя токена,bitand
это просто лексический эквивалент&
, а не только побитовый оператор and. Он также работает как оператор address-of.) Хм, есть какой-то шаблон или лямбда-выражение, которое может получить указатель на функцию-член что мы можем()
без использования.
или->
?std::ios::left
, что в gcc оно определено как 32, но я не мог найти способ извлечь из этого пользу. Я думаю, что я собираюсь позволить этому пойти и сделать некоторую фактическую работу :-)int
32 не проблема, мой ответ уже показывает, как это сделать,++
начиная сint c{};
нуля. Но да, я не спущусь по кроличьей норе, чтобы посмотреть лямбды, шаблоны илиstd::function
. Илиstd::string
идея. Но мы не привыклиg
к тому, что мы не можем объявить,std::string
не проиграв; моя идея использоватьgoto
вместоfor
не удалась.decltype(something)
может дать намchar
тип, но стоит намy
.struct ss : std::ostream { operator auto () { return fill(); } };
но это мало помогает.C ++ (gcc) x86_64 только для Linux,
9295 8900 8712 68125590 байт, 18 уникальных символовПопробуйте онлайн!
Это основано на идеях из этого ответа PPCG . Программа на машинном языке выражается в виде массива 32-битных целых чисел, каждый из которых представлен в виде суммы
1+11+111...
. Оказывается, это может быть более эффективным, чтобы закодироватьx
какy
таковойy%(1<<32)==x
. Закодированная программа машинного языка следующая... который основан на следующем C-коде.
Редактировать: теперь принимает входные данные
stdin
вместоargv[1]
. Спасибо @ ASCII-only и @PeterCordes за их предложения!Edit4:
слегкаулучшено кодирование.источник
-w
флаг пожалуйста: P (также можно переименоватьii
вa
)gcc -zexecstack
для этого, верно? Потомуint m[]
что нетconst
. (И последние наборы инструментов.rodata
в любом случае помещают на неисполняемую страницу, поэтому дажеconst int m[]
не работают, например, на моей системе Arch Linux сgcc
8.2.1 20181127 иld
(GNU Binutils) 2.31.1.) В любом случае, вы забыли упомянуть об этом в своем ответе, но это в твоей ссылке TIO.1
сpush %rax
/pop %rdi
вместо другого push-немедленного. Или, проще говоря, для значений, которые не являются 64-битными, то есть не указателями, 2-байтовымиmov %eax, %edi
. Кроме того, Linuxsyscall
не уничтожает свои входные регистры, толькоrax
с возвращаемым значением и RCX + R11 с сохраненными RIP и RFLAGS как часть работыsyscall
инструкции. Таким образом, вы можете оставитьrdi
иrdx
установить1
между вызовами, и использовать разные рег. Кроме того, RBX является сохраняемым при вызове, поэтому его нельзя сохранить в RBX. Это работает, потому что стартовый код CRT не волнует.21 уникальный персонаж + 1 неустранимый перевод строки
Пробелы не требуются, за исключением первой новой строки. Скомпилировано в g ++ 7.3.0.
Используемые символы:
%:include<ostram>()f-
.Улучшения других ответов:
for
циклы наif
и рекурсии.std::addressof(std::cout)->fill()
, акаstd::cout.fill()
.источник
2120 уникальных персонажей, исключая пробелыВсе пробелы могут быть изменены на новые строки.
Выход с сегфо. Используемые символы:
%:include<ostram>;-h
.Он работает в этой конкретной версии компилятора на 64-битной Linux:
С параметром:
Даже тогда я не уверен, что это всегда будет работать. Это может также зависеть от многих других вещей.
cia
иciu
смещения памяти делятся на 4 междуia
iu
иi
. (int
32-битный в этой версии.) Возможно, вам придется изменить числа в соответствии с фактическим смещением. Адреса были бы намного более предсказуемыми, если бы они все содержались в структуре. К сожалению, нестатическоеauto
не допускается в структуре.e
массив из 0 элементов типа элемента с размером (2 32 -1) × 2 32 байта. Если соответствующий тип указателяe
уменьшен, то верхняя половина указателя будет уменьшена на (2 32 -1), что эквивалентно увеличению на единицу. Это может сбросить уменьшенный счетчик без использования знака равенства.Более разумная версия, которая должна работать более надежно, но использует еще один символ
=
:Даже это не работает в последней версии g ++, потому что кажется, что оно больше не позволяет определять
main
произвольный тип.Эти две программы не используют скобки. Но тогда кажется, что точек с запятой не избежать.
источник
22 уникальных персонажа, исключая пробелы. Разделяет числа символом NUL, который правильно отображается в Windows.
Попробуйте онлайн
Гистограмма:
источник
char(0)
), а не пробел (char(32)
в ASCII / UTF-8). en.cppreference.com/w/cpp/io/manip/ends . Я попробовал это на своем рабочем столе Linux, чтобы убедиться, и вывод выглядит так1234
, а не1 2 3 4
. Это выглядит так же на вашем выходе TIO!"
для ," "
если они могли бы использоватьiiii
для разделения с'0'
для10203040
? Я полагаю, вы можете доказать, что в двоичном выводе программы все еще есть разделитель, но указание этого изменения и его описание на английском языке важно для вашего ответа, потому что это не замена замены! Я был бы счастлив удалить мое отрицательное мнение, если вы расширите свой ответ, чтобы объяснить и оправдать это.