www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Output range of ranges to single buffer

reply Jacob Carlborg <doob me.com> writes:
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
Jan 13
next sibling parent Dav1d <d dav1d.de> writes:
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]); }
Jan 13
prev sibling parent reply "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
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.
Jan 13
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 13
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
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
Jan 13
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Jan 14
parent Jacob Carlborg <doob me.com> writes:
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
Jan 14
prev sibling parent Jonathan M Davis via Digitalmars-d-learn writes:
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
Jan 14