23 caractères uniques à l'aide de Digraphs. (25 sans). Pas d'UB.
Utilisez la syntaxe d'initialisation renforcée C ++ 11 pour lister-initialiser un entier à zéro en int var{};
évitant =
et 0
. (Ou dans votre cas, en évitant global iiii
). Cela vous donne une source de zéros autres que les variables globales (qui sont initialisées statiquement à zéro, contrairement aux locaux).
Les compilateurs actuels acceptent cette syntaxe par défaut, sans avoir à activer d'options spéciales.
(L'astuce de bouclage entier est amusante, et ok pour le golf avec l'optimisation désactivée, mais le débordement signé est un comportement indéfini dans ISO C ++. L'activation de l'optimisation transformera ces boucles enveloppantes en boucles infinies, sauf si vous compilez avec gcc / clang -fwrapv
pour donner un puits de débordement d'entier signé bien - comportement défini: enveloppement du complément à 2.
Fait amusant: ISO C ++ std::atomic<int>
a un complément de 2 bien défini! int32_t
doit être le complément de 2 s'il est défini, mais le comportement de débordement n'est pas défini, il peut donc toujours être un typedef pour int
ou long
sur n'importe quelle machine où l'un de ces types est de 32 bits, sans remplissage et complément de 2.)
Inutile pour ce cas spécifique:
Vous pouvez également initialiser une nouvelle variable en tant que copie d'une variable existante, avec des accolades ou (avec un initialiseur non vide), des parens pour l'initialisation directe .
int a(b)
ou int a{b}
équivalent àint a = b;
Mais int b();
déclare une fonction au lieu d'une variable initialisée à zéro.
En outre, vous pouvez obtenir un zéro avec int()
ou char()
, c'est -à- dire l' initialisation zéro d'un objet anonyme.
Nous pouvons remplacer vos <=
comparaisons par des <
comparaisons par une simple transformation logique : effectuez l'incrémentation du compteur de boucles juste après la comparaison, plutôt qu'en bas de la boucle. L'OMI est plus simple que les alternatives proposées par les gens, comme utiliser ++
dans la première partie de a for()
pour transformer 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;
}
Nous pourrions jouer au golf, for(int r{}; r++ < n;)
mais l'OMI est moins facile à lire pour les humains. Nous n'optimisons pas le nombre total d'octets.
Si nous utilisions déjà h
, nous pourrions enregistrer le '
ou "
pour un espace.
En supposant un environnement ASCII ou UTF-8, l'espace est un char
avec une valeur 32. Nous pouvons créer cela dans une variable assez facilement, puiscout << 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
Et d'autres valeurs peuvent évidemment être créées à partir d'une séquence ++
et d'un doublement, en fonction des bits de leur représentation binaire. Décaler efficacement un 0 (rien) ou 1 (++) dans le LSB avant de doubler dans une nouvelle variable.
Cette version utilise à la h
place de '
ou "
.
Il est beaucoup plus rapide que l'une des versions existantes (ne s'appuyant pas sur une longue boucle) et est exempt de comportement indéfini . Il se compile sans avertissement avec g++ -O3 -Wall -Wextra -Wpedantic
et avecclang++
. -std=c++11
est facultatif. Il est légal et portable ISO C ++ 11 :)
Il ne dépend pas non plus des variables globales. Et je l'ai rendu plus lisible par l'homme avec des noms de variables qui ont un sens.
Nombre d'octets uniques: 25 , à l' exclusion des commentaires avec lesquels j'ai supprimég++ -E
. Et à l'exclusion de l'espace et de la nouvelle ligne comme votre comptoir. J'ai utilisé à sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
partir de cet askubuntu pour compter les occurrences de chaque personnage, et wc
je l'ai canalisé pour compter le nombre de caractères uniques que j'avais.
#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;
}
}
Les 2 seuls f
caractères sont de for
. Nous pourrions utiliser des while
boucles à la place si nous en avions une utilisation w
.
Nous pourrions éventuellement réécrire les boucles dans un style en langage assembleur i < r || goto some_label;
pour écrire un saut conditionnel au bas de la boucle, ou autre chose. (Mais en utilisant or
au lieu de ||
). Non, ça ne marche pas. goto
est une instruction comme if
et ne peut pas être un sous-composant d'une expression comme il le peut en Perl. Sinon, nous aurions pu l'utiliser pour supprimer les caractères (
et )
.
Nous pourrions échanger f
pour g
avec if(stuff) goto label;
au lieu de for
, et les deux boucles exécutent toujours au moins 1 itération, nous n'aurions donc besoin que d'une seule branche de boucle en bas, comme une do{}while
structure de boucle asm normale . En supposant que l'utilisateur entre un entier> 0 ...
Digraphes et trigraphes
Heureusement, les trigraphes ont été supprimés à partir d'ISO C ++ 17 , nous n'avons donc pas à utiliser à la ??>
place de }
si nous sommes uniques pour la révision C ++ la plus récente.
Mais uniquement les trigraphes: ISO C ++ 17 a toujours des digraphes comme :>
pour ]
et %>
pour}
. Ainsi , au coût d'utilisation %
, nous pouvons éviter à la fois {
et }
, et utiliser %:
pour #
une économie nette de 2 moins de caractères uniques.
Et C ++ a des mots-clés d'opérateur comme not
pour l' !
opérateur, ou bitor
pour l' |
opérateur. Avec xor_eq
for ^=
, vous pouvez mettre à zéro une variable avec i xor_eq i
, mais elle contient plusieurs caractères que vous n'utilisiez pas.
Le courant g++
ignore déjà les trigraphes par défaut même sans -std=gnu++17
; vous devez utiliser -trigraphs
pour les activer, ou -std=c++11
quelque chose pour une stricte conformité à une norme ISO qui les inclut.
23 octets uniques:
%: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;
%>
%>
Essayez-le en ligne!
La version finale utilise un '
guillemet simple au lieu de h
ou "
pour le séparateur d'espace. Je ne voulais pas digraphier le char c{}
truc alors je l'ai supprimé. L'impression d'un caractère est plus efficace que l'impression d'une chaîne, donc je l'ai utilisé.
Histogramme:
$ 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
Le séparateur d'espace (toujours non résolu)
Dans une réponse maintenant supprimée, Johan Du Toit a proposé d'utiliser un séparateur alternatif, en particulier std::ends
. C'est un caractère NUL char(0)
, et s'imprime en largeur nulle sur la plupart des terminaux. Ainsi, la sortie ressemblerait 1234
, non 1 2 3 4
. Ou pire, séparés par des ordures sur tout ce qui ne s'est pas effondré en silence '\0'
.
Si vous pouvez utiliser un séparateur arbitraire, lorsque le chiffre 0
est facile à créer avec cout << some_zeroed_var
. Mais personne ne veut 10203040
, c'est encore pire que pas de séparateur.
J'essayais de penser à un moyen de créer un std::string
holding a" "
sans utiliser char
ou un string littéral. Peut-être y ajouter quelque chose? Peut-être avec un digraphe pour []
définir le premier octet sur une valeur de 32
, après en avoir créé un de longueur 1 via l'un des constructeurs?
Johan a également suggéré la std::ios
fonction membre fill () qui renvoie le caractère de remplissage actuel. La valeur par défaut pour un flux est définie par std::basic_ios::init()
et est ' '
.
std::cout << i << std::cout.fill();
remplace << ' ';
mais utilise à la .
place de'
.
Avec -
, nous pouvons prendre un pointeur vers cout
et à utiliser ->fill()
pour appeler la fonction de membre:
std::cout << (bitand std::cout)->fill()
. Ou pas, nous n'utilisions pas b
non plus, donc nous pourrions aussi bien avons utilisé au &
lieu de son équivalent lexical, bitand
.
Appel d'une fonction membre sans .
ou->
Mettez-le dans une classe et définissez 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(); }
}
Puis ss s{}
avant la boucle, et std::cout << i << s;
à l'intérieur de la boucle. Génial, il compile et fonctionne correctement, mais nous avons dû utiliser p
et h
pour operator char()
, pour une perte nette de 1. Au moins, nous avons évité b
de créer des fonctions membres public
en utilisant struct
au lieu de class
. (Et nous pourrions remplacer l'héritage protected
au cas où cela aiderait).