1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * Finds differences between two ranges.
8  *
9  * All changes are discovered in O(max(n, m)) where n and m are the length of
10  * the two ranges.
11  */
12 module util.change;
13 
14 import std.range : isInputRange, ElementType;
15 
16 /**
17  * Type of a change.
18  */
19 enum ChangeType
20 {
21     none,
22     added,
23     removed
24 }
25 
26 /**
27  * Describes a change.
28  */
29 struct Change(T)
30 {
31     T value;
32     ChangeType type;
33 }
34 
35 /**
36  * Range for iterating over changes between two sorted ranges.
37  */
38 struct Changes(R1, R2, alias pred = "a < b")
39     if (isInputRange!R1 && isInputRange!R2 &&
40         is(ElementType!R1 == ElementType!R2))
41 {
42     import std.range : ElementType;
43     import std.traits : Unqual;
44     import std.typecons : Rebindable;
45 
46     private alias E = ElementType!R1;
47 
48     static if ((is(E == class) || is(E == interface)) &&
49                (is(E == const) || is(E == immutable)))
50     {
51         private alias MutableE = Rebindable!E;
52     }
53     else static if (is(E : Unqual!E))
54     {
55         private alias MutableE = Unqual!E;
56     }
57     else
58     {
59         private alias MutableE = E;
60     }
61 
62     private
63     {
64         // Current change.
65         Change!MutableE _current;
66 
67         // Next and previous states.
68         R1 prev;
69         R2 next;
70 
71         bool _empty;
72     }
73 
74     this(R1 prev, R2 next)
75     {
76         this.prev = prev;
77         this.next = next;
78 
79         popFront();
80     }
81 
82     void popFront()
83     {
84         import std.range : empty, front, popFront;
85         import std.functional : binaryFun;
86 
87         if (prev.empty && next.empty)
88         {
89             _empty = true;
90         }
91         else if (prev.empty)
92         {
93             _current = Change!E(next.front, ChangeType.added);
94             next.popFront();
95         }
96         else if (next.empty)
97         {
98             _current = Change!E(prev.front, ChangeType.removed);
99             prev.popFront();
100         }
101         else
102         {
103             auto a = prev.front;
104             auto b = next.front;
105 
106             if (binaryFun!pred(a, b))
107             {
108                 // Removed
109                 _current = Change!E(a, ChangeType.removed);
110                 prev.popFront();
111             }
112             else if (binaryFun!pred(b, a))
113             {
114                 // Added
115                 _current = Change!E(b, ChangeType.added);
116                 next.popFront();
117             }
118             else
119             {
120                 // No change
121                 _current = Change!E(a, ChangeType.none);
122                 prev.popFront();
123                 next.popFront();
124             }
125         }
126     }
127 
128     @property auto ref front() pure nothrow
129     {
130         return _current;
131     }
132 
133     bool empty() const pure nothrow
134     {
135         return _empty;
136     }
137 }
138 
139 /**
140  * Convenience function for constructing a range that finds changes between two
141  * ranges.
142  */
143 auto changes(alias pred = "a < b", R1, R2)(R1 previous, R2 next)
144 {
145     return Changes!(R1, R2, pred)(previous, next);
146 }
147 
148 unittest
149 {
150     import std.algorithm : equal;
151 
152     immutable prev = "abcd";
153     immutable next = "acdef";
154 
155     immutable Change!dchar[] result = [
156             {'a', ChangeType.none},
157             {'b', ChangeType.removed},
158             {'c', ChangeType.none},
159             {'d', ChangeType.none},
160             {'e', ChangeType.added},
161             {'f', ChangeType.added},
162         ];
163 
164     assert(result.equal(changes(prev, next)));
165 }