Поради щодо гри в гольф на C ++


48

Які загальні поради щодо гольфу в C ++? Я шукаю ідеї, які можна застосувати до проблем із гольфом взагалі, які принаймні дещо характерні для C ++ (наприклад, "видалити коментарі" - це не відповідь). Будь ласка, опублікуйте одну пораду за кожну відповідь.


4
Багато порад щодо гри в гольф на C також застосовні до C ++, тому, будь ласка, припустіть, що читачі знайомі з цим питанням; публікуйте тут лише те, якщо у вас є щось, що також не є дійсним підказом C для гольфу.
Toby Speight

@TobySpeight Можливо, тому що вони мають однаковий URL, крім ідентифікатора питання.
NoOneIsHere

C і C ++, навіть якщо не "гольф" типу, є правильними та легкими (якщо врахувати правильний підмножина C ++)
RosLuP

Відповіді:


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
@BogdanAlexandru Ер ... запусти. Це справді працює. Значення дужки виразів є посиланням на те чи інше з eі o. Зауважте, що це відрізняється від того, як працює цей оператор в c, де цей трюк не працює, оскільки він не може бути значенням.
dmckee

Замініть std::endlтим, '\n'що економить 5 годин
Мукул Кумар

3
@MukulKumar Ну, так. Але для того, щоб продемонструвати цю підказку, я для ясності залишив усе, крім потрійних умовних un-golf.
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
@George Edison: GCC розшифровується як колекція компіляторів GNU , яка охоплює всі її фронти, у тому числі для C, C ++, Go тощо.
Joey Adams

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

25
@George: Компілятор 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(<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;}

vs.

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

Два символи, збережені на звичайному IF або три загалом для IF / ELSE.

Як відмінність C і C ++, результат вираження комами в C ++ в цілому може бути використаний як значення ... 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, використовуйте нотацію e . Наприклад, 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#Conditionss
roofcat

r? foo (): 0; // якщо (r) foo () це нормально ;;;;; але для цього r?: foo (); я цього не знаю
RosLuP

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посилайтесь на невизначене поведінку, використовуючи його як value та x++як rvalue. невизначена поведінка та пункти послідовності
roofcat

Так, можливий + = 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;}

Код тестування, який не має волі, має щось на зразок:

#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 байт коротше?
girobuz

2

Якщо ви хочете поміняти місцями дві цілі величини a і b,

a^=b^=a^=b;

Можна використовувати, зберігаючи 5 символів, ніж звичайний спосіб

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

1
Про той стандартний спосіб. ,tу створених раніше ints і тоді 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'є char* + char, який є покажчиком доповнення, в той час як std::string("") + 'a'це std::string + char- конкатенація. string()працювали б.
користувач202729
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.