23 personajes únicos que usan dígrafos. (25 sin). No UB
Utilice la sintaxis de inicializador con soporte de C ++ 11 para inicializar en una lista un entero a cero int var{};
evitando =
y 0
. (O en su caso, evitando global iiii
). Esto le proporciona una fuente de ceros distintos de las variables globales (que se inicializan estáticamente a cero, a diferencia de los locales).
Los compiladores actuales aceptan esta sintaxis de manera predeterminada, sin tener que habilitar ninguna opción especial.
(El truco de wraparound entero es divertido, y está bien para jugar al golf con la optimización deshabilitada, pero el desbordamiento firmado es un comportamiento indefinido en ISO C ++. Habilitar la optimización convertirá esos bucles envolventes en bucles infinitos, a menos que compile con gcc / clang -fwrapv
para proporcionar un desbordamiento entero firmado -definido comportamiento: complemento de 2 envolvente.
Dato curioso: ¡ISO C ++ std::atomic<int>
tiene un complemento de 2 bien definido! int32_t
se requiere que sea el complemento de 2 si está definido, pero el comportamiento de desbordamiento no está definido, por lo que aún puede ser un typedef para int
o long
en cualquier máquina donde uno de esos tipos sea de 32 bits, sin relleno, y el complemento de 2).
No es útil para este caso específico:
También puede inicializar una nueva variable como una copia de una existente, con llaves o (con un inicializador no vacío), parens para la inicialización directa .
int a(b)
o int a{b}
son equivalentes aint a = b;
Pero int b();
declara una función en lugar de una variable inicializada a cero.
Además, puede obtener un cero con int()
o char()
, es decir , inicialización cero de un objeto anónimo.
Podemos reemplazar sus <=
comparaciones con <
comparaciones por una simple transformación lógica : haga el incremento del contador de bucle justo después de la comparación, en lugar de en la parte inferior del bucle. En mi opinión, esto es más simple que las alternativas que la gente ha propuesto, como usar ++
en la primera parte de a for()
para hacer un 0 en 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;
}
Podríamos reducir eso a golf, for(int r{}; r++ < n;)
pero la OMI es menos fácil de leer para los humanos. No estamos optimizando para el recuento total de bytes.
Si ya estuviéramos usando h
, podríamos guardar el '
o "
para un espacio.
Suponiendo un entorno ASCII o UTF-8, el espacio tiene un char
valor 32. Podemos crearlo en una variable con bastante facilidad, luegocout << 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
Y, obviamente, se pueden crear otros valores a partir de una secuencia ++
y duplicación, en función de los bits de su representación binaria. Cambiando efectivamente un 0 (nada) o 1 (++) en el LSB antes de duplicar en una nueva variable.
Esta versión usa en h
lugar de '
o "
.
Es mucho más rápido que cualquiera de las versiones existentes (sin depender de un bucle largo), y está libre de Comportamiento indefinido . Se compila sin advertencias con g++ -O3 -Wall -Wextra -Wpedantic
y conclang++
. -std=c++11
es opcional. Es legal y portátil ISO C ++ 11 :)
Tampoco se basa en variables globales. Y lo hice más legible para los humanos con nombres de variables que tienen un significado.
Recuento de bytes únicos: 25 , excluyendo los comentarios que eliminég++ -E
. Y excluyendo el espacio y la nueva línea como su mostrador. Utilicé sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
este askubuntu para contar las ocurrencias de cada personaje, y lo canalicé wc
para contar cuántos caracteres únicos tenía.
#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;
}
}
Los únicos 2 f
caracteres son de for
. Podríamos usar while
bucles en su lugar si tuviéramos un uso w
.
Posiblemente podríamos reescribir los bucles en un estilo de lenguaje ensamblador i < r || goto some_label;
para escribir un salto condicional en la parte inferior del bucle, o lo que sea. (Pero usando en or
lugar de ||
). No, eso no funciona. goto
es una declaración como if
y no puede ser un subcomponente de una expresión como puede en Perl. De lo contrario, podríamos haberlo usado para eliminar los caracteres (
y )
.
Podríamos operar f
para g
con if(stuff) goto label;
en lugar de for
, y ambos bucles siempre se ejecutan al menos 1 iteración por lo que sólo necesitaríamos un bucle-rama en la parte inferior, al igual que una normal de asm do{}while
estructura de bucle. Suponiendo que el usuario ingresa un número entero> 0 ...
Dígrafos y Trígrafos
Afortunadamente, los trigrafos se han eliminado a partir de ISO C ++ 17, por lo que no tenemos que usarlos en ??>
lugar de }
si estamos jugando al golf exclusivo para la revisión más reciente de C ++.
Pero solo trigrafos específicamente: ISO C ++ 17 todavía tiene digrafos como :>
por ]
y %>
para}
. Así que en el costo de usar %
, podemos evitar tanto {
y }
, y el uso %:
de #
un ahorro neto de 2 menos caracteres únicos.
Y C ++ tiene palabras clave de operador como not
para el !
operador o bitor
para el |
operador. Con xor_eq
for ^=
, puede poner a cero una variable con i xor_eq i
, pero tiene varios caracteres que no estaba usando.
Current g++
ya ignora los trigraphs por defecto incluso sin -std=gnu++17
; tiene que usar -trigraphs
para habilitarlos, o -std=c++11
algo por el estricto cumplimiento de un estándar ISO que los incluye.
23 bytes únicos:
%: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;
%>
%>
Pruébalo en línea!
La versión final utiliza una '
comilla simple en lugar de h
o "
para el separador de espacio. No quería dibujar las char c{}
cosas, así que lo eliminé. Imprimir un char es más eficiente que imprimir una cadena, así que lo usé.
Histograma:
$ 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
El separador de espacios (aún sin resolver)
En una respuesta ahora eliminada, Johan Du Toit propuso usar un separador alternativo, específicamente std::ends
. Ese es un carácter NUL char(0)
, y se imprime como ancho cero en la mayoría de los terminales. Entonces la salida se vería así 1234
, no 1 2 3 4
. O peor, separados por basura en cualquier cosa que no colapsó en silencio '\0'
.
Si puede usar un separador arbitrario, cuando el dígito 0
es fácil de crear cout << some_zeroed_var
. Pero nadie quiere 10203040
, eso es peor que ningún separador.
Estaba tratando de pensar en una forma de crear una std::string
retención" "
sin usar char
o un literal de cadena. ¿Quizás agregarle algo? ¿Quizás con un dígrafo para []
establecer el primer byte en un valor de 32
, después de crear uno con longitud 1 a través de uno de los constructores?
Johan también sugirió la std::ios
función miembro fill () que devuelve el carácter de relleno actual. El valor predeterminado para una secuencia lo establece std::basic_ios::init()
y es ' '
.
std::cout << i << std::cout.fill();
reemplaza << ' ';
pero usa en .
lugar de'
.
Con -
, podemos tomar un puntero a cout
y el uso ->fill()
de llamar a la función miembro:
std::cout << (bitand std::cout)->fill()
. O no, no estábamos usando b
ya sea por lo que bien podría haber utilizado &
en lugar de su equivalente léxico, bitand
.
Llamar a una función miembro sin .
o->
Ponlo dentro de una clase y define 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(); }
}
Luego ss s{}
antes del bucle, y std::cout << i << s;
dentro del bucle. Genial, se compila y funciona correctamente, pero tuvimos que usar p
y h
para operator char()
, para una pérdida neta de 1. Al menos evitamos b
hacer funciones miembro public
usando en struct
lugar de class
. (Y podríamos anular la herencia protected
en caso de que alguna vez ayude).