Jestem nowy w sporcie golfowym. Próbuję wygenerować drabinkę liczb całkowitych, używając najmniejszej liczby unikalnych znaków w C ++.
Powiedzmy, że otrzymaliśmy liczbę całkowitą 4.
Wygenerujemy następującą drabinę:
1
1 2
1 2 3
1 2 3 4
W skrócie, mój program odczyta dodatnią liczbę całkowitą ze standardowego wejścia i wydrukuje tę drabinę na wyjściu. Próbuję to zrobić z możliwie najmniejszą liczbą unikalnych postaci .
Mój program jest następujący:
#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;
}
}
Oto sprawdzanie, którego użyłem do sprawdzenia liczby unikalnych znaków w moim programie:
#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;
}
Najlepiej, jeśli chcę użyć mniej niż 25 unikalnych znaków, aby ukończyć ten program (oprócz znaków nowego wiersza, ale z odstępami). Obecnie mój program używa 27. Nie jestem pewien, jak dalej go zoptymalizować.
Czy ktoś mógłby mi doradzić, jak dalej go zoptymalizować (pod względem liczby użytych unikalnych znaków)? Pamiętaj, że można używać tylko C ++.
źródło
Odpowiedzi:
Myślę, że udało mi się usunąć znak = z twojego kodu, chociaż teraz jest on znacznie wolniejszy
To nie jest ładne, ale nadużywając przepełnienia liczb całkowitych możemy wrócić do 0 bez użycia =
Musieliśmy też trochę zmienić strażników. Niestety z powodu dołączenia nie mogłem pozbyć się wszystkich nowych znaków linii (chociaż jest blisko), więc może to być kolejna droga do zbadania.
Edycja: na razie zabrakło czasu, ale jeśli uwzględnisz i używasz strstream i różnych innych bibliotek, myślę, że możesz być w stanie usunąć również znak „, ponownie używając liczb całkowitych, aby uzyskać właściwy znak dla spacji i przekazać go do strstream
źródło
#include<std>
i wyeliminować wszystkie:
s. Niezbyt dobra praktyka kodowania, ale to nie ma znaczenia.using namespace std;
który użyłby dodatkowego p dla: więc netto 0g
, więc myślę, że strata netto. Gdyby to był kod złota, możemy zmniejszyć liczbę bajtów przez zmianę nazwyii
,iii
oraziiii
do innych nazw pojedynczy list (pick innych już używanych liter), ale to nie to, co jest o to wyzwanie, więc nie sądzę. Zastanawiam się, czy przyniosłyby jakieś korzyścigetc
iputc
zamiastcin
/cout
musiałbym spróbować.signed char
. Jeśli kompilujesz z włączoną optymalizacją, ten kod może zepsuć się z nowoczesnymi kompilatorami, chyba że użyjeszgcc -fwrapv
do tego, aby podpisane przepełnienie było dobrze zdefiniowane jako zawijanie dopełniacza 2. obsługuje także clang-fwrapv
. (wunsigned
tym typy całkowiteunsigned char
mają dobrze zdefiniowane zachowanie (zawijanie) w ISO C ++). To zależy od ABI, czychar
jestsigned char
lubunsigned char
, więcchar
może być w porządku.W końcu uzyskałem 24 unikalne postacie, łącząc odpowiedzi @ExpiredData i @someone. Ponadto użycie krótkiego typu danych zamiast int pomogło przyspieszyć mój program, ponieważ przepełnienie krótkiego typu danych zajmuje krócej.
Mój kod jest następujący.
źródło
char iiiii;
ostatniej inicjalizacji zmiennej.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=
i0
. (Lub w twoim przypadku, unikając globalnegoiiii
). 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 dlaint
lublong
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)
lubint 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()
lubchar()
, 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.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;
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ąłem
g++ -E
. I wykluczając spację i znak nowej linii, jak twój licznik. Użyłemsed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
tego askubuntu, aby policzyć wystąpienia każdej postaci, iwc
przeliczyłem to, aby policzyć, ile unikalnych postaci miałem.Jedyne 2
f
znaki pochodzą zfor
.while
Zamiast tego moglibyśmy użyć pętliw
.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ącor
zamiast||
). Nie, to nie działa.goto
jest instrukcją podobną doif
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
nag
zeif(stuff) goto label;
zamiastfor
, a obie pętle zawsze uruchomić co najmniej 1 iteracji tak musielibyśmy jednej pętli oddziałem na dole tylko, jak normalny ASMdo{}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, albobitor
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:
Wypróbuj online!
Ostateczna wersja używa
'
pojedynczego cudzysłowu zamiasth
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:
Separator przestrzeni (wciąż nierozwiązany)
W usuniętej teraz odpowiedzi Johan Du Toit zaproponował zastosowanie specjalnie alternatywnego separatora
std::ends
. Jest to znak NULchar(0)
i drukowany jest jako zero-szerokość na większości terminali. Tak wyglądałoby wyjście1234
, a nie1 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 chce10203040
, to nawet gorzej niż brak separatora.Próbowałem wymyślić sposób na utworzenie
std::string
gospodarstwa" "
bez użyciachar
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 nastd::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 docout
i wykorzystania->fill()
do wywołania funkcji użytkownika:std::cout << (bitand std::cout)->fill()
. Albo nie, my też nie korzystaliśmyb
, więc równie dobrze moglibyśmy użyć&
zamiast jego odpowiednika leksykalnymbitand
.Wywoływanie funkcji członka bez
.
lub->
Umieść go w klasie i zdefiniuj
operator char() { fill(); }
Następnie
ss s{}
przed pętlą istd::cout << i << s;
wewnątrz pętli. Świetnie, kompiluje się i działa poprawnie, ale musieliśmy użyćp
ih
dlaoperator char()
, dla straty netto równej 1. Przynajmniej uniknęliśmyb
tworzenia funkcji członkowskichpublic
przez używaniestruct
zamiastclass
. (I możemy zastąpić dziedziczenie,protected
na wypadek, gdyby to kiedykolwiek pomogło).źródło
cout.fill()
odstd::ios
, ale nie byliśmy wcześniej używając.
Może moglibyśmy nazwać jakoś biorąc wskaźnik i korzystania->fill()
z funkcji członka? Czy coś zwraca wskaźnik docout
lub inny strumień?<< (bitand std::cout)->fill()
kompiluje, ale używa-
. (Pomimo nazwy tokena,bitand
jest to tylko leksykalny odpowiednik&
, a nie operator bitowy i. Działa również jako operator adresu.) Hmm, jest jakiś szablon lub lambda, które mogą uzyskać wskaźnik do funkcji członka że możemy()
bez użycia.
lub->
?std::ios::left
jest zdefiniowana w gcc jako 32, ale tak naprawdę nie mogłem znaleźć sposobu, aby z tego skorzystać. Myślę, że pozwolę temu odejść i trochę popracować :-)int
32 nie jest problemem, moja odpowiedź już pokazuje, jak to zrobić,++
zaczynając odint c{};
zera. Ale tak, nie schodzę do króliczej nory, szukając lambdas, szablonów lubstd::function
. Lubstd::string
pomysł. Ale nie przyzwyczajamy sięg
do tego, że nie możemy faktycznie zadeklarować,std::string
nie tracąc; mój pomysł na użyciegoto
zamiastfor
nie wypalił.decltype(something)
może dać namchar
typ, ale kosztuje nasy
.struct ss : std::ostream { operator auto () { return fill(); } };
ale to niewiele pomaga.C ++ (gcc) x86_64 Tylko Linux,
9295 8900 8712 68125590 bajtów, 18 unikalnych znakówWypróbuj online!
Jest to oparte na pomysłach z tej odpowiedzi PPCG . Program w języku maszynowym jest wyrażony jako tablica 32 bitowych liczb całkowitych, z których każdy jest reprezentowany jako suma
1+11+111...
. Okazuje się, że kodowaniex
jakoy
takie może być bardziej efektywney%(1<<32)==x
. Kodowany program języka maszynowego jest następujący... który jest oparty na następującym kodzie C.
Edycja: Teraz akceptuje dane wejściowe
stdin
zamiastargv[1]
. Dzięki tylko dla @ ASCII i @PeterCordes za sugestie!Edit4:
Nieznaczniepoprawione kodowanie.źródło
-w
flag pls: P (możesz także zmienić nazwęii
naa
)gcc -zexecstack
tego, prawda? Ponieważint m[]
nie jestconst
. (Ostatnie łańcuchy narzędzi i tak zostały umieszczone.rodata
na stronie niewykonywalnej, więc nawetconst int m[]
nie działa np. W moim systemie Arch Linux zgcc
8.2.1 20181127 ild
(GNU Binutils) 2.31.1.) W każdym razie zapomniałeś wspomnieć o tym w swojej odpowiedzi, ale jest w twoim linku TIO.1
zpush %rax
/pop %rdi
zamiast innego push-instant. Lub prościej, dla wartości, które nie są 64-bitowe, tj. Bez wskaźników, 2-bajtowemov %eax, %edi
. Ponadto Linuxsyscall
nie niszczy rejestrów wejściowych, tylkorax
wartością zwracaną i RCX + R11 z zapisanymi RIP i RFLAGS jako część działaniasyscall
instrukcji. Możesz więc wychodzićrdi
irdx
ustawiać1
różne połączenia oraz używać różnych rejestrów. Ponadto RBX jest zachowywany na wywołaniach, więc nie jest tak naprawdę zapisywany w RBX Clobber main. Zdarza się, że kod startowy CRT nie ma znaczenia.21 unikalnych znaków + 1 nieusuwalna nowa linia
Białe spacje nie są wymagane, z wyjątkiem pierwszej nowej linii. Skompilowane wg g ++ 7.3.0.
Użyte znaki:
%:include<ostram>()f-
.Ulepszenia innych odpowiedzi:
for
pętle naif
i rekurencję.std::addressof(std::cout)->fill()
, akastd::cout.fill()
.źródło
2120 unikalnych znaków z wyłączeniem białych znakówWszystkie białe znaki można zmienić na nowe linie.
Wyjścia z segfault. Używane znaki:
%:include<ostram>;-h
.Działa w tej konkretnej wersji kompilatora na 64-bitowym systemie Linux:
Z parametrem:
Nawet wtedy nie jestem pewien, czy to zawsze zadziała. Może to również zależeć od wielu innych rzeczy.
cia
iciu
są przesunięciami pamięci podzielonymi przez 4 międzyia
iu
ii
. (int
w tej wersji jest 32-bitowy). Może być konieczna zmiana liczb w celu dopasowania do rzeczywistego przesunięcia. Adresy byłyby znacznie bardziej przewidywalne, gdyby wszystkie były zawarte w strukturze. Niestetyauto
w strukturach niedozwolona jest funkcja niestatyczna .e
jest tablicą 0-elementową typu elementu o rozmiarze (2 32 -1) × 2 32 bajty. Jeśli odpowiedni typ wskaźnikae
jest zmniejszany, wyższa połowa wskaźnika byłaby zmniejszana o (2 32 -1), co jest równoważne zwiększaniu o jeden. To może zresetować zmniejszony licznik bez użycia znaku równości.Bardziej rozsądna wersja, która powinna działać bardziej niezawodnie, ale używa jeszcze jednej postaci
=
:Nawet to nie działa w najnowszej wersji g ++, ponieważ wydaje się, że nie pozwala już na definiowanie
main
w dowolnym typie.Te dwa programy nie używają nawiasów. Ale wtedy średników wydaje się nie do uniknięcia.
źródło
22 Unikalne znaki z wyjątkiem białych znaków. Oddziela liczby znakiem NUL, który wyświetla się poprawnie w systemie Windows.
Wypróbuj online
Histogram:
źródło
char(0)
), a nie spacją (char(32)
w ASCII / UTF-8). en.cppreference.com/w/cpp/io/manip/ends . Wypróbowałem to na pulpicie Linuksa, żeby się upewnić, a wyjście wygląda tak1234
, jakby nie1 2 3 4
. Wygląda tak samo na wyjściu TIO!"
dla" "
jeśli mogliby używanyiiii
do oddzielania z'0'
za10203040
? Myślę, że możesz argumentować, że w wyjściu binarnym programu nadal znajduje się separator, ale wskazanie tej zmiany i opisanie jej w języku angielskim jest ważne dla twojej odpowiedzi, ponieważ nie jest to zamiana zastępcza! Z przyjemnością usunę moją opinię, jeśli rozszerzysz swoją odpowiedź, aby to wyjaśnić i uzasadnić.