49用d编程区间

把区间与容器/算法结合起来的是模板(非常重要).
stl.拉开序幕.
d的区间,RandomAccessRange,最强大,随机访问,就是类似数组.

import std.stdio;
import std.algorithm;

void main(){
     int [] values = [1,20,7,11];
    writeln(values.filter!(value => value> 10));
//返回的是区间
}
 int[] chosen = values.filter!(value => value > 10); //编译错误,
//不能把FilterResult!(__lambda2, int[])转换为int[]
//但可以转为数组.

传统的:

struct Node {
    int element;
    Node * next;
}

void print(const(Node) * list) {
    for ( ; list; list = list.next) {
        write(' ', list.element);
    }
}

数据结构知道算法如何实现.类似的

void print(const int[] array) {
    for(int i = 0;i != array.length; ++i) {//长度
        write(' ', array[i]);//[]
    }
}

因而传统N种算法,M种数据结构,
数组,链表,关联数组,二叉树,堆等必须编写find(), sort(), swap()等算法.折腾.接近N星M的折腾.但无法对关联数组排序.
区间远离数据结构.只需要实现N种算法,M种数据结构就可以了.区间就是搞个规范.然后你们去实现吧.

    foreach (value; 3..7) {//数字区间
    int[] slice = array[5..10];  

各种各样的区间输入(empty,front,popFront),前向(前+save),双向(back,popBack),随机([],另一个..)区间.
随机还分有限/无限随机访问区间.
还有输出区间.要求put(range, element)操作.

    int[] slice = [ 10, 11, 12 ];

    for (int i = 0; i != slice.length; ++i) {
        write(' ', slice[i]);
    }

    assert(slice.length == 3); 

迭代容器元素,并不改变容器自身.

   for ( ; slice.length; slice = slice[1..$]) {
        write(' ', slice[0]);   // 瞎折腾
    }

另造一个切片来:

   int[] slice = [ 10, 11, 12 ];
    int[] surrogate = slice;

    for ( ; surrogate.length; surrogate = surrogate[1..$]) {
        write(' ', surrogate[0]);
    }

    assert(surrogate.length == 0); //0
    assert(slice.length == 3);     //不变

d标准库也是这样,返回特殊的,原来的切片不变.
输入区间三函数empty,front,popFront(),前两个可当属性用.
popFront()有(),表明改变了.

void print(T)(T range) {
    for ( ; !range.empty; range.popFront()) {
        write(' ', range.front);
    }

    writeln();
}

示例:

import std.string;

struct Student {
    string name;
    int number;

    string toString() const {
        return format("%s(%s)", name, number);
    }
}

struct School {
    Student[] students;
}

void main() {
    auto school = School( [ Student("Ebru", 1),
                            Student("Derya", 2) ,
                            Student("Damla", 3) ] );
}

加三成员:

struct School {
    // ...

    @property bool empty() const {
        return students.length == 0;
    }//判断是否为空,好结束判断
    @property ref Student front(){
         return students [0];
    }//当前元素,返回引用
    void popFront() {
        students = students[1 .. $];
    }//修改元素.这里删元素不合理.
}

只导入std.array,就可使最常见的容器类型匹配最有能力的区间类型:切片可按随机访问区间使用.
std.array可为切片提供empty, front, popFront()和其他函数.因而任意区间函数可使用切片.

import std.array;
// ...
    print([ 1, 2, 3, 4 ]);

如果导入了std.range,就没必要导入std.array了.

void print(T)(T range) {
    for ( ; !range.empty; range.popFront()) {
        write(' ', range.front);
    }//编译错误,定义处

    writeln();
}

void main() {
    int[4] array = [ 1, 2, 3, 4 ];
    print(array);
}

由于切片中的popFront要删元素,因而固定数组不能用作区间(元素不变),

void print(T)(T range)
        if (isInputRange!T) {    //限制模板
    // ...
}
// ...
    print(array);    //报编译错误.调用处

固定数组仍然可以:print(array[]);//编译,复制成区间了.
尽管切片可用作区间,不是所有区间都可用作数组.

import std.array;
// ...
    // 注意: 利用统一调用
    auto copiesOfStudents = school.array;//不注意,就看不出来.
    writeln(copiesOfStudents);

std.array.array帮助从切片转成数组.其迭代输入区间复制为一个新数组.
把串自动解码为dchar的区间.其实这是个坏主意.
当把串用作区间时,自动解码串元素.

void printElements(T)(T str) {
    for (int i = 0; i != str.length; ++i) {
        write(' ', str[i]);
    }

    writeln();
}

// ...

    printElements("abc?deé??"c);
    printElements("abc?deé??"w);
    printElements("abc?deé??"d);

自动解码的值不是实际元素,而是右值.

import std.array;

void main() {
    char[] s = "hello".dup;
    s.front = 'H';//编译错误//右值
}

这样:

import std.array;
import std.string;//represention

void main() {
    char[] s = "hello".dup;
    s.representation.front = 'H';    // 编译
    assert(s == "Hello");
//串被当作`ubyte`数组
}

char, wchar, 和 dchar的表示为ubyte, ushort, 和 uint
切片的一个优势:不拥有元素,每次调用popFront时计算返回元素,front返回当前元素作为值,其为dchar代表一个统一
一些区间没有元素,但能访问其他区间元素,为了保留实际元素,可用特殊区间.

struct School {
    Student[] students;
}

struct StudentRange {
    Student[] students;

    this(School school) {
        this.students = school.students;
    }//这里.则学校不再是区间

    @property bool empty() const {
        return students.length == 0;
    }

    @property ref Student front() {
        return students[0];
    }

    void popFront() {
        students = students[1 .. $];
    }
}
//使用
    auto school = School( [ Student("Ebru", 1),
                            Student("Derya", 2) ,
                            Student("Damla", 3) ] );

    print(StudentRange(school));

    // 原元素,保留了的
    assert(school.students.length == 3);

无限区间enum empty = false;,或static immutable empty = false;

struct FibonacciSeries
{
    int current = 0;
    int next = 1;//但不超过`int.max`

    enum empty = false ;   //无限区间
 
    @property int front()const {
         return current;
    }

    void popFront(){
         const nextNext = current + next;
        current = next;
        next = nextNext;
    }
}

print(FibonacciSeries());不会终止.
当不需要消耗完区间时有用.

StudentRange studentsOf(ref School school) {
    return StudentRange(school);
}
// ...
    print(school.studentsOf);//注意,利用统一调用

感觉又是个间接层(麻烦),//避免显式记区间类型.

import std.range;

// ...
    auto school = School( [ Student("Ebru", 1),
                            Student("Derya", 2) ,
                            Student("Damla", 3) ] );
    print(school.studentsOf.take(2));
//先要从学生们中建一个学校

writeln(typeof(school.studentsOf.take(2)).stringof);感觉很折腾.得到Take!(StudentRange)
std.range and std.algorithm有大量区间,结构,类和算法.

import std.algorithm;

// ...

    auto turkishSchool = School( [ Student("Ebru", 1),
                                   Student("Derya", 2) ,
                                   Student("Damla", 3) ] );

    auto americanSchool = School( [ Student("Mary", 10),
                                    Student("Jane", 20) ] );

    swapFront(turkishSchool.studentsOf,
              americanSchool.studentsOf);
//交换区间头

    print(turkishSchool.studentsOf);
    print(americanSchool.studentsOf);

过滤

 print(school.studentsOf.filter!(a => a.number % 2));
import std.array;

// ...

    bool startsWithD(Student student) {
        return student.name.front == 'D';
    }

    print(school.studentsOf.filter!startsWithD);

其实与c++的元编程没啥区别.(过滤!函数)就是过滤<函数>(),只不过c++比较夸张而已.
注意student.name[0],这是第一个代码单元,不是第一个字母

import std.stdio;
import std.range;
import std.random;

void main() {
    auto diceThrower = generate!(() => uniform(0, 6));
    writeln(diceThrower.take(10));
}

std.rangegenerate可调用实体(函数,指针,λ)生成区间.无穷区间,函数返回值作为一个个的元素.
,不到使用时不干活.
前向区间提供save函数.返回区间副本.

struct FibonacciSeries {
 // ...
    @property FibonacciSeries save()const {
         return  this ;
    }//副本,不是引用.
}
import std.range;

// ...

void report(T)(const dchar[] title, const ref T range) {
    writefln("%40s: %s", title, range.take(5));
}

void main() {
    auto range = FibonacciSeries();
    report("Original range", range);

    range.popFrontN(2);
    report("After removing two elements", range);

    auto theCopy = range.save;
    report("The copy", theCopy);

    range.popFrontN(3);
    report("After removing three more elements", range);
    report("The copy", theCopy);
}

区间是挨个挨个的.
stdio也可带InputRange对象.
writeln(FibonacciSeries().take(5).cycle.take(20));
[0, 1, 1, 2, 3, 0, 1, 1, 2, 3, 0, 1, 1, 2, 3, 0, 1, 1, 2, 3]

    auto series                   = FibonacciSeries();
    auto firstPart                = series.take(5);
    auto cycledThrough            = firstPart.cycle;
    auto firstPartOfCycledThrough = cycledThrough.take(20);
//上面为区间
    writeln(firstPartOfCycledThrough);

哪有这么麻烦?
双向区间:

import std.array;
import std.stdio;

struct Reversed {
    int[] range;

    this(int[] range) {
        this.range = range;
    }

    @property bool empty() const {
        return range.empty;
    }

    @property int front() const {
        return range.back;  // 逆向
    }

    @property int back() const {
        return range.front; // 逆向
    }

    void popFront() {
        range.popBack();    // 逆向
    }

    void popBack() {
        range.popFront();   // 逆向
    }
}

void main() {
    writeln(Reversed([ 1, 2, 3]));
}

随机访问[].
char,wchar不能随机访问,
无穷随机编译时empty==false,
有穷,定义了长度属性

class SquaresRange {
    int first;

    this(int first = 0) {
        this.first = first;
    }

    enum empty = false;

    @property int front() const {
        return opIndex(0);
    }

    void popFront() {
        ++first;
    }

    @property SquaresRange save() const {
        return new SquaresRange(first);
    }

    int opIndex(size_t index) const {
         /* 常数时间*/
        immutable integerValue = first + cast(int)index;
        return integerValue * integerValue;
    }
}
    squares.popFrontN(5);//弹N
    writeln(squares[0]);//头,
//=-=========
 bool are_lastTwoDigitsSame(int value) {
        if (value < 10) {//至少
            return false;
        }

        immutable lastTwoDigits = value % 100;
        return (lastTwoDigits % 11) == 0;
    }//可整除11.

    writeln(squares.take(50).filter!are_lastTwoDigitsSame);

示例:

    auto range = Together([ 1, 2, 3 ], [ 101, 102, 103]);
    writeln(range[4]);

元素不直接复制,只是从原区间访问.

struct Together {
    const(int)[][] slices;

    this(const(int)[][] slices...) {//
        this.slices = slices.dup;

        clearFront();
        clearBack();
    }//去头尾空切片
    private  void clearFront(){
         while(!slices.empty && slices.front.empty){
            slices.popFront();
        }
    }

    private  void clearBack(){
         while(!slices.empty && slices.back.empty){
            slices.popBack();
        }
    }
    @property bool empty() const {
        return slices.empty;
    }
    @property int front() const {
        return slices.front.front;
    }
    void popFront(){
        slices.front.popFront();
        clearFront();//每一次都要校验,
    }
    @property Together save()const {
         return Together(slices.dup);
    }//仍然是区间,不是实体.
    @property int back()const {
         return slices.back.back;
    }

    void popBack(){
        slices.back.popBack();
        clearBack();
    }//尾类似
    @property size_t length()const {
        size_t totalLength = 0;

        foreach(slice;slices){
            totalLength + = slice.length;
        }

        return totalLength;
    }//上下两个一样的.
    @property size_t length()const {
         return slices.fold!((a,b)=> a + b.length)(size_t.init);
    }
    int opIndex(size_t index) const {
        /* Save the index for the error message */
        immutable originalIndex = index;

        foreach (slice; slices) {
            if (slice.length > index) {
                return slice[index];

            } else {
                index -= slice.length;
            }
        }

        throw new Exception(
            format("Invalid index: %s (length: %s)",originalIndex, this.length));
    }//太麻烦了.不能`常数时间`访问
}

其实切片是搞复杂了.每一次都要校验,能不复杂吗?
看得出来,搞复杂了.

    auto range = Together(FibonacciSeries().take(10).array,
                          [ 777, 888 ],
                          (new SquaresRange()).take(5).array);

    writeln(range.save);

可用,但太长,太麻烦,不推荐.
输出区间

情况 方法
R有成员函数叫put,并取E作参数 range.put(e);
R有成员函数叫put,并取E[]作参数 range.put([ e ]);
R是输入区间,e可赋值给range.front range.front = e;range.popFront();
E是输入区间,可复制给R for(;!e.empty;e.popFront())put(range, e.front);
R有E作参数(如R可能是闭包) range(e);
R有E[]作参数(如R可能是闭包) range([e]);

其中R为区间类型,E为元素类型,range为区间对象,e为区间元素

struct MultiFile {
    string delimiter;
    File[] files;

    this(string delimiter, string[] fileNames...) {
        this.delimiter = delimiter;

        /* 总是包含stdout*/
        this.files ~= stdout;

        /* 每个文件名的文件对象*/
        foreach (fileName; fileNames) {
            this.files ~= File(fileName, "w");
        }
    }

    // 取数组的版本,但不是串
    void put(T)(T slice)
            if (isArray!T && !isSomeString!T) {
        foreach (element; slice) {
            put(element);//调用下面其他版本的put
        }
    }

    // 取非数组和是串的版本
    void put(T)(T value)
            if (!isArray!T || isSomeString!T) {
        foreach (file; files) {
            file.write(value, delimiter);
        }
    }
}

接着

import std.traits;
import std.stdio;
import std.algorithm;

// ...

void main() {
    auto output = MultiFile("\n", "output_0", "output_1");
    copy([ 1, 2, 3], output);
    copy([ "red", "blue", "green" ], output);
}

标准库中std.algorithm.copy使用输出区间

import std.stdio;
import std.range;

void main() {
    int[] slice = [ 1, 2, 3 ];
    int[] slice2 = slice;

    put(slice2, 100);

    writeln(slice2);//只2个元素,丢了一个
    writeln(slice);
}

再:

    int[] slice = [ 1, 2, 3 ];
    int[] slice2 = slice;

    foreach (i; 0 .. 4) {    
        put(slice2, i * 100);
    }

由程序员保证空间足够,输出区间也是一个折腾.

import std.array;

// ...

    auto a = appender([ 1, 2, 3 ]);

    foreach (i; 0 .. 4) {
        a.put(i * 100);
    }

补救措施.
还可以

    a ~= 1000;writeln(a.data);

c++的一个向量,都搞定了.还有区间模板.

发布了440 篇原创文章 · 获赞 29 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/fqbqrr/article/details/104590104