Some notes.
I’m assuming you want to use D largely, but not entirely, for competitive programming. That’s me right now.
Basics
Syntax is very similar. Function definitions, semicolon-terminated
statements, variable declarations, and so on. You can declare
int main() {...}
or void main() {...}
or
something with arguments.
Basic types like bool
and int
and
double
are all there. Wonderfully, long
is 64
bits. Instead of unsigned
whatever, just prefix a
u
, e.g. uint
.
Arithmetic operators and bit operators are all there too, including
unsigned right shift >>>
. Although ^
is still xor, D has exponentiation as ^^
. Sadly,
%
is still same-sign remainder; there’s no true mod.
import std.stdio;
Casts look like cast(int) x;
Control Flow
if
, while
, for
,
do
, and even switch
all work as you’d expect,
along with break
and continue
.
foreach
is the nice addition though. Not only can you
iterate over arrays and stuff, but range loops go like:
foreach (i; 0 .. 10) writeln(i);
If you want both the index and the element (a la
zipWithIndex
):
foreach (i, e; array) writeln(i, e);
And to modify the element in place:
foreach (ref e; array) e += 4;
This ref
is also the syntax to pass something by
reference to a function, by the way. Functions are otherwise pretty much
the same.
void func(ref int x) { x += 4; }
I/O
You have access to scanf
and printf
, for
some weird reason.
But D’s alternatives are readf
and writef
.
They also have variants writefln
to add a newline. You
often won’t even need writef
anyway, since
write
and writeln
take any number of arguments
and turn them into strings before printing.
int n = 100;
("Case #1: ", n, " is the answer."); writeln
Two gotchas with readf
:
-
readf
specifiers don’t skip white space, so where you’d write:("%d%d", &a, &b); scanf
you should write:
(" %d %d", &a, &b); readf
-
readf
on a string reads to end of input.
So call readln
instead, to read a line: either with no
arguments to return a string or with a buffer to read it into there.
This also reads the newline, but after import std.string;
you can call s = chomp(s);
to get rid of control
characters.
Call functions like stderr.writef(...)
for debug
output.
I just want my int-scanning macro so I don’t forget my ampersands, please
I can do better: readf
infers the type of whatever you
give it. This variadic magic fits on an 80-character line. After seeing
this, just thinking about cstdio
code makes me angry.
void scan(T...)(ref T args) { foreach (ref arg; args) readf(" %s", &arg); }
Arrays
int[100008] a;
gives you a static (fixed-length)
array.
int[] b;
gives you a dynamic (variable-length) array.
It’s sort of like C++’s STL vector
. Actually, it’s properly
called a slice, and you can read more about it in the D Slices article, which
gets linked quite often in the forum.
Unfortunately after a tragic experience on Codeforces, I’ve
discovered its reallocation semantics seems somewhat slow when compared
to similar uses of C++ vector
for one big problem:
after you pop an element off the back or otherwise decrease the
length, the slice must be reallocated to insert more elements.
So far, I’ve found std.container.Array
better fits such
needs. But if you only push things in, iterate across them, or delete
everything at once, I think slices are okay.
Declare Array
s like:
!int a; Array
You use a bang for templates in D.
Anyway, in general, binary ~
is the append or
concatenate operator; you can append elements by writing
b ~= 3;
or b ~= [5, 7];
For slices,
experimenting shows that D does roughly double the slice after each
reallocation so that this is amortized O(1), as you’d hope, although I
can’t find any documentation saying so. You can get the length with
b.length
.
Array
s, on the other hand, multiply their capacities by
3/2 on reallocation.
Also, if you import std.array;
, you get to do
b.front
and b.back
on slices (you don’t need
parentheses!), which are refs through which you can modify the element.
You also get b.popFront()
and b.popBack()
(which are void
like their C++ STL counterparts, to my
disappointment) and b.empty
. There doesn’t appear to be a
.clear()
function but you can just set
b.length = 0;
. Also, calling .dup
makes a
dynamic copy.
Strings (string
) are just an alias for immutable
char[]
s. (So if you want to store a string in a
char[]
, call .dup
. But you can still
~=
a string to a variable of type string
; it
just replaces the variable with a new copy.)
Array
s also have .front
,
.back
, .length
, .empty
, indexing
and so on. Their popping methods are called .removeFront()
and removeBack()
, however. Array
s do
have a .clear()
method, but unlike STL vector
,
it throws away any capacity. It appears setting .length = 0
is slightly faster if you expect to make the Array
large
again soon.
Deques, etc.
This is so far the one place D has disappointed me relative to C++,
and only mildly. Your best bet is DList
(doubly-linked
list) from std.container
. The type looks like
DList!int
. As above, you can .front
,
.popFront()
, and also .insertFront(x)
, and
same for back
and cognates, of course.
You can also .empty
, but sadly, you can’t get the length
of a DList
. But now that I think about it,
.empty
is often enough anyway if you’re BFSing or whatever,
and you can always maintain the length yourself somewhere.
You can declare associative arrays by putting stuff between the same square brackets you used for arrays. These are hash tables, not ordered. The type is just e.g.
string[int] map; // int to string map
Test for key inclusion Pythonically with in
, as:
if (key in map) map[key] += 1;
It also supports .length
; .keys
and
.values
(dynamic array copies); .byKey()
and
.byValue()
and .byKeyValue()
(iterable-ish
things, not copies); .get
(pass a second default value to
return if )
There is a RedBlackTree
and a BinaryHeap
in std.container
.
Structs
As you’d expect, but no semicolon.
struct Pt {
int x;
int y;
}
You get ==
and a constructor Pt(42, 1337)
for free.
Debug
Sweet: just surround a block with debug
.
debug {
// code here
}
Compile with -debug
to enable.
You also get assert(...);
, which is normally enabled;
you compile with -release
to disable.
Pairs/Tuples
Not as beautiful as I’d hoped, but certainly not worse than C++. The import is
import std.typecons;
The type looks like Tuple!(int, string)
. Construct as
tuple(1337, "hi")
. Access elements with [0]
,
[1]
and so on.
The nice thing is that these are real tuples that can hold more than two things, I guess.
Also, interestingly, you can foreach
-iterate across a
tuple, which gets expanded at compile-time.
Alias
D’s alternatives to typedef
s and some other things.
alias Weight = double;
Sorting and Other Algorithms
import std.algorithm;
No s.begin(), s.end()
nonsense. Just
sort(array)
. Optionally, you can specify a comparator with
either a string or a function:
!"a > b"(array);
sort
!((x, y) => x > y)(array);
sort
bool myComp(int x, int y) @safe pure nothrow { return x > y; }
!myComp(array); sort
The string made me kind of uncomfortable at first, but I guess that’s
my reflex to JavaScript string callbacks and the same concerns don’t
apply here. There’s no eval
ing, it’s all compile time, and
local variables don’t leak; any expression involving a
and
b
works.
What else is there? min
and max
, which
support more than 2 arguments; swap
;
fill(array, val)
; sum
.
Extra Things
Inside indexes, $
is a shortcut for array length, so
a[$-1]
is the last element. You can do stuff like
a[$/2]
too.
D has const
, which has the same meaning as in C++ (you
can’t modify the variable through this pointer) but it also has
two more powerful modifiers: immutable
(this variable is
guaranteed not-modifiable through any pointer) and enum
(compile-time constants, as if #define
d). If you write
enum x = ...
you can force D to calculate the expression at
compile-time, even if it’s very complicated involving function calls and
whatnot
Other function parameter modifiers than ref
include
in
, out
, lazy
.
The case
s of switch
has some extra goodies.
You can write
case 2: .. case 5:
or
case 2, 4:
Also, remember how you strangely import stuff from
std.array
and then you can call things on arrays? This is
actually Uniform Function Call Syntax (UFCS) sugar; you
can write array.front
to call front(array)
. Of
course, you can directly call front(array)
too. This is how
the std.array
functions work.
You can even do it yourself, although I probably wouldn’t advise it:
int foo(int a, int b) {
return a + b + 1;
}
(3.foo(4)); writeln
There are functional goodies filter
, map
,
reduce
, which are also called like sort
, with
the bang !
template-ish syntax. UFCS lets you chain
functional goodies like array.map!"a + 1".filter!"a > 5"
if you want to. You can call reduce(seed, array)
(= foldl)
or reduce(array)
(= foldl1).
These goodies work on “ranges”, not just arrays, which are things
that allow sequential access in general, like how C++ uses pairs of
generators. Note that filter
and map
return
lazy ranges; you can call array
on the results (after
importing std.array
) to convert it to an array.
std.range
has lots of other more basic things you’d
expect in a functional language:
take
,drop
,repeat
,cycle
(all with the same meaning as in Haskell)retro
is a reversed version of a range like Python’sreversed
chain(...)
chains two or more ranges together like Python’sitertools.chain
chunks(n)
cuts the source into chunks of length nonly
yields a range of its varargs.
The function to
from std.conv
does
conversions between types, and is quite good at it,
e.g. to!int
and to!string
.
Operator overloading is a bit weird (but see D’s rationale):
import std.stdio;
import std.algorithm;
import std.array;
struct Pt {
int x, y;
(string op)(in Pt o)
Pt opBinaryif (op == "+") // this is checked at compile time
{
return Pt(x + o.x, y + o.x);
}
int opCmp()(const ref Pt o) const {
if (x != o.x) return x < o.x ? -1 : 1;
if (y != o.y) return y < o.y ? -1 : 1;
return 0;
}
}
void main() {
[] ps = [ Pt(1, 2), Pt(3, 4), Pt(1, 5), Pt(3, 2) ];
Pt(ps);
sort(ps);
writeln= ps.map!(a => a + Pt(100, 100)).array;
ps = ps.reduce!"a + b";
Pt res (ps);
writeln(res);
writeln}
opCmp
is three-valued, and the comparison operators
<
, <=
, >
,
>=
are derived from it. Unfortunately, there doesn’t
seem to be a more convenient way to write this that is still as fast;
you can do cmp(only(x, y), only(o.x, o.y))
, using
cmp
from std.algorithm
for lexicographical
comparison and only
from std.range
to create a
lazy range, but it still takes 3 to 4 times as much time. (This version
is then 3 to 4 times faster than if you
cmp([x, y], [o.x, o.y])
.)
Pleasingly, structs have a built-in definition of
==
.
Note, by the way, we can’t use map!"a + Pt(100,100)"
because “Pt” isn’t available in the thing that compiles the string into
the function.
Template
Okay, I added a few more imports, but this is still much shorter than my C++ template.
I don’t need any of the range-loop macros or scanning macros or debug
macros or (s).begin(), (s).end()
macros or vector-dumping
function (vectors have nice to!string
already) or
typedefs.
I might need to add a minify
and maxify
,
but the definitions of those will be shorter than C++ too. w00t. For now
(this’ll get updated later):
import std.stdio, std.array, std.range, std.string, std.typecons;
import std.algorithm, std.container, std.math, std.numeric, std.random;
void scan(T...)(ref T args) { foreach (ref arg; args) readf(" %s", &arg); }
void minify(T)(ref T a, in T b) { if (a > b) a = b; }
void maxify(T)(ref T a, in T b) { if (a < b) a = b; }
void ewriteln(T...)(T args) { stderr.writeln("\033[35m", args, "\033[0m"); }
int ilen(T)(const ref T a) { return cast(int)(T.length); }