Thread overview
[Issue 4591] New: Concat of std.typecons.Tuples
August 06, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4591

           Summary: Concat of std.typecons.Tuples
           Product: D
           Version: D2
          Platform: All
        OS/Version: All
            Status: NEW
          Keywords: patch
          Severity: enhancement
          Priority: P2
         Component: Phobos
        AssignedTo: nobody@puremagic.com
        ReportedBy: bearophile_hugs@eml.cc


--- Comment #0 from bearophile_hugs@eml.cc 2010-08-06 09:31:51 PDT ---
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.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
October 07, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4591


Andrei Alexandrescu <andrei@metalanguage.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |ASSIGNED
                 CC|                            |andrei@metalanguage.com
         AssignedTo|nobody@puremagic.com        |andrei@metalanguage.com


-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
July 25, 2011
http://d.puremagic.com/issues/show_bug.cgi?id=4591



--- Comment #1 from bearophile_hugs@eml.cc 2011-07-25 05:30:07 PDT ---
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
}

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
May 14, 2013
http://d.puremagic.com/issues/show_bug.cgi?id=4591


bearophile_hugs@eml.cc changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
         AssignedTo|andrei@erdani.com           |nobody@puremagic.com


--- Comment #2 from bearophile_hugs@eml.cc 2013-05-14 09:49:37 PDT ---
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])) };

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------