001 package ttsolver.constraint;
002
003 import ifs.model.*;
004 import ttsolver.model.*;
005 import ifs.util.*;
006 import java.util.*;
007 import edu.purdue.smas.timetable.util.Constants;
008
009 /**
010 * Departmental ballancing constraint.
011 * <br><br>
012 * The new implementation of the balancing times for departments works as follows: Initially, there is a histogram for
013 * each department computed. For each time slot, it says how many placements of all classes (of the department) include
014 * this time slot. Each such placement has the weight of 1 / number of placements of the class, so the total sum of all
015 * values in the histogram (i.e., for all time slots) is equal to the total sum of half-hours required by the given set
016 * of classes.
017 * <br><br>
018 * On the other hand, each class splits its number of half-hours (e.g., 2x50 has 4 half-hours, "4 points") into the
019 * time slots which it can occupy, according to the frequencies of the utilization of each time slots (i.e., number of
020 * placements containing the time slots divided by the number of all placements of the class).
021 * <br><br>
022 * For example, a histogram for department 1286:<code><br>
023 * 1: [0.10,0.00,0.10,0.00,0.10] <- 7:30 [Mon, Tue, Wed, Thu, Fri]<br>
024 * 2: [0.10,0.00,0.10,0.00,0.10] <- 8:00 [Mon, Tue, Wed, Thu, Fri]<br>
025 * 3: [0.35,0.62,0.48,0.62,0.10] ... and so on<br>
026 * 4: [0.35,0.62,0.48,0.62,0.10]<br>
027 * 5: [1.35,1.12,1.48,0.12,1.10]<br>
028 * 6: [1.35,1.12,1.48,0.12,1.10]<br>
029 * 7: [0.35,0.62,0.48,1.63,0.10]<br>
030 * 8: [0.35,0.62,0.48,1.63,0.10]<br>
031 * 9: [0.35,0.12,0.48,0.12,0.10]<br>
032 * 10:[0.35,0.12,0.48,0.12,0.10]<br>
033 * 11:[0.35,0.12,0.48,0.12,0.10]<br>
034 * 12:[0.35,0.12,0.48,0.12,0.10]<br>
035 * 13:[0.35,0.12,0.48,1.12,0.10]<br>
036 * 14:[0.35,0.12,0.48,1.12,0.10]<br>
037 * 15:[0.35,0.12,0.48,0.12,0.10]<br>
038 * 16:[0.35,0.12,0.48,0.12,0.10]<br>
039 * 17:[0.35,0.12,0.48,0.12,0.10]<br>
040 * 18:[0.35,0.12,0.48,0.12,0.10]<br>
041 * 19:[0.10,0.00,0.10,0.00,0.10]<br>
042 * 20:[0.10,0.00,0.10,0.00,0.10]<br>
043 * 21:[0.00,0.00,0.00,0.00,0.00]<br>
044 * </code><br>
045 * You can easily see, that the time slots which are prohibited for all of the classes of the department have zero
046 * values, also some time slots are used much often than the others. Note that there are no preferences involved in
047 * this computation, only prohibited / not used times are less involved.
048 * <br><br>
049 * The idea is to setup the initial limits for each of the time slots according to the above values. The reason for doing
050 * so is to take the requirements (time patterns, required/prohibited times) of all classes of the department into
051 * account. For instance, take two classes A and B of type MWF 2x100 with two available locations starting from 7:30
052 * and 8:30. Note that the time slot Monday 8:30-9:00 will be always used by both of the classes and for instance the
053 * time slot Monday 7:30-8:00 (or Friday 9:30-10:00) can be used by none of them, only one of them or both of them.
054 * From the balancing point of the view, I believe it should be preferred to have one class starting from 7:30 and the
055 * other one from 8:30.
056 * <br><br>
057 * So, after the histogram is computed, its values are increased by the given percentage (same reason as before, to
058 * allow some space between the actual value and the limit, also not to make the limits for a department with 21 time
059 * slots less strict than a department with 20 time slots etc.). The initial limits are than computed as these values
060 * rounded upwards (1.90 results in 2).
061 * <br><br>
062 * Moreover, the value is increased only when the histogram value of a time slot is below the following value:
063 * spread factor * (number of used time slots / number of all time slots). Is assures a department of at least the
064 * given percentage more time slots than required, but does not provide an additional reward for above average
065 * use of time slots based on 'required' times.
066 * <br><br>
067 * For example, the department 1286 will have the following limits (histogram increased by 20% (i.e., each value is 20%
068 * higher) and rounded upwards):
069 * <code><br>
070 * 1: [1,0,1,0,1]<br>
071 * 2: [1,0,1,0,1]<br>
072 * 3: [1,1,1,1,1]<br>
073 * 4: [1,1,1,1,1]<br>
074 * 5: [2,2,2,1,2]<br>
075 * 6: [2,2,2,1,2]<br>
076 * 7: [1,1,1,2,1]<br>
077 * 8: [1,1,1,2,1]<br>
078 * 9: [1,1,1,1,1]<br>
079 * 10:[1,1,1,1,1]<br>
080 * 11:[1,1,1,1,1]<br>
081 * 12:[1,1,1,1,1]<br>
082 * 13:[1,1,1,2,1]<br>
083 * 14:[1,1,1,2,1]<br>
084 * 15:[1,1,1,1,1]<br>
085 * 16:[1,1,1,1,1]<br>
086 * 17:[1,1,1,1,1]<br>
087 * 18:[1,1,1,1,1]<br>
088 * 19:[1,0,1,0,1]<br>
089 * 20:[1,0,1,0,1]<br>
090 * 21:[0,0,0,0,0]<br>
091 * </code><br>
092 * The maximal penalty (i.e., the maximal number of half-hours which can be used above the pre-computed limits by a
093 * department) of a constraint is used. Initially, the maximal penalty is set to zero. It is increased by one after
094 * each time when the constraint causes the given number (e.g., 100) of un-assignments.
095 * <br><br>
096 * Also, the balancing penalty (the total number of half-hours over the initial limits) is computed and it can be
097 * minimized during the search (soft constraint).
098 * <br><br>
099 * Parameters:
100 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
101 * <tr><td>DepartmentSpread.SpreadFactor</td><td>{@link Double}</td><td>Initial allowance of the slots for a particular time (factor)<br>Allowed slots = ROUND(SpreadFactor * (number of requested slots / number of slots per day))</td></tr>
102 * <tr><td>DepartmentSpread.Unassignments2Weaken</td><td>{@link Integer}</td><td>Increase the initial allowance when it causes the given number of unassignments</td></tr>
103 * </table>
104 *
105 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
106 * @version 1.0
107 */
108
109 public class DepartmentSpreadConstraint extends Constraint {
110 private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(DepartmentSpreadConstraint.class);
111 private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",new java.text.DecimalFormatSymbols(Locale.US));
112 private static java.text.DecimalFormat sIntFormat = new java.text.DecimalFormat("0",new java.text.DecimalFormatSymbols(Locale.US));
113 private int iMaxCourses[][] = null;
114 private int iCurrentPenalty = 0;
115 private int iMaxAllowedPenalty = 0;
116
117 private String iDepartment = null;
118 private boolean iInitialized = false;
119 private int[][] iNrCourses = null;
120 private Vector [][] iCourses = null;
121 private double iSpreadFactor = 1.20;
122 private int iUnassignmentsToWeaken = 250;
123 private long iUnassignment = 0;
124
125 public DepartmentSpreadConstraint(DataProperties config, String department) {
126 iDepartment = department;
127 iSpreadFactor = config.getPropertyDouble("DepartmentSpread.SpreadFactor",iSpreadFactor);
128 iUnassignmentsToWeaken = config.getPropertyInt("DepartmentSpread.Unassignments2Weaken", iUnassignmentsToWeaken);
129 iNrCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
130 iCourses = new Vector[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
131 for (int i=0;i<iNrCourses.length;i++) {
132 for (int j=0;j<Constants.DAY_CODES.length;j++) {
133 iNrCourses[i][j] = 0;
134 iCourses[i][j]=new Vector(10,5);
135 }
136 }
137 }
138
139 /** Initialize constraint (to be called after all variables are added to this constraint) */
140 public void init() {
141 if (iInitialized) return;
142 double histogramPerDay[][] = new double[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
143 iMaxCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
144 for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++)
145 for (int j=0;j<Constants.DAY_CODES.length;j++)
146 histogramPerDay[i][j]=0.0;
147 int totalUsedSlots = 0;
148 for (Enumeration e=variables().elements();e.hasMoreElements();) {
149 Lecture lecture = (Lecture)e.nextElement();
150 Placement firstPlacement = (Placement)lecture.values().firstElement();
151 if (firstPlacement!=null) {
152 totalUsedSlots += firstPlacement.getTimeLocation().getNrHalfHoursPerMeeting()*firstPlacement.getTimeLocation().getNrMeetings();
153 }
154 for (Enumeration e2=lecture.values().elements();e2.hasMoreElements();) {
155 Placement p = (Placement)e2.nextElement();
156 int firstSlot = p.getTimeLocation().getStartSlot();
157 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) continue;
158 int endSlot = firstSlot+p.getTimeLocation().getNrHalfHoursPerMeeting()-1;
159 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
160 int dayCode = p.getTimeLocation().getDayCode();
161 for (int j=0;j<Constants.DAY_CODES.length;j++) {
162 if ((dayCode & Constants.DAY_CODES[j])!=0) {
163 histogramPerDay[i][j] += 1.0 / lecture.values().size();
164 }
165 }
166 }
167 }
168 }
169 //System.out.println("Histogram for department "+iDepartment+":");
170 double threshold = iSpreadFactor*((double)totalUsedSlots/(5.0*Constants.SLOTS_PER_DAY_NO_EVENINGS));
171 //System.out.println("Threshold["+iDepartment+"] = "+threshold);
172 int totalAvailableSlots = 0;
173 for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
174 //System.out.println(" "+fmt(i+1)+": "+fmt(histogramPerDay[i]));
175 for (int j=0;j<Constants.DAY_CODES.length;j++) {
176 iMaxCourses[i][j]=(int)(0.999+(histogramPerDay[i][j]<=threshold?iSpreadFactor*histogramPerDay[i][j]:histogramPerDay[i][j]));
177 totalAvailableSlots += iMaxCourses[i][j];
178 }
179 }
180 iCurrentPenalty = 0;
181 for (Enumeration e=variables().elements();e.hasMoreElements();) {
182 Lecture lecture = (Lecture)e.nextElement();
183 Placement placement = (Placement)lecture.getAssignment();
184 if (placement==null) continue;
185 int firstSlot = placement.getTimeLocation().getStartSlot();
186 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) continue;
187 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
188 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
189 for (int j=0;j<Constants.DAY_CODES.length;j++) {
190 int dayCode = Constants.DAY_CODES[j];
191 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
192 iNrCourses[i][j]++;
193 iCourses[i][j].addElement(placement);
194 iCurrentPenalty += Math.max(0,iNrCourses[i][j]-iMaxCourses[i][j]);
195 }
196 }
197 }
198 }
199 iMaxAllowedPenalty = iCurrentPenalty;
200 //System.out.println("Initial penalty = "+fmt(iMaxAllowedPenalty));
201 iInitialized = true;
202 }
203
204 private Lecture getAdept(Placement placement, int[][] nrCourses, Set conflicts) {
205 //sLogger.debug(" -- looking for an adept");
206 Lecture adept = null;
207 int improvement = 0;
208 for (Enumeration e=variables().elements();e.hasMoreElements();) {
209 Lecture lect = (Lecture)e.nextElement();
210 if (lect.getAssignment()==null || lect.equals(placement.variable())) continue;
211 if (conflicts.contains(lect.getAssignment())) continue;
212 int imp = getPenaltyIfUnassigned((Placement)lect.getAssignment(),nrCourses);
213 if (imp==0) continue;
214 //sLogger.debug(" -- "+lect+" can decrease penalty by "+imp);
215 if (adept==null || imp>improvement) {
216 adept = lect;
217 improvement = imp;
218 }
219 }
220 //sLogger.debug(" -- adept "+adept+" selected, penalty will be decreased by "+improvement);
221 return adept;
222 }
223
224 private int getPenaltyIfUnassigned(Placement placement, int[][] nrCourses) {
225 int firstSlot = placement.getTimeLocation().getStartSlot();
226 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
227 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
228 int penalty = 0;
229 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
230 for (int j=0;j<Constants.DAY_CODES.length;j++) {
231 int dayCode = Constants.DAY_CODES[j];
232 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 &&
233 nrCourses[i][j]>iMaxCourses[i][j]) penalty ++;
234 }
235 }
236 return penalty;
237 }
238
239 private int tryUnassign(Placement placement, int[][] nrCourses) {
240 //sLogger.debug(" -- trying to unassign "+placement);
241 int firstSlot = placement.getTimeLocation().getStartSlot();
242 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
243 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
244 int improvement = 0;
245 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
246 for (int j=0;j<Constants.DAY_CODES.length;j++) {
247 int dayCode = Constants.DAY_CODES[j];
248 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
249 if (nrCourses[i][j]>iMaxCourses[i][j]) improvement++;
250 nrCourses[i][j]--;
251 }
252 }
253 }
254 //sLogger.debug(" -- penalty is decreased by "+improvement);
255 return improvement;
256 }
257
258 private int tryAssign(Placement placement, int[][] nrCourses) {
259 //sLogger.debug(" -- trying to assign "+placement);
260 int firstSlot = placement.getTimeLocation().getStartSlot();
261 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
262 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
263 int penalty = 0;
264 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
265 for (int j=0;j<Constants.DAY_CODES.length;j++) {
266 int dayCode = Constants.DAY_CODES[j];
267 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
268 nrCourses[i][j]++;
269 if (nrCourses[i][j]>iMaxCourses[i][j]) penalty++;
270 }
271 }
272 }
273 //sLogger.debug(" -- penalty is incremented by "+penalty);
274 return penalty;
275 }
276
277 public void computeConflicts(Value value, Set conflicts) {
278 if (!iInitialized || iUnassignmentsToWeaken==0) return;
279 Placement placement = (Placement)value;
280 int penalty = iCurrentPenalty + getPenalty(placement);
281 if (penalty<=iMaxAllowedPenalty) return;
282 int firstSlot = placement.getTimeLocation().getStartSlot();
283 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
284 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
285 //sLogger.debug("-- computing conflict for value "+value+" ... (penalty="+iCurrentPenalty+", penalty with the value="+penalty+", max="+iMaxAllowedPenalty+")");
286 int[][] nrCourses = new int[iNrCourses.length][Constants.DAY_CODES.length];
287 for (int i=0;i<iNrCourses.length;i++)
288 for (int j=0;j<Constants.DAY_CODES.length;j++)
289 nrCourses[i][j] = iNrCourses[i][j];
290 tryAssign(placement, nrCourses);
291 //sLogger.debug(" -- nrCurses="+fmt(nrCourses));
292 for (Enumeration e=variables().elements();e.hasMoreElements();) {
293 Lecture lect = (Lecture)e.nextElement();
294 if (conflicts.contains(lect)) {
295 penalty -= tryUnassign((Placement)lect.getAssignment(), nrCourses);
296 }
297 if (penalty<=iMaxAllowedPenalty) return;
298 }
299 while (penalty>iMaxAllowedPenalty) {
300 Lecture lect = getAdept(placement, nrCourses, conflicts);
301 if (lect==null) break;
302 conflicts.add(lect.getAssignment());
303 //sLogger.debug(" -- conflict "+lect.getAssignment()+" added");
304 penalty -= tryUnassign((Placement)lect.getAssignment(), nrCourses);
305 }
306 }
307
308 public boolean inConflict(Value value) {
309 if (!iInitialized || iUnassignmentsToWeaken==0) return false;
310 Placement placement = (Placement)value;
311 return getPenalty(placement)+iCurrentPenalty>iMaxAllowedPenalty;
312 }
313
314 public boolean isConsistent(Value value1, Value value2) {
315 if (!iInitialized || iUnassignmentsToWeaken==0) return true;
316 Placement p1 = (Placement)value1;
317 Placement p2 = (Placement)value2;
318 if (!p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) return true;
319 int firstSlot = Math.max(p1.getTimeLocation().getStartSlot(),p2.getTimeLocation().getStartSlot());
320 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return true;
321 int endSlot = Math.min(p1.getTimeLocation().getStartSlot()+p1.getTimeLocation().getNrHalfHoursPerMeeting()-1,p2.getTimeLocation().getStartSlot()+p2.getTimeLocation().getNrHalfHoursPerMeeting()-1);
322 int penalty = 0;
323 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
324 for (int j=0;j<Constants.DAY_CODES.length;j++) {
325 int dayCode = Constants.DAY_CODES[j];
326 if ((dayCode & p1.getTimeLocation().getDayCode()) != 0 && (dayCode & p2.getTimeLocation().getDayCode()) != 0) {
327 penalty += Math.max(0,2-iMaxCourses[i][j]);
328 }
329 }
330 }
331 return (penalty<iMaxAllowedPenalty);
332 }
333
334 private void weaken() {
335 if (!iInitialized) return;
336 if (iUnassignmentsToWeaken==0) return;
337 iMaxAllowedPenalty ++;
338 }
339
340 public void assigned(long iteration, Value value) {
341 super.assigned(iteration, value);
342 if (!iInitialized) return;
343 Placement placement = (Placement)value;
344 int firstSlot = placement.getTimeLocation().getStartSlot();
345 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
346 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
347 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
348 for (int j=0;j<Constants.DAY_CODES.length;j++) {
349 int dayCode = Constants.DAY_CODES[j];
350 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
351 iNrCourses[i][j]++;
352 if (iNrCourses[i][j]>iMaxCourses[i][j]) iCurrentPenalty++;
353 iCourses[i][j].addElement(value);
354 }
355 }
356 }
357 }
358
359 public void unassigned(long iteration, Value value) {
360 super.unassigned(iteration, value);
361 if (!iInitialized) return;
362 Placement placement = (Placement)value;
363 int firstSlot = placement.getTimeLocation().getStartSlot();
364 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
365 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
366 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
367 for (int j=0;j<Constants.DAY_CODES.length;j++) {
368 int dayCode = Constants.DAY_CODES[j];
369 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
370 if (iNrCourses[i][j]>iMaxCourses[i][j]) iCurrentPenalty--;
371 iNrCourses[i][j]--;
372 iCourses[i][j].removeElement(value);
373 }
374 }
375 }
376 }
377
378 /** Increment unassignment counter */
379 public void incUnassignmentCounter(Value value) {
380 iUnassignment ++;
381 if (iUnassignment%iUnassignmentsToWeaken==0)
382 weaken();
383 }
384
385 public String toString() {
386 if (!iInitialized) return iDepartment+" (not initialized)";
387 return iDepartment+" (p="+fmt(getPenalty())+", mx="+fmt(iMaxCourses)+", mp="+fmt(iMaxAllowedPenalty)+"): "+fmt(iNrCourses);
388 }
389
390 /** Department balancing penalty for this department */
391 public int getPenalty() {
392 if (!iInitialized) return 0;
393 return iCurrentPenalty;
394 }
395
396 /** Department balancing penalty of the given placement */
397 public int getPenalty(Placement placement) {
398 if (!iInitialized) return 0;
399 int firstSlot = placement.getTimeLocation().getStartSlot();
400 if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
401 int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
402 int penalty = 0;
403 for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
404 for (int j=0;j<Constants.DAY_CODES.length;j++) {
405 int dayCode = Constants.DAY_CODES[j];
406 if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 &&
407 iNrCourses[i][j]>=iMaxCourses[i][j]) penalty ++;
408 }
409 }
410 return penalty;
411 }
412
413 private String fmt(double value) {
414 return sDoubleFormat.format(value);
415 }
416
417 private String fmt(double[] values) {
418 if (values==null) return null;
419 StringBuffer sb = new StringBuffer("[");
420 for (int i=0;i<Math.min(5,values.length);i++)
421 sb.append(i==0?"":",").append(sDoubleFormat.format(values[i]));
422 sb.append("]");
423 return sb.toString();
424 }
425
426 private String fmt(double[][] values) {
427 if (values==null) return null;
428 StringBuffer sb = new StringBuffer("[");
429 for (int i=0;i<values.length;i++) {
430 /*double total = 0;
431 for (int j=0;j<values[i].length;j++)
432 total += values[i][j];
433 sb.append(i==0?"":",").append(fmt(total));*/
434 sb.append(i==0?"":",").append(fmt(values[i]));
435 }
436 sb.append("]");
437 return sb.toString();
438 }
439
440 private String fmt(int value) {
441 return sIntFormat.format(value);
442 }
443
444 private String fmt(int[] values) {
445 if (values==null) return null;
446 StringBuffer sb = new StringBuffer("[");
447 for (int i=0;i<Math.min(5,values.length);i++)
448 sb.append(i==0?"":",").append(sIntFormat.format(values[i]));
449 sb.append("]");
450 return sb.toString();
451 }
452
453 private String fmt(int[][] values) {
454 if (values==null) return null;
455 StringBuffer sb = new StringBuffer("[");
456 for (int i=0;i<values.length;i++) {
457 /*int total = 0;
458 for (int j=0;j<values[i].length;j++)
459 total += values[i][j];
460 sb.append(i==0?"":",").append(fmt(total));*/
461 sb.append(i==0?"":",").append(fmt(values[i]));
462 }
463 sb.append("]");
464 return sb.toString();
465 }
466 }