@@ -97,7 +97,30 @@ class Solution implements Comparable<Solution> {
97
97
/// Creates a new [Solution] with no pieces set to any state (which
98
98
/// implicitly means they have state [State.unsplit] unless they're pinned to
99
99
/// another state).
100
- Solution (Piece root, int pageWidth) : this ._(root, pageWidth, 0 , {});
100
+ factory Solution (Piece root, int pageWidth) {
101
+ var pieceStates = < Piece , State > {};
102
+ var cost = 0 ;
103
+
104
+ // Bind every pinned piece to its state and propagate any constraints from
105
+ // those.
106
+ void traversePinned (Piece piece) {
107
+ if (piece.pinnedState case var pinned? ) {
108
+ var additionalCost = _tryBind (pieceStates, piece, pinned);
109
+
110
+ // Pieces should be implemented such that they never get pinned into a
111
+ // conflicting state because then there's no possible solution, so
112
+ // [_tryBind()] should always succeed and [additionalCost] won't be
113
+ // `null`.
114
+ cost += additionalCost! ;
115
+ }
116
+
117
+ piece.forEachChild (traversePinned);
118
+ }
119
+
120
+ traversePinned (root);
121
+
122
+ return Solution ._(root, pageWidth, cost, pieceStates);
123
+ }
101
124
102
125
Solution ._(Piece root, int pageWidth, this .cost, this ._pieceStates) {
103
126
var writer = CodeWriter (pageWidth, this );
@@ -111,7 +134,7 @@ class Solution implements Comparable<Solution> {
111
134
/// The state this solution selects for [piece] .
112
135
///
113
136
/// If no state has been selected, defaults to the first state.
114
- State pieceState (Piece piece) => _pieceStates[piece] ?? piece.states.first ;
137
+ State pieceState (Piece piece) => _pieceStates[piece] ?? State .unsplit ;
115
138
116
139
/// Whether [piece] has been bound to a state in this set.
117
140
bool isBound (Piece piece) => _pieceStates.containsKey (piece);
@@ -147,8 +170,11 @@ class Solution implements Comparable<Solution> {
147
170
_invalidPiece = piece;
148
171
}
149
172
150
- /// When called on a [Solution] with some unselected piece states, chooses a
151
- /// piece and yields further solutions for each state that piece can have.
173
+ /// Derives new potential solutions from this one by binding
174
+ /// [_nextPieceToExpand] to all of its possible states.
175
+ ///
176
+ /// If there is no potential piece to expand, or all attempts to expand it
177
+ /// fail, returns an empty list.
152
178
List <Solution > expand (Piece root, int pageWidth) {
153
179
// If the piece whose newline constraint was violated is already bound to
154
180
// one state, then every solution derived from this one will also fail in
@@ -176,51 +202,25 @@ class Solution implements Comparable<Solution> {
176
202
// make any noticeable performance difference on the one pathological
177
203
// example I tried. Leaving this here as a TODO to investigate more when
178
204
// there are other benchmarks we can try.
205
+ var solutions = < Solution > [];
179
206
180
- return [
181
- for (var state in expandPiece.states)
182
- if (_tryBind (root, pageWidth, expandPiece, state) case var solution? )
183
- solution
184
- ];
185
- }
186
-
187
- /// Attempts to extend this solution's piece states by binding [piece] to
188
- /// [state] , taking into account any constraints pieces place on each other.
189
- ///
190
- /// Returns a new [Solution] with [piece] bound to [state] and any other
191
- /// pieces constrained by that choice bound to their constrained values
192
- /// (recursively). Returns `null` if a constraint conflicts with the already
193
- /// bound or pinned state for some piece.
194
- Solution ? _tryBind (Piece root, int pageWidth, Piece piece, State state) {
195
- var conflict = false ;
196
- var newStates = {..._pieceStates};
197
- var newCost = cost;
207
+ // For each state that the expanding piece can be in, create a new solution
208
+ // that inherits all of the bindings of this one, and binds the expanding
209
+ // piece to that state (along with any further pieces constrained by that
210
+ // one).
211
+ for (var state in expandPiece.states) {
212
+ var newStates = {..._pieceStates};
198
213
199
- void bind (Piece thisPiece, State thisState) {
200
- // If we've already failed from a previous sibling's constraint violation,
201
- // early out.
202
- if (conflict) return ;
203
-
204
- // If this piece is already pinned or bound to some other state, then the
205
- // solution doesn't make sense.
206
- var alreadyBound = thisPiece.pinnedState ?? newStates[thisPiece];
207
- if (alreadyBound != null && alreadyBound != thisState) {
208
- conflict = true ;
209
- return ;
210
- }
214
+ var additionalCost = _tryBind (newStates, expandPiece, state);
211
215
212
- newStates[thisPiece] = thisState;
213
- newCost += thisPiece. stateCost (thisState) ;
216
+ // Discard the solution if we hit a constraint violation.
217
+ if (additionalCost == null ) continue ;
214
218
215
- // This piece may in turn place further constraints on others.
216
- thisPiece. applyConstraints (thisState, bind );
219
+ solutions
220
+ . add ( Solution ._(root, pageWidth, cost + additionalCost, newStates) );
217
221
}
218
222
219
- bind (piece, state);
220
-
221
- if (conflict) return null ;
222
-
223
- return Solution ._(root, pageWidth, newCost, newStates);
223
+ return solutions;
224
224
}
225
225
226
226
/// Compares two solutions where a more desirable solution comes first.
@@ -260,4 +260,46 @@ class Solution implements Comparable<Solution> {
260
260
states,
261
261
].join (' ' );
262
262
}
263
+
264
+ /// Attempts to add a binding from [piece] to [state] in [boundStates] , and
265
+ /// then adds any further bindings from constraints that [piece] applies to
266
+ /// its children, recursively.
267
+ ///
268
+ /// This may fail if [piece] is already bound to a different [state] , or if
269
+ /// any constrained pieces are bound to different states.
270
+ ///
271
+ /// If successful, returns the additional cost required to bind [piece] to
272
+ /// [state] (along with any other applied constrained pieces). Otherwise,
273
+ /// returns `null` to indicate failure.
274
+ static int ? _tryBind (
275
+ Map <Piece , State > boundStates, Piece piece, State state) {
276
+ var success = true ;
277
+ var additionalCost = 0 ;
278
+
279
+ void bind (Piece thisPiece, State thisState) {
280
+ // If we've already failed from a previous sibling's constraint violation,
281
+ // early out.
282
+ if (! success) return ;
283
+
284
+ // Apply the new binding if it doesn't conflict with an existing one.
285
+ switch (boundStates[thisPiece]) {
286
+ case null :
287
+ // Binding a unbound piece to a state.
288
+ additionalCost += thisPiece.stateCost (thisState);
289
+ boundStates[thisPiece] = thisState;
290
+
291
+ // This piece may in turn place further constraints on others.
292
+ thisPiece.applyConstraints (thisState, bind);
293
+ case var alreadyBound when alreadyBound != thisState:
294
+ // Already bound to a different state, so there's a conflict.
295
+ success = false ;
296
+ default :
297
+ break ; // Already bound to the same state, so nothing to do.
298
+ }
299
+ }
300
+
301
+ bind (piece, state);
302
+
303
+ return success ? additionalCost : null ;
304
+ }
263
305
}
0 commit comments