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 }