23 unikalne postacie za pomocą Digraphs. (25 bez). Bez UB.
Użyj składni inicjalizatora wzmocnionego C ++ 11, aby zainicjować listę liczb całkowitych do zera, int var{};
unikając =
i 0
. (Lub w twoim przypadku, unikając globalnego iiii
). Daje to źródło zer innych niż zmienne globalne (które są statycznie inicjowane na zero, w przeciwieństwie do miejscowych).
Obecne kompilatory domyślnie akceptują tę składnię, bez konieczności włączania żadnych specjalnych opcji.
(Sztuczka z zawijaniem liczb całkowitych jest fajna i dobra do gry w golfa z wyłączoną optymalizacją, ale podpisane przepełnienie jest niezdefiniowanym zachowaniem w ISO C ++. Włączenie optymalizacji przekształci te zawinięte pętle w nieskończone pętle, chyba że skompilujesz z gcc / clang, -fwrapv
aby dobrze podpisać przepełnienie liczb całkowitych -definiowane zachowanie: objęcie dopełniacza 2.
Ciekawostka: ISO C ++ std::atomic<int>
ma dobrze zdefiniowane dopełnienie komplementu 2! int32_t
musi być uzupełnieniem 2, jeśli w ogóle jest zdefiniowane, ale zachowanie przepełnienia jest niezdefiniowane, więc nadal może być typem zdefiniowanym dla int
lub long
na dowolnym komputerze, na którym jeden z tych typów ma 32 bity, bez dopełniania i uzupełnienia 2).
Nieprzydatne w tym konkretnym przypadku:
Możesz także zainicjować nową zmienną jako kopię istniejącej, za pomocą nawiasów klamrowych lub (z niepustym inicjatorem) parens do bezpośredniej inicjalizacji .
int a(b)
lub int a{b}
są równoważne zint a = b;
Ale int b();
deklaruje funkcję zamiast zmiennej zainicjowanej na zero.
Można także uzyskać zero za pomocą int()
lub char()
, tj. Inicjalizację zera anonimowego obiektu.
Możemy zamienić twoje <=
porównania na <
porównania przez prostą transformację logiczną : wykonaj przyrost licznika pętli bezpośrednio po porównaniu, zamiast na dole pętli. IMO jest to prostsze niż alternatywy proponowane przez ludzi, takie jak użycie ++
w pierwszej części a, for()
aby zamienić 0 na 1.
// comments aren't intended as part of the final golfed version
int n;
std::cin >> n; // end condition
for(int r{}; r < n;) { // r = rows from 0 .. n-1
++r;
for(int i{}; i < r;) {
++i;
std::cout << i << ' ';
}
std::cout << std::endl;
}
Moglibyśmy grać w golfa, for(int r{}; r++ < n;)
ale IMO jest trudniejsze do odczytania dla ludzi. Nie optymalizujemy całkowitej liczby bajtów.
Gdybyśmy już używali h
, moglibyśmy zaoszczędzić miejsce '
lub "
.
Zakładając środowisko ASCII lub UTF-8, przestrzeń ma char
wartość 32. Możemy ją łatwo utworzyć w zmiennej, a następniecout << c;
char c{};
c++; c++; // c=2
char cc(c+c+c+c); // cc=8
char s(cc+cc+cc+cc); // s=32 = ' ' = space in ASCII/UTF-8
A inne wartości można oczywiście utworzyć z sekwencji ++
podwojenia i na podstawie bitów ich reprezentacji binarnej. Skutecznie przesuwając 0 (nic) lub 1 (++) do LSB przed podwojeniem do nowej zmiennej.
Ta wersja używa h
zamiast '
lub "
.
Jest znacznie szybszy niż którakolwiek z istniejących wersji (nie opiera się na długiej pętli) i jest wolny od niezdefiniowanego zachowania . To kompiluje bez ostrzeżenia ze g++ -O3 -Wall -Wextra -Wpedantic
izclang++
. -std=c++11
jest opcjonalne. Jest to legalne i przenośne ISO C ++ 11 :)
Nie opiera się również na zmiennych globalnych. I uczyniłem go bardziej czytelnym dla człowieka dzięki nazwom zmiennych, które mają znaczenie.
Liczba bajtów unikalnych: 25 , z wyłączeniem komentarzy, które usunąłemg++ -E
. I wykluczając spację i znak nowej linii, jak twój licznik. Użyłem sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
tego askubuntu, aby policzyć wystąpienia każdej postaci, i wc
przeliczyłem to, aby policzyć, ile unikalnych postaci miałem.
#include<iostream>
int main() {
char c{};
c++; c++; // c=2
char cc(c+c+c+c); // cc=8
char s(cc+cc+cc+cc); // s=32 = ' ' = space in ASCII/UTF-8
int n;
std::cin >> n; // end condition
for(int r{}; r < n;) { // r = rows counting from 0
++r;
for(int i{}; i < r;) {
++i;
std::cout << i << s;
}
std::cout << std::endl;
}
}
Jedyne 2 f
znaki pochodzą z for
. while
Zamiast tego moglibyśmy użyć pętli w
.
Moglibyśmy przepisać pętle na styl asemblera, i < r || goto some_label;
aby napisać warunkowy skok na dole pętli lub cokolwiek innego. (Ale używając or
zamiast ||
). Nie, to nie działa. goto
jest instrukcją podobną do if
i nie może być podskładnikiem wyrażenia tak jak w Perlu. W przeciwnym razie moglibyśmy go użyć do usunięcia znaków (
i )
.
Możemy handlować f
na g
ze if(stuff) goto label;
zamiast for
, a obie pętle zawsze uruchomić co najmniej 1 iteracji tak musielibyśmy jednej pętli oddziałem na dole tylko, jak normalny ASM do{}while
strukturze pętli. Zakładając, że użytkownik wprowadzi liczbę całkowitą> 0 ...
Digraphs and Trigrafs
Na szczęście, trygrafy zostały usunięte od ISO C ++ 17, więc nie musimy ich używać ??>
zamiast, }
jeśli jesteśmy wyjątkowymi golfistami w najnowszej wersji C ++.
Ale tylko trygrafy: ISO C ++ 17 nadal ma digrafy, takie jak :>
for ]
i %>
for}
. Więc kosztem użyciu %
, możemy uniknąć zarówno {
a }
i używać %:
dla #
dla oszczędności netto 2 mniej unikalnych znaków.
C ++ i ma kluczowe operatora jak not
dla !
operatora, albo bitor
dla |
operatora. Za pomocą xor_eq
for ^=
można zerować zmienną i xor_eq i
, ale ma ona wiele znaków, których nie używałeś.
Obecne g++
już domyślnie ignoruje trygrafy, nawet bez -std=gnu++17
; musisz użyć, -trigraphs
aby je włączyć -std=c++11
lub coś w celu ścisłej zgodności ze standardem ISO, który je obejmuje.
23 unikalne bajty:
%:include<iostream>
int main() <%
int n;
std::cin >> n;
for(int r<% %>; r < n;) <%
++r;
for(int i<%%>; i < r;) <%
++i;
std::cout << i << ' ';
%>
std::cout << std::endl;
%>
%>
Wypróbuj online!
Ostateczna wersja używa '
pojedynczego cudzysłowu zamiast h
lub "
dla separatora spacji. Nie chciałem kopiować char c{}
treści, więc je usunąłem. Drukowanie znaku jest bardziej wydajne niż drukowanie łańcucha, więc wykorzystałem to.
Histogram:
$ sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic | tee /dev/tty | wc -l
15 // newline
95 // space
11 %
2 '
3 (
3 )
4 +
9 :
10 ;
14 <
8 >
2 a
4 c
6 d
3 e
2 f
12 i
2 l
2 m
11 n
5 o
7 r
5 s
11 t
3 u
25 // total lines, including space and newline
Separator przestrzeni (wciąż nierozwiązany)
W usuniętej teraz odpowiedzi Johan Du Toit zaproponował zastosowanie specjalnie alternatywnego separatora std::ends
. Jest to znak NUL char(0)
i drukowany jest jako zero-szerokość na większości terminali. Tak wyglądałoby wyjście1234
, a nie 1 2 3 4
. Albo gorzej, oddzielone śmieciami od wszystkiego, co nie cicho się zawali '\0'
.
Jeśli możesz użyć dowolnego separatora, kiedy cyfra 0
jest łatwa do utworzeniacout << some_zeroed_var
. Ale nikt nie chce 10203040
, to nawet gorzej niż brak separatora.
Próbowałem wymyślić sposób na utworzenie std::string
gospodarstwa" "
bez użycia char
lub dosłownego ciągu znaków. Może coś do tego dodać? Może z wykopem dla[]
do ustawienia pierwszego bajtu na wartość 32
po utworzeniu jednego o długości 1 za pomocą jednego z konstruktorów?
Johan zasugerował również std::ios
funkcję członka fill (), która zwraca bieżący znak wypełnienia. Domyślne ustawienie strumienia jest ustawione na std::basic_ios::init()
i ma wartość ' '
.
std::cout << i << std::cout.fill();
zastępuje, << ' ';
ale używa .
zamiast'
.
Dzięki -
, możemy podjąć wskaźnik do cout
i wykorzystania ->fill()
do wywołania funkcji użytkownika:
std::cout << (bitand std::cout)->fill()
. Albo nie, my też nie korzystaliśmy b
, więc równie dobrze moglibyśmy użyć&
zamiast jego odpowiednika leksykalnym bitand
.
Wywoływanie funkcji członka bez .
lub->
Umieść go w klasie i zdefiniuj operator char() { fill(); }
// not digraphed
struct ss : std::ostream { // default = private inheritance
// ss() { init(); } // ostream's constructor calls this for us
operator char() { return fill(); }
}
Następnie ss s{}
przed pętlą i std::cout << i << s;
wewnątrz pętli. Świetnie, kompiluje się i działa poprawnie, ale musieliśmy użyć p
i h
dla operator char()
, dla straty netto równej 1. Przynajmniej uniknęliśmy b
tworzenia funkcji członkowskich public
przez używanie struct
zamiast class
. (I możemy zastąpić dziedziczenie, protected
na wypadek, gdyby to kiedykolwiek pomogło).