23 personaggi unici usando Digraphs. (25 senza). No UB.
Utilizzare la sintassi dell'inizializzatore rinforzato C ++ 11 per elencare l'inizializzazione di un numero intero su zero int var{};
evitando =
e 0
. (O nel tuo caso, evitando globale iiii
). Questo ti dà una fonte di zeri diversa dalle variabili globali (che sono inizializzate staticamente a zero, a differenza dei locali).
I compilatori correnti accettano questa sintassi per impostazione predefinita, senza dover abilitare alcuna opzione speciale.
(Il trucco avvolgente intero è divertente, e va bene per giocare a golf con l'ottimizzazione disabilitata, ma l'overflow firmato è un comportamento indefinito in ISO C ++. L'ottimizzazione dell'ottimizzazione trasformerà quei loop avvolgenti in loop infiniti, a meno che non si compili con gcc / clang -fwrapv
per fornire bene l'overflow di numeri interi firmati definito comportamento: avvolgente complemento di 2.
Curiosità: ISO C ++ std::atomic<int>
ha un complemento di 2 ben definito! int32_t
è necessario essere il complemento di 2 se definito affatto, ma il comportamento di overflow non è definito, quindi può ancora essere un typedef per int
o long
su qualsiasi macchina in cui uno di questi tipi è 32 bit, nessuna spaziatura e complemento di 2).
Non utile per questo caso specifico:
È inoltre possibile inizializzare una nuova variabile come copia di una esistente, con parentesi graffe o (con un inizializzatore non vuoto), parentesi per l'inizializzazione diretta .
int a(b)
o int a{b}
sono equivalenti aint a = b;
Ma int b();
dichiara una funzione invece di una variabile inizializzata a zero.
Inoltre, è possibile ottenere uno zero con int()
o char()
, ovvero inizializzazione zero di un oggetto anonimo.
Possiamo sostituire i tuoi <=
confronti con i <
confronti con una semplice trasformazione logica : esegui l'incremento del contatore del ciclo subito dopo il confronto, anziché nella parte inferiore del ciclo. IMO questo è più semplice delle alternative che le persone hanno proposto, come usare ++
nella prima parte di a for()
per fare uno 0 in un 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;
}
Potremmo giocare a golf for(int r{}; r++ < n;)
ma IMO è meno facile da leggere per gli umani. Non stiamo ottimizzando per il conteggio totale dei byte.
Se stessimo già utilizzando h
, potremmo salvare '
o "
per uno spazio.
Supponendo un ambiente ASCII o UTF-8, lo spazio è a char
con valore 32. Possiamo crearlo in una variabile abbastanza facilmente, quindicout << 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
E altri valori possono ovviamente essere creati da una sequenza di ++
e raddoppio, in base ai bit della loro rappresentazione binaria. Spostando efficacemente uno 0 (niente) o 1 (++) nell'LSB prima di raddoppiare in una nuova variabile.
Questa versione utilizza h
invece di '
o "
.
È molto più veloce di una delle versioni esistenti (non si basa su un ciclo lungo) ed è privo di comportamento indefinito . Si compila senza avvisi con g++ -O3 -Wall -Wextra -Wpedantic
e conclang++
. -std=c++11
è facoltativo. È legale e portatile ISO C ++ 11 :)
Inoltre non si basa su variabili globali. E l'ho reso più leggibile dall'uomo con nomi di variabili che hanno un significato.
Numero di byte univoci: 25 , esclusi i commenti con cui ho eliminatog++ -E
. Ed escludendo spazio e newline come il tuo contatore. Ho usato sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
da questo askubuntu per contare le occorrenze di ciascun personaggio, e ho analizzato questowc
per contare quanti personaggi unici avevo.
#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;
}
}
Gli unici 2 f
caratteri provengono da for
. Potremmo usare i while
loop invece se ne avessimo bisogno w
.
Potremmo eventualmente riscrivere i loop in uno stile di linguaggio assembly i < r || goto some_label;
per scrivere un salto condizionale nella parte inferiore del loop, o altro. (Ma usando or
invece di ||
). No, non funziona. goto
è un'affermazione simile if
e non può essere un sottocomponente di un'espressione come in Perl. Altrimenti avremmo potuto usarlo per rimuovere i caratteri (
e )
.
Potremmo scambiare f
per g
con if(stuff) goto label;
invece di for
, e entrambi i loop sempre eseguito almeno 1 iterazione così avremmo bisogno di un solo ciclo-ramo in basso, come un normale asm do{}while
struttura ad anello. Supponendo che l'utente inserisca un numero intero> 0 ...
Digraphs e Trigraphs
Fortunatamente, le trigrafi sono state rimosse a partire da ISO C ++ 17, quindi non dobbiamo usarle al ??>
posto del }
golf unico per la revisione C ++ più recente.
Ma solo trigrafi specifici: ISO C ++ 17 ha ancora digrafi come :>
a favore ]
e %>
a favore}
. Quindi al costo di utilizzo %
, siamo in grado di evitare sia {
e }
, e utilizzare %:
per #
un risparmio netto di 2 meno personaggi unici.
E C ++ ha parole chiave dell'operatore come not
per l' !
operatore o bitor
per l' |
operatore. Con xor_eq
for ^=
, puoi azzerare una variabile con i xor_eq i
, ma ha più caratteri che non stavi utilizzando.
Current g++
ignora già le trigrafi di default anche senza -std=gnu++17
; devi usarli -trigraphs
per abilitarli, o -std=c++11
qualcosa per rigorosa conformità a uno standard ISO che li include.
23 byte unici:
%: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;
%>
%>
Provalo online!
La versione finale utilizza una '
virgoletta singola anziché h
o "
per il separatore di spazi. Non volevo digerire la char c{}
roba, quindi l'ho cancellata. La stampa di un carattere è più efficiente della stampa di una stringa, quindi l'ho usata.
Istogramma:
$ 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
Il separatore di spazio (ancora irrisolto)
In una risposta ora cancellata, Johan Du Toit ha proposto di utilizzare un separatore alternativo, in particolare std::ends
. È un carattere NUL char(0)
e viene stampato come larghezza zero sulla maggior parte dei terminali. Quindi il risultato sarebbe simile 1234
, no 1 2 3 4
. O peggio, separati da immondizia su tutto ciò che non è crollato silenziosamente '\0'
.
Se è possibile utilizzare un separatore arbitrario, quando 0
è facile creare la cifra cout << some_zeroed_var
. Ma nessuno vuole 10203040
, è anche peggio di nessun separatore.
Stavo cercando di pensare a un modo per creare una std::string
holding a" "
senza usare char
o una stringa letterale. Forse aggiungere qualcosa ad esso? Forse con un digraph per []
impostare il primo byte su un valore di 32
, dopo averne creato uno con lunghezza 1 tramite uno dei costruttori?
Johan ha anche suggerito la std::ios
funzione membro fill () che restituisce il carattere di riempimento corrente. L'impostazione predefinita per uno stream è impostata da std::basic_ios::init()
, ed è ' '
.
std::cout << i << std::cout.fill();
sostituisce << ' ';
ma utilizza .
invece di'
.
Con -
, possiamo prendere un puntatore a cout
e l'uso ->fill()
di chiamare la funzione membro:
std::cout << (bitand std::cout)->fill()
. O no, non eravamo utilizzando b
sia così potremmo anche usato &
al posto del suo equivalente lessicale, bitand
.
Chiamare una funzione membro senza .
o->
Inseriscilo in una classe e definiscilo 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(); }
}
Quindi ss s{}
prima del loop e std::cout << i << s;
all'interno del loop. Fantastico, si compila e funziona correttamente, ma abbiamo dovuto usare p
e h
per operator char()
, per una perdita netta di 1. Almeno abbiamo evitato b
di fare funzioni membro public
usando struct
invece di class
. (E potremmo scavalcare l'eredità con protected
nel caso in cui ciò possa mai aiutare).