Советы по игре в гольф на C ++


48

Какие общие советы у вас есть для игры в гольф на C ++? Я ищу идеи, которые могут быть применены к задачам по коду для гольфа в целом, которые, по крайней мере, несколько специфичны для C ++ (например, «удалить комментарии» - это не ответ). Пожалуйста, оставьте один совет за ответ.


4
Многие из советов по игре в гольф на C также применимы к C ++, поэтому, пожалуйста, предположите, что читатели знакомы с этим вопросом; Размещайте сообщения только в том случае, если у вас есть что-то, что не является действительным C-игрой
Тоби Спейт

@TobySpeight Вероятно, потому что у них есть тот же самый URL кроме идентификатора вопроса.
NoOneIsHere

C и C ++, даже если не «игра в гольф», правильны и просты (если
принять

Ответы:


24

Тройной условный оператор ?:может часто использоваться в качестве подставки в для простого if- elseзаявлений на значительной экономии средств.

Это имеет особое значение в том смысле, что его можно использовать для выбора альтернативных значений, как в

#include <iostream>
#include <cstdlib>
int main(int c, char**v){
  int o=0,e=0,u;
  while(--c) ((u=atoi(v[c]))%2?o:e)+=u;
  std::cout << "Sum of odds " << o <<std::endl
            << "Sum of evens " << e <<std::endl;
}

Я еще не запускал код, но я не думаю, что он работает так, как вы говорите. ((u = atoi (v [c]))% 2? o: e) + = u ничего не делает, кроме добавления значения u в выражение слева, которое получает значение o или e, но переменные o и e остаются неизменными, поэтому они всегда будут 0. проверьте код, чтобы увидеть, что происходит. Вы должны использовать адреса, чтобы заставить это работать
Богдан Александру

4
@ БогданАлександру Э-э ... запусти. Это действительно работает. Значение выражения в скобках является ссылкой на один или другой из eи o. Обратите внимание, что это отличается от того, как этот оператор работает в c, где этот трюк не работает, потому что он не может быть lvalue.
dmckee

Заменить std::endlс , '\n'что экономит 5 символов
Mukul Kumar

3
@MukulKumar Ну, да. Но в целях демонстрации этого совета я оставил для ясности все, кроме троичного условия, для игры в гольф.
dmckee

22

Иногда вы можете сохранить два символа, используя тот факт, что статические переменные продолжительности хранения (которые особенно включают в себя все глобальные переменные области видимости) автоматически инициализируются нулями в начале (в отличие от автоматических переменных, где у вас нет такой гарантии). Так что вместо

int main()
{
  int a=0;
  // ...
}

ты можешь написать

int a;
int main()
{
  // ...
}

+1, но определенно плохая практика
mondlos

@mondlos: Гольф в основном подразумевает плохую практику.
celtschk

15

Некоторые компиляторы (например, GCC) поддерживают многосимвольные константы . Это может сохранить несколько символов, когда требуется большое целочисленное значение. Пример:

int n='  ';

Значение зависит от реализации. Обычно значение 'ab'равно 256*'a'+'b'или 'a'+256*'b'. Вы можете указать до 4 символов между кавычками.


3
GCC? Вы имеете в виду g ++ ?
Натан Осман

6
@ Джордж Эдисон: GCC означает коллекцию компиляторов GNU , которая охватывает все ее интерфейсы, в том числе для C, C ++, Go и т. Д.
Джои Адамс,

@Joey: я знаю, но это также имя компилятора GNU C.
Натан Осман

25
@ Джордж: Компилятор GNU C называется gcc, а не GCC.
fredoverflow

Можно также запомнить это, я мог забыть.

12

Тот, который я нашел удобным:

Воспользовавшись тем фактом, что ненулевые значения оцениваются trueв булевых выражениях, и это имеет x&&yзначение x*yпри работе с булевыми выражениями

(x!=0 && y!=0)

оценивает

(x*y)

Вы просто должны знать о переполнениях, как указано ниже.


2
Технически, это так x!=0 && y!=0. Но при использовании умножения вы должны быть осторожны с переполнениями. При использовании 32-разрядных целых чисел x = y = 65536 (и несколько других комбинаций степеней двойки) также приведет к x * y = 0 .
Мартин Эндер

Да это правильно. Я использовал его в качестве проверки границ двумерного массива здесь: codegolf.stackexchange.com/a/37571/31477, где это не имеет значения. Я отредактирую эти пункты.
Балдрикк,

1
Обратите внимание, однако, что &&имеет поведение короткого замыкания, которое *отсутствует. Например, вы не можете заменить i++!=0&&j++!=0на i++*j++.
celtschk

@celtschk да, хорошая мысль. Но если вы просто делаете булеву алгебру, то это работает
Болдрикк

11

Используйте следующие типы:

u64, s64, u32, s32 (or int)

Для повторяющихся слов / типов используйте #defines:

#define a while

Это того стоит, если вы используете whileмного, чтобы наверстать лишние 10 символов. ( Около 4. )


1
Типы u64, s64, u32 и s32 не являются частью C ++. Они могут быть нестандартным расширением вашего компилятора (хотя я их никогда не видел).
celtschk

5
Эти два совета лучше разместить в двух отдельных ответах, чтобы за них можно было голосовать индивидуально.
Трихоплакс


10

По возможности меняйте &&и ||на &и |соответственно.

При использовании простых операторов if:

if(<condition>)<stuff>;

можно изменить на:

<condition>?<stuff>:<any single letter variable>;

который сохраняет характер.


8

Вместо того, чтобы использовать while(1), используйте for(;;), сохраняя один символ :)


8

Использование оператора запятой вместо открытых и закрытых скобок может сохранить несколько символов, если у вас есть ситуация, когда в ваших предложениях содержится более одного оператора:

if(c){x=1;cout<<"Hi";y=2;}else{x=2;cout<<"Bye";y=3;}

против

if(c)x=1,cout<<"Hi",y=2;else x=2,cout<<"Bye",y=3;###

Два символа, сохраненные на простой IF, или всего три для IF / ELSE.

Как различие между C и C ++, результат выражения запятой в C ++ в целом может использоваться как lvalue ... FWIW.


7

Поскольку элементы массива хранятся непосредственно в памяти друг за другом, а не как-то так:

for(int x = 0; x < 25; x++) {
    for(int y = 0; y < 25; y++)
        array[x][y] = whatever;
}

Вы можете сделать что-то вроде этого:

int* pointer = array;
for(int i = 0; i < 25*25; i++, pointer++)
    *pointer = whatever;

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


Не забывайте, что вы можете вырезать все эти пробелы! (
Совсем

@stokastic Примеры предназначены не для игры в гольф, а только для демонстрации того, как использовать технику.
Stuntddude

6
почему нет for(int* i=array; i<array+25*25; i++)? Тогда вам нужно только отслеживать одну переменную.
Лукас

6

Совершенно очевидно, но если вы используете много стандартной библиотеки, вы using namespace std;можете сохранить несколько символов.


5
Если вы используете только одно имя, но это довольно часто, using std::name;возможно, будет короче.
celtschk

10
Это сохраняет только символы, если вы используете std::пять или более раз.
nyuszika7h

6

Полезно помнить, что a[i]это так же, как *(a+i).

Замените a[0]на *aдля двухсимвольной экономии. Кроме того, a[i][0]эквивалентно *a[i]и a[0][i]сокращается до i[*a]. Поэтому, если вы жестко программируете 0индекс в своем массиве, возможно, существует лучший способ.


5

Вместо написания больших степеней 10, используйте электронные обозначения . Например, a=1000000000длиннее чем a=1e9. Это может быть распространено на другие номера, как a=1e9+24лучше, чем a=1000000024.


1
Обратите внимание, что это не совсем эквивалентно, необходимо привести к целочисленным типам перед использованием. Например, 1e9/xэто не то же самое, что 1000000000/xили int(1e9)/x.
user202729

5

Вы можете использовать троичный оператор ?:без каких-либо выражений в блоке true (он сохраняет байт)

#include <iostream>

int foo()
{
    std::cout << "Foo\n";
}

int main()
{
    1?foo():0;  // if (true) foo()
    0?:foo();   // if (!false) foo()
}

Проверьте это здесь


5
Кажется, это расширение GNU, а не стандарт C ++. https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Conditionals.html#Conditionals
terracecat,

? р Foo (): 0; // если (r) foo () это нормально ;;;;; но для этого r?: foo (); я этого не знаю
РосЛюП

5

Более короткий заголовок

Это специфично для GCC, оно может быть расширено другими компиляторами.

Предварительно скомпилированный заголовок.

В G ++ bits/stdc++.hэто предварительно скомпилированный заголовок состоит из всех остальных заголовков. Если вам нужно import2 разных, вы можете просто использовать это.

Короче заголовок.

Это все заголовки, перечисленные на http://en.cppreference.com/w/cpp/header :

отсортировано в порядке возрастания длины.

Некоторые из них уже длиннее bits/stdc++.h, а некоторые требуют поддержки C ++ 17. Некоторые другие не поддерживаются TIO G ++ (по причинам, о которых я не знаю). Отфильтровать их мы имеем:

Может случиться, что некоторые из них могут быть заменены на более короткие. Просто бинарный поиск, можно ли заменить тот, который вам нужен. В частности:

cstdio -> ios        (-3 bytes)
algorithm -> regex   (-4 bytes)
vector -> queue      (-1 byte)
string -> map        (-3 bytes)
bitset -> regex      (-1 byte)
numeric -> random    (-1 byte)

4

#importвместо того, чтобы #includeдать вам еще один байт.

Кроме того, пробел между #importи заголовком не обязательно:

#include <map>
// vs
#import<map>

И если вам нужно что-то из stdlibзаголовка, вы можете импортировать любой заголовок с контейнером STL (предпочтительно setили map) вместо cstdlib.


3

Арифметические операции над логическими значениями:

Несмотря на то что

a*=b>0?.5:-.5

Это лучше чем

if(b>0)a*=.5;else a*=-.5;

это не так хорошо, как

a*=(b>0)-.5

Кроме того, использование #define для всего, что используется часто. Это часто короче, чем использование функций, так как имена типов не нужны.

Объедините вещи как можно больше:

a+=a--;

такой же как

a=2*a-1;

Хотя ваши примеры верны, будьте осторожны, вызывая неопределенное поведение при использовании xв качестве lvalue и x++в качестве rvalue. неопределенные точки поведения и последовательности
Cellcat

Да возможно + = a--; имеет неопределенное поведение
RosLuP

3

Используйте общие лямбды в качестве дешевых шаблонов

Для других типов intиспользование их в качестве аргументов функции может быть дорогостоящим. Тем не менее, были введены общие лямбда-выражения (в C ++ 14?), Позволяющие любой лямбда-выражению быть шаблоном - использование autoтипов аргументов позволяет сохранять байты. Для сравнения:

double f(double x, double y)
[](auto x, auto y)

Общие лямбды также очень удобны для принятия итераторов - вероятно, лучший способ принять входные данные массива в C ++ [](auto a, auto z), где aи zпередаются как begin()и end()из массива / вектора / списка / и т.д.


2

В моей первой попытке кода гольф для задачи «Вычесть следующие числа» я начал с функции (58 байт)

int f(int N, int P){int F;for(F=N;P;F-=++N,P--);return F;}

затем безопасно 5 байтов с переходом на лямбду и перемещением инициализации из for(53)

[](int N,int P){int F=N;for(;P;F-=++N,P--);return F;}

и наконец после переключения с forна whileя получил 51 байт:

[](int N,int P){int F=N;while(P--)F-=++N;return F;}

Тестовый код ungolfed выглядит примерно так:

#include <iostream>
int main(void)
{
    int N, P;
    std::cin >> N >> P;
    auto f = [](int N,int P)
    {
        int F = N;
        while (P--)
            F -= ++N;
        return F;
    };
    std::cout << f(N, P) << std::endl;
    return 0;
}

ОБНОВИТЬ:

На самом деле forможет достигать той же длины, что и while:

[](int N,int P){int F=N;for(;P--;F-=++N);return F;}

2

Вроде поздно на вечеринку, наверное ...

Если вы хотите превратить выражение в -1 и 1 вместо 0 и 1 вместо этого:

int x;
if (a * 10 > 5)
    x = 1;
else
    x = -1;

сделай это:

int x = (a * 10 > 5) * 2 - 1;

Это может сэкономить несколько байтов в зависимости от использования.


А int x=(a*10>5)*2-1;не могли бы вы сделать int x=a*10>5?1:-1;, что на 1 байт короче?
Гиробуз

2

Если вы хотите поменять местами две целочисленные переменные a и b,

a^=b^=a^=b;

можно использовать, сохранив 5 символов, чем стандартным способом

a+=b;
b=a-b;
a-=b;

1
Об этом стандартном способе. ,tв созданных ранее целых, а затем t=a;a=b;b=t;уже на 3 байта короче, чем a+=b;b=a-b;a-=b;. Тем не менее, ваш a^=b^=a^=b;даже короче, так что +1 от меня. Я не знаю C ++, но это действительно работает . Как игрок в код Java, мне грустно, что он там не работает . :(
Кевин Круйссен

1
@KevinCruijssen Да, я должен был упомянуть C ++, я не очень хорошо знаю Java, но a^=b;b^=a;a^=b;он отлично работает в Java.
joker007

1
Нет необходимости явно упоминать C ++. Все эти советы для C ++. :) Как разработчику Java, мне было просто любопытно, можно ли сделать что-то подобное в Java, но, видимо, нет. a^=b;b^=a;a^=b;действительно работает, но длиннее ,t+ t=a;a=b;b=t;. Извините за упоминание Java, поскольку это не по теме здесь. Но хороший совет для программистов C ++!
Кевин Круйссен

2

Используйте встроенные функции GCC вместо импорта

Если вы используете компилятор GCC, это иногда помогает использовать их встроенные функции, такие как __builtin_putsили __builtin_clz. Например,

44 байта:

int main(){__builtin_puts("Hello, world!");}`

50 байтов:

#import<cstdio>
int main(){puts("Hello, world!");}

1

Если вы используете C ++ 11 или новее (что всегда должно быть в настоящее время), используйте autoдля сложных типов, если это возможно.

Пример: 54 байта вместо 66

#include<vector>
std::vector<int> f(std::vector<int> l){return l;}
#include<vector>
auto f(std::vector<int> l){return l;}

Кроме того, поскольку производительность не имеет значения, для некоторых задач std::listможет просто выполнить работу на несколько байтов меньше:

#include<list>
auto f(std::list<int> l){return l;}

1

Функции <algorithm>часто требуют передачи, a.begin(),a.end()которая очень длинная, вместо этого вы можете использовать &a[0],&*end(a)для сохранения 3 байта, если aесть vectorили string.

sort(a.begin(),a.end());
sort(begin(a),end(a));
sort(&a[0],&*end(a));

0

Не используйте string(""), используйте "". Это экономит 8 байтов.


Это не совсем эквивалентно. Например, "" + 'a'is char* + char- это добавление указателя, а std::string("") + 'a'is std::string + char- конкатенация строк. string()должно сработать.
user202729
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.