23 unique characters using Digraphs. (25 without). No UB.
Use C++11 braced initializer syntax to list-initialize an integer to zero with int var{};
avoiding =
and 0
. (Or in your case, avoiding global iiii
). This gives you a source of zeros other than global variables (which are statically initialized to zero, unlike locals).
Current compilers accept this syntax by default, without having to enable any special options.
(The integer wraparound trick is fun, and ok for golfing with optimization disabled, but signed overflow is undefined behaviour in ISO C++. Enabling optimization will turn those wraparound loops into infinite loops, unless you compile with gcc/clang -fwrapv
to give signed integer overflow well-defined behaviour: 2's complement wraparound.
Fun fact: ISO C++ std::atomic<int>
has well-defined 2's complement wrap-around! int32_t
is required to be 2's complement if defined at all, but the overflow behaviour is undefined so it can still be a typedef for int
or long
on any machine where one of those types is 32 bits, no padding, and 2's complement.)
Not useful for this specific case:
You can also initialize a new variable as a copy of an existing one, with either braces or (with a non-empty initializer), parens for direct initialization.
int a(b)
or int a{b}
are equivalent to int a = b;
But int b();
declares a function instead of a variable initialized to zero.
Also, you can get a zero with int()
or char()
, i.e. zero-initialization of an anonymous object.
We can replace your <=
compares with <
compares by a simple logic transformation: do the loop-counter increment right after the compare, instead of at the bottom of the loop. IMO this is simpler than the alternatives people have proposed, like using ++
in the first part of a for()
to make a 0 into a 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;
}
We could golf that down to for(int r{}; r++ < n;)
but IMO that's less easy for humans to read. We're not optimizing for total byte count.
If we were already using h
, we could save the '
or "
for a space.
Assuming an ASCII or UTF-8 environment, space is a char
with value 32. We can create that in a variable easily enough, then cout << 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
And other values can obviously be created from a sequence of ++
and doubling, based on the bits of their binary representation. Effectively shifting a 0 (nothing) or 1 (++) into the LSB before doubling into a new variable.
This version uses h
instead of '
or "
.
It's much faster than either of the existing versions (not relying on a long loop), and is free of Undefined Behaviour. It compiles with no warnings with g++ -O3 -Wall -Wextra -Wpedantic
and with clang++
. -std=c++11
is optional. It is legal and portable ISO C++11 :)
It also doesn't rely on global variables. And I made it more human-readable with variable names that have a meaning.
Unique-byte count: 25, excluding the comments which I stripped with g++ -E
. And excluding space and newline like your counter. I used sed 's/\(.\)/\1\n/g' ladder-nocomments.cpp | sort | uniq -ic
from this askubuntu to count occurrences of each character, and piped that into wc
to count how many unique characters I had.
#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;
}
}
The only 2 f
characters are from for
. We could use while
loops instead if we had a use for w
.
We could possibly rewrite the loops into an assembly-language style of i < r || goto some_label;
to write a conditional jump at the bottom of the loop, or whatever. (But using or
instead of ||
). Nope, that doesn't work. goto
is a statement like if
and can't be a sub-component of an expression like it can in Perl. Otherwise we could have used it to remove the (
and )
characters.
We could trade f
for g
with if(stuff) goto label;
instead of for
, and both loops always run at least 1 iteration so we'd only need one loop-branch at the bottom, like a normal asm do{}while
loop structure. Assuming the user inputs an integer > 0...
Digraphs and Trigraphs
Fortunately, trigraphs have been removed as of ISO C++17 so we don't have to use ??>
instead of }
if we're unique-golfing for the most recent C++ revision.
But only trigraphs specifically: ISO C++17 still has digraphs like :>
for ]
and %>
for }
. So at the cost of using %
, we can avoid both {
and }
, and use %:
for #
for a net saving of 2 fewer unique characters.
And C++ has operator keywords like not
for the !
operator, or bitor
for the |
operator. With xor_eq
for ^=
, you could zero a variable with i xor_eq i
, but it has multiple characters you weren't using.
Current g++
already ignores trigraphs by default even without -std=gnu++17
; you have to use -trigraphs
to enable them, or -std=c++11
or something for strict conformance to an ISO standard that does include them.
23 unique bytes:
%: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;
%>
%>
Try it online!
The final version uses a '
single-quote instead of h
or "
for the space separator. I didn't want to digraph the char c{}
stuff so I deleted it. Printing a char is more efficient than printing a string, so I used that.
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
The space separator (still unsolved)
In a now-deleted answer, Johan Du Toit proposed using an alternate separator, specifically std::ends
. That's a NUL character, char(0)
, and prints as zero-width on most terminals. So the output would look like 1234
, not 1 2 3 4
. Or worse, separated by garbage on anything that didn't silently collapse '\0'
.
If you can use an arbitrary separator, when the digit 0
is easy to create with cout << some_zeroed_var
. But nobody wants 10203040
, that's even worse than no separator.
I was trying to think of a way to create a std::string
holding a " "
without using char
or a string literal. Maybe appending something to it? Maybe with a digraph for []
to set the first byte to a value of 32
, after creating one with length 1 via one of the constructors?
Johan also suggested the std::ios
fill() member function which returns the current fill character. The default for a stream is set by std::basic_ios::init()
, and is ' '
.
std::cout << i << std::cout.fill();
replaces << ' ';
but uses .
instead of '
.
With -
, we can take a pointer to cout
and use ->fill()
to call the member function:
std::cout << (bitand std::cout)->fill()
. Or not, we weren't using b
either so we might as well have used &
instead of its lexical equivalent, bitand
.
Calling a member function without .
or ->
Put it inside a class, and 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(); }
}
Then ss s{}
before the loop, and std::cout << i << s;
inside the loop. Great, it compiles and works properly, but we had to use p
and h
for operator char()
, for a net loss of 1.
At least we avoided b
to make member functions public
by using struct
instead of class
. (And we could override the inheritance with protected
in case that ever helps).