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).
5It is certainly novel to ask for [tag:tips] regarding any other scoring criteria than [tag:code-golf], but afaict, it is on-topic, since the [tag:tips] pages says to make it a better answer to a programming challenge that is on-topic. – Adám – 2019-05-13T15:23:19.413
This would have a wider interest if extended to any language – Luis Mendo – 2019-05-13T16:47:04.423
8@LuisMendo I don't really think that is true in this case, since many languages completely trivialise this scoring scheme. If this user wants help learning to "unique golf" it only really makes sense in a subset of languages, so I think this is much better as a tip than as a generic challenge. That said the base problem could probably be a challenge if someone wants to post it. – FryAmTheEggman – 2019-05-13T16:52:56.313
So how many characters would this be in Unary? Yeah, that'd be super-exploitable. – Darrel Hoffman – 2019-05-13T19:45:50.203
3I think you can use digraphs <% and %> instead of curly braces, and I think I missed some. – my pronoun is monicareinstate – 2019-05-14T00:09:11.153
2I definitely missed some. # is %:, so you can get rid of three characters and introduce one ( { => <%, } => %>, # => %:) and get to 25. If you combine this with the answer below, I think you can get 24. – my pronoun is monicareinstate – 2019-05-14T00:36:30.587
@someone +1 Oh my god. Where did you learn these tricks? :) – LanceHAOH – 2019-05-14T00:51:31.300
2@LanceHAOH Trigraphs are extremely common in [underhanded] questions, and digraphs show up as well when reading about trigraphs. – my pronoun is monicareinstate – 2019-05-14T01:20:45.653
@someone: fortunately trigraphs have been removed as of ISO C++17. But only trigraphs, not digraphs or operator keywords like
– Peter Cordes – 2019-05-14T06:55:19.673not
instead of!
. So if you're unique-golfing in the most recent C++ version, you get to ignore trigraphs, but not digraphs. :/ 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 a standard that does include them. But yes,gcc -std=c++17
does do%>
as}