Comment #0 by bearophile_hugs — 2010-08-06T09:31:51Z
In Python it's often useful to join tuples. So I suggest to add this operator to the tuples of std.typecons.
A problem arises when you try to concatenate two Tuple that share one or more field names. This problem can be faced in several different ways, here I show two different solutions.
Both solutions can allow to perform Tuple~Tuple or Tuple~struct but not struct~Tuple. To perform struct~Tuple you have to use Tuple!()()~struct~Tuple (I don't know if this can be improved).
This first version refuses to join two tuples if two field names clash:
alias T TypesAndStrings; // better to move this at the top of the Tuple struct
///
auto opBinary(string op, TOther)(TOther other)
if (op == "~" && is(TOther == struct) &&
(!__traits(compiles, { void isTuple(U...)(Tuple!U){} isTuple(other); }) ||
distinctFieldNames!(T, TOther.TypesAndStrings)() )) {
// is TOther a Tuple?
static if (__traits(compiles, { void isTuple(U...)(Tuple!U){} isTuple(other); })) {
enum string fieldsName = "field";
alias TOther.Types OtherTypes;
Tuple!(T, TOther.TypesAndStrings) result;
} else {
enum string fieldsName = "tupleof";
alias typeof(TOther.tupleof) OtherTypes;
Tuple!(T, OtherTypes) result;
}
// copy fields of this instance
foreach (i, Unused; Types) // static foreach
static if (__traits(isStaticArray, Types[i]))
mixin( Format!("result.field[%s][] = this.%s[%s];", i, fieldsName, i) );
else
mixin( Format!("result.field[%s] = this.%s[%s];", i, fieldsName, i) );
// copy fields of other instance
foreach (i, Unused; OtherTypes) // static foreach
static if (__traits(isStaticArray, OtherTypes[i]))
mixin( Format!("result.field[%s][] = other.%s[%s];", i + Types.length, fieldsName, i) );
else
mixin( Format!("result.field[%s] = other.%s[%s];", i + Types.length, fieldsName, i) );
return result;
}
+/
This second version removes the fields names from both tuples if and only if two or more field names clash:
alias T TypesAndStrings; // better to move this at the top of the Tuple struct
///
auto opBinary(string op, TOther)(TOther other) if (op == "~" && is(TOther == struct)) {
// is TOther a Tuple?
static if (__traits(compiles, { void isTuple(U...)(Tuple!U){} isTuple(other); })) {
enum string fieldsName = "field";
alias TOther.Types OtherTypes;
static if (distinctFieldNames!(T, TOther.TypesAndStrings)()) {
Tuple!(T, TOther.TypesAndStrings) result;
} else {
Tuple!(Types, OtherTypes) result;
}
} else {
enum string fieldsName = "tupleof";
alias typeof(TOther.tupleof) OtherTypes;
Tuple!(T, OtherTypes) result;
}
// copy fields of this instance
foreach (i, Unused; Types) // static foreach
static if (__traits(isStaticArray, Types[i]))
mixin( Format!("result.field[%s][] = this.%s[%s];", i, fieldsName, i) );
else
mixin( Format!("result.field[%s] = this.%s[%s];", i, fieldsName, i) );
// copy fields of other instance
foreach (i, Unused; OtherTypes) // static foreach
static if (__traits(isStaticArray, OtherTypes[i]))
mixin( Format!("result.field[%s][] = other.%s[%s];", i + Types.length, fieldsName, i) );
else
mixin( Format!("result.field[%s] = other.%s[%s];", i + Types.length, fieldsName, i) );
return result;
}
}
---------------------
To work that code needs Iota and distinctFieldNames that can be found in bug 4582 :
private template Iota(int stop) { // this is useful in general
static if (stop <= 0)
alias TypeTuple!() Iota;
else
alias TypeTuple!(Iota!(stop-1), stop-1) Iota;
}
private bool distinctFieldNames(T...)() {
enum int tlen = T.length; // can't move this below, probably DMD bug
foreach (i1; Iota!(tlen))
static if (is(typeof(T[i1]) : string))
foreach (i2; Iota!(tlen))
static if (i1 != i2 && is(typeof(T[i2]) : string))
if (T[i1] == T[i2])
return false;
return true;
}
---------------------
Both versions need an extra alias that keeps all the Tuple instantiation arguments:
alias T TypesAndStrings;
This code shown to me by Philippe Sigaud avoids to define this alias, but this solution looks too much complex for this job:
private string[3] _between(char b, char e, string s)() {
int foundb, ib;
string notFound = "";
foreach (i, c; s) {
if (c == b) {
if (foundb == 0) {
foundb = 1;
ib = i+1;
continue;
} else {
foundb++;
}
}
if (c == e) {
if (foundb == 1)
return [s[0 .. ib-1], s[ib .. i], s[i+1 .. $]];
else
foundb--;
}
}
return [s, notFound, notFound];
}
/// Given an instantiated template, returns a tuple of its arguments.
template TemplateParameters(T) {
mixin("alias TypeTuple!(" ~ _between!('(', ')' , T.stringof)[1] ~ ") TemplateParameters;");
}
If the _between() + TemplateParameters() are added to Phobos then I can use them instead of the TypesAndStrings alias.
Comment #1 by bearophile_hugs — 2011-07-25T05:30:07Z
Slicing too is sometimes useful:
import std.typecons;
void main() {
auto t1 = tuple(10, 20, 30, 40, 50);
auto t2 = tuple(100, 200, 300);
auto t3 = t1[0 .. 2]; // tuple slicing
auto t4 = t1 ~ t2; // tuple concat
}
Comment #2 by bearophile_hugs — 2013-05-14T09:49:37Z
I suggest to add the support for Tuple concatenation and join:
In Python 2.6:
>>> t1 = (1, 2)
>>> t1 + t1
(1, 2, 1, 2)
>>> t1 + (3,)
(1, 2, 3)
Proposed D syntax:
void main() {
import std.typecons;
auto t1 = tuple(1, 2);
auto t2 = t1 ~ t1;
auto t3a = t1 ~ 3;
auto t3b = t1 ~ tuple(3);
}
An use case, this computes the frequency of the first digit (Benford's Law):
import std.stdio, std.range, std.math, std.conv, std.bigint,
std.algorithm;
auto benford(R)(R data) {
auto heads = data.filter!q{a != 0}.map!q{ a.text[0] - '1' }.array;
immutable double k = heads.length;
return iota(1, 10)
.zip(heads.sort().group.map!(p => p[1] / k))
.map!q{ [a[]] ~ log10(1.0 + 1.0 / a[0]) };
}
void main() {
auto fibs = recurrence!q{a[n - 1] + a[n - 2]}(1.BigInt, 1.BigInt);
writefln("%9s %9s %9s", "Actual", "Expected", "Deviation");
foreach (p; fibs.take(1000).benford)
writefln("%1.0f: %5.2f%% | %5.2f%% | %5.4f%%",
p[0], p[1] * 100, p[2] * 100, abs(p[2] - p[1]) * 100);
}
Currently the benford() function returns a range of double[]:
.map!q{ [a[]] ~ log10(1.0 + 1.0 / a[0]) };
If I want to return a range of 3-tuples:
.map!q{ tuple(a[], log10(1.0 + 1.0 / a[0])) };
With the proposed syntax the code becomes:
.map!q{ a ~ log10(1.0 + 1.0 / a[0]) };
Or:
.map!q{ a ~ tuple(log10(1.0 + 1.0 / a[0])) };