Thread overview
Output range of ranges to single buffer
Jan 13, 2016
Jacob Carlborg
Jan 13, 2016
H. S. Teoh
Jan 13, 2016
Ali Çehreli
Jan 14, 2016
Jacob Carlborg
Jan 14, 2016
Ali Çehreli
Jan 14, 2016
Jacob Carlborg
Jan 14, 2016
Jonathan M Davis
Jan 13, 2016
Dav1d
January 13, 2016
Is it possible to somehow output a range of ranges to a single string buffer? For example, converting an array of integers to a string with the same representation as the source code.

import std.algorithm;
import std.conv;
import std.string;

void main()
{
    auto a = [1, 2, 3, 4, 5];
    auto b = '[' ~ a.map!(e => e.to!string).join(", ") ~ ']';
    assert(b == "[1, 2, 3, 4, 5]");
}

The above code is straight forward. But I would like to avoid creating the intermediate strings, "e.to!string", and instead put the converted integer and the result of join directly in the same buffer.

A bit more complex example:

struct Foo
{
    int a;
    string b;
    int c;

    string toString()
    {
        auto values = [a.to!string, b, c.to!string].join(", ");
        return "Foo(" ~ values ~ ')';
    }
}

void main()
{
    auto a = [
        Foo(1, "foo", 2),
        Foo(3, "bar", 4)
    ];

    auto b = '[' ~ a.map!(e => e.to!string).join(", ") ~ ']';
    assert(b == "[Foo(1, foo, 2), Foo(3, bar, 4)]");
}

-- 
/Jacob Carlborg
January 13, 2016
On Wed, Jan 13, 2016 at 10:15:03PM +0100, Jacob Carlborg via Digitalmars-d-learn wrote:
> Is it possible to somehow output a range of ranges to a single string buffer? For example, converting an array of integers to a string with the same representation as the source code.
> 
> import std.algorithm;
> import std.conv;
> import std.string;
> 
> void main()
> {
>     auto a = [1, 2, 3, 4, 5];
>     auto b = '[' ~ a.map!(e => e.to!string).join(", ") ~ ']';
>     assert(b == "[1, 2, 3, 4, 5]");
> }
> 
> The above code is straight forward. But I would like to avoid creating the intermediate strings, "e.to!string", and instead put the converted integer and the result of join directly in the same buffer.

Isn't that just a matter of replacing each of the segments with their range equivalents? Also, std.format.formattedWrite will do writeln-formatting into a buffer (well, any output range, really) -- I'm pretty sure it doesn't allocate, at least for the simplest cases like converting an integer. So you should be able to do something like this:

	auto data = [ 1, 2, 3, 4, 5 ];
	char[] buf = ...;
	formattedWrite(buf, "[%(%d, %)]", data);


T

-- 
Customer support: the art of getting your clients to pay for your own incompetence.
January 13, 2016
On Wednesday, 13 January 2016 at 21:15:03 UTC, Jacob Carlborg wrote:
> Is it possible to somehow output a range of ranges to a single string buffer? For example, converting an array of integers to a string with the same representation as the source code.
>
> [...]

std.format can do it. From the site:


import std.stdio;

void main()
{
    writefln("My items are %(%s %).", [1,2,3]);
    writefln("My items are %(%s, %).", [1,2,3]);
}
January 13, 2016
On 01/13/2016 01:20 PM, H. S. Teoh via Digitalmars-d-learn wrote:

> std.format.formattedWrite will do
> writeln-formatting into a buffer (well, any output range, really) -- I'm
> pretty sure it doesn't allocate, at least for the simplest cases like
> converting an integer. So you should be able to do something like this:
>
> 	auto data = [ 1, 2, 3, 4, 5 ];
> 	char[] buf = ...;
> 	formattedWrite(buf, "[%(%d, %)]", data);

And buf can be an Appender:

import std.stdio;
import std.format;
import std.array;

void main() {
    auto data = [ 1, 2, 3, 4, 5 ];

    auto buf = appender!string(); // Or appender!(char[]) if needed
    formattedWrite(buf, "[%(%d, %)]", data);

    assert(buf.data == "[1, 2, 3, 4, 5]");
}

Ali

January 14, 2016
On 2016-01-13 22:20, H. S. Teoh via Digitalmars-d-learn wrote:

> Isn't that just a matter of replacing each of the segments with their
> range equivalents? Also, std.format.formattedWrite will do
> writeln-formatting into a buffer (well, any output range, really) -- I'm
> pretty sure it doesn't allocate, at least for the simplest cases like
> converting an integer. So you should be able to do something like this:
>
> 	auto data = [ 1, 2, 3, 4, 5 ];
> 	char[] buf = ...;
> 	formattedWrite(buf, "[%(%d, %)]", data);

Aha, interesting. I didn't know formattedWrite could format an array/range directly like that.

The more complex example works by defining "toString" which takes an output range (delegate). But what if I need to format a third party type that I cannot add methods to? UFCS does not seem to work.

-- 
/Jacob Carlborg
January 14, 2016
On 01/13/2016 11:41 PM, Jacob Carlborg wrote:

> what if I need to format a third party type that I cannot add
> methods to? UFCS does not seem to work.

Here is an experiment that wraps the third party type to provide a lazy toString:

import std.stdio;
import std.format;
import std.array;
import std.algorithm;
import std.range;

/* Wraps an element and provides a lazy toString that dispatches the work to a
 * user-provided 'formatter' function. E is the element type. */
struct Formatted(alias formatter, E) {
    E element;

    void toString(void delegate(const(char)[]) sink) const {
        formatter(sink, element);
    }
}

/* Adapts a range by converting the elements to 'Formatted'. R is the range
 * type. */
auto formatted(alias formatter, R)(R range) {
    return range.map!(e => Formatted!(formatter, ElementType!R)(e));
}

/* A third party test type that does not have a lazy toString member
 * function. */
struct Foo {
    double d;
    string s;
}

void main() {
	auto data = [ Foo(1.5, "hello"), Foo(2.5, "world") ];

    auto buf = appender!string();
	formattedWrite(buf, "%(%s\n%)",
                   data.formatted!(
                       (sink, a) => formattedWrite(sink, "%s and %s",
                                                   a.d, a.s)));

    writeln(buf.data);
}

Prints the objects according to the user's lambda:

1.5 and hello
2.5 and world

It would be great if the user could provide just the format and accessed the members:

    data.formatted!("%s and %s", a.d, a.s); // <-- ERROR

But I couldn't get it working because the compiler does not know what 'a' is at that point. It might be acceptable to provide a lambda per member but then it gets to cluttered:

    data.formatted!("%s and %s", a => a.d, a => a.s); // Might work

Ali

January 14, 2016
On Thursday, January 14, 2016 08:41:23 Jacob Carlborg via Digitalmars-d-learn wrote:
> On 2016-01-13 22:20, H. S. Teoh via Digitalmars-d-learn wrote:
>
> > Isn't that just a matter of replacing each of the segments with their range equivalents? Also, std.format.formattedWrite will do writeln-formatting into a buffer (well, any output range, really) -- I'm pretty sure it doesn't allocate, at least for the simplest cases like converting an integer. So you should be able to do something like this:
> >
> >     auto data = [ 1, 2, 3, 4, 5 ];
> >     char[] buf = ...;
> >     formattedWrite(buf, "[%(%d, %)]", data);
>
> Aha, interesting. I didn't know formattedWrite could format an array/range directly like that.
>
> The more complex example works by defining "toString" which takes an output range (delegate). But what if I need to format a third party type that I cannot add methods to? UFCS does not seem to work.

You can't do toString via UFCS any more than you can overload any operators via UFCS. If a type's toString does not work how you want, or a type does not provide one, then you'll need to convert objects of that type to a string in a different way.

- Jonathan M Davis

January 14, 2016
On 2016-01-14 17:59, Ali Çehreli wrote:

> Here is an experiment that wraps the third party type to provide a lazy
> toString:
>
> import std.stdio;
> import std.format;
> import std.array;
> import std.algorithm;
> import std.range;
>
> /* Wraps an element and provides a lazy toString that dispatches the
> work to a
>   * user-provided 'formatter' function. E is the element type. */
> struct Formatted(alias formatter, E) {
>      E element;
>
>      void toString(void delegate(const(char)[]) sink) const {
>          formatter(sink, element);
>      }
> }

Wrap the object, why didn't I think of that :)

> /* Adapts a range by converting the elements to 'Formatted'. R is the range
>   * type. */
> auto formatted(alias formatter, R)(R range) {
>      return range.map!(e => Formatted!(formatter, ElementType!R)(e));
> }
>
> /* A third party test type that does not have a lazy toString member
>   * function. */
> struct Foo {
>      double d;
>      string s;
> }
>
> void main() {
>      auto data = [ Foo(1.5, "hello"), Foo(2.5, "world") ];
>
>      auto buf = appender!string();
>      formattedWrite(buf, "%(%s\n%)",
>                     data.formatted!(
>                         (sink, a) => formattedWrite(sink, "%s and %s",
>                                                     a.d, a.s)));
>
>      writeln(buf.data);
> }
>
> Prints the objects according to the user's lambda:
>
> 1.5 and hello
> 2.5 and world

Thanks.

-- 
/Jacob Carlborg