2

有没有办法使用stringas InputStream

假设我已经从网络下载了文本数据:

string str = to!string(std.net.curl.get("www.someurl.com/data.txt"));

现在我想使用read()-family 函数来解析它以扫描不同的类型。

在纯 C 中有一个sscanf函数。在 C++ 中,我们有std::stringstream. 那么如何在 D 中获得类似的功能呢?

4

1 回答 1

6

我认为两个可能的候选人是std.conv.parsestd.format.formattedRead

parse将允许您通过多次调用将字符串解析为多种类型。在将字符串转换为请求的类型时,它会获取字符串ref并尽可能多地使用字符串。当您想要做的是通过一系列调用使用字符串而不是一次全部转换时,它的效果特别好。例如

import std.array;
import std.conv;
import std.math;
import std.string;

void main()
{
    auto str = "10 12.22 3.14159 22";
    auto a = parse!int(str);
    assert(a == 10);
    assert(str == " 12.22 3.14159 22");

    str = str.stripLeft();
    assert(str == "12.22 3.14159 22");

    auto b = parse!double(str);
    assert(approxEqual(b, 12.22));
    assert(str == " 3.14159 22");

    str = str.stripLeft();
    assert(str == "3.14159 22");

    auto c = parse!long(str);
    assert(c == 3);
    assert(str == ".14159 22");

    str = str.stripLeft();
    assert(str == ".14159 22");

    auto d = parse!float(str);
    assert(approxEqual(d, 0.14159));
    assert(str == " 22");

    str = str.stripLeft();
    assert(str == "22");

    auto e = parse!int(str);
    assert(e == 22);
    assert(str.empty);
}

formattedRead另一方面更接近sscanf。你必须给它一个格式字符串,它会返回它读取了多少元素。与 类似parse,它会在读取字符串时使用它,但它会根据格式字符串进行使用,而不是尝试使用尽可能多的字符串来转换为一个请求类型。sscanf然而,与它不同的是,formattedRead它是类型安全的,并且知道传递给它的变量的类型。因此,您可以使用%s它来转换为给定变量的类型,而不必提供特定于所用变量类型的标志(尽管如果您愿意,您仍然可以使用更具体的标志 - 就像 with 一样writefln)。例如

import std.array;
import std.format;
import std.math;
import std.string;

void main()
{
    auto str = "10 12.22 3.14159 22";
    int a;
    double b;
    long c;
    auto numRead1 = formattedRead(str, "%s %s %s", &a, &b, &c);
    assert(numRead1 == 3);
    assert(a == 10);
    assert(approxEqual(b, 12.22));
    assert(c == 3);
    assert(str == ".14159 22");

    float d;
    int e;
    auto numRead2 = formattedRead(str, "%s %s", &d, &e);
    assert(numRead2 == 2);
    assert(approxEqual(d, 0.14159));
    assert(e == 22);
    assert(str.empty);
}

其他替代方案是简单地利用字符串是范围这一事实,并使用 Phobos 中的各种基于范围的函数以适合您正在做的任何方式使用字符串。例如,如果您知道您的字符串纯粹由由空格分隔的整数组成,您可以通过以下方式将它们int懒惰地转换为 s范围

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

void main()
{
    auto str = "42 22 9 77 46 2 1 0 99";
    auto range = std.array.splitter(str).map!(a => to!int(a))();
    assert(equal(range, [42, 22, 9, 77, 46, 2, 1, 0, 99]));
}

如果你想要一个数组而不是一个惰性范围,你可以简单地调用std.array.array这个范围。

您可以使用各种基于范围的函数(主要的函数在std.rangestd.algorithm中)做很多事情,但是如果您将字符串的内容转换为其他内容,则它们往往会更好地工作是统一的,因为您可以通过这种方式一次转换整个字符串,但是如果您需要以不同方式转换字符串的不同部分,您可以使用和 之类的函数将字符串分开并将其分段转换。您还可以使用在空格上拆分字符串,然后根据字符串在字符串中的位置转换每个部分,但此时,您可能还不如只使用or 。不过,您确实有很多选择。finduntilsplitterparseformattedRead

如果您对范围不是特别熟悉,那么我建议您阅读http://ddili.org/ders/d.en/ranges.html,因为它是我们目前最好的教程。

于 2013-10-20T22:01:43.673 回答