001package org.cpsolver.ifs.util; 002 003import java.io.ByteArrayOutputStream; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.IOException; 007import java.io.OutputStream; 008import java.io.PrintStream; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Date; 012import java.util.Enumeration; 013import java.util.HashSet; 014import java.util.Iterator; 015import java.util.List; 016import java.util.Map; 017import java.util.Properties; 018import java.util.Random; 019import java.util.Set; 020import java.util.StringTokenizer; 021import java.util.TreeSet; 022 023import org.apache.logging.log4j.Level; 024import org.apache.logging.log4j.LogManager; 025import org.apache.logging.log4j.Logger; 026import org.apache.logging.log4j.core.config.Configurator; 027import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; 028import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; 029import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 030import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; 031import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; 032import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; 033import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; 034import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 035 036/** 037 * Several auxiliary static methods. 038 * 039 * @author Tomáš Müller 040 * @version IFS 1.3 (Iterative Forward Search)<br> 041 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 042 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 043 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 044 * <br> 045 * This library is free software; you can redistribute it and/or modify 046 * it under the terms of the GNU Lesser General Public License as 047 * published by the Free Software Foundation; either version 3 of the 048 * License, or (at your option) any later version. <br> 049 * <br> 050 * This library is distributed in the hope that it will be useful, but 051 * WITHOUT ANY WARRANTY; without even the implied warranty of 052 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 053 * Lesser General Public License for more details. <br> 054 * <br> 055 * You should have received a copy of the GNU Lesser General Public 056 * License along with this library; if not see 057 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 058 */ 059public class ToolBox { 060 private static long sSeed = System.currentTimeMillis(); 061 private static Random sRandom = new Random(sSeed); 062 063 /** Returns random number (int) from the set 0 .. limit - 1 064 * @param limit a limit 065 * @return a random number between 0 and limit - 1 066 **/ 067 public static int random(int limit) { 068 return (int) (random() * limit); 069 } 070 071 /** Returns random element from the given set of elements 072 * @param set collection of objects 073 * @param <E> some type 074 * @return randomly selected object 075 **/ 076 public static <E> E random(Collection<E> set) { 077 switch (set == null ? 0 : set.size()) { 078 case 0: 079 return null; 080 case 1: 081 return set.iterator().next(); 082 case 2: 083 Iterator<E> i = set.iterator(); 084 if (sRandom.nextBoolean()) i.next(); 085 return i.next(); 086 default: 087 int index = random(set.size()); 088 if (set instanceof List<?>) return ((List<E>)set).get(index); 089 Iterator<E> it = set.iterator(); 090 for (int j = 0; j < index; j++) it.next(); 091 return it.next(); 092 } 093 } 094 095 /** 096 * Returns a randomly generated subset of the given set 097 * 098 * @param set 099 * set 100 * @param part 101 * probability of selection of an element into the resultant 102 * subset 103 * @param <E> some type 104 * @return randomly selected subset 105 */ 106 public static <E> Collection<E> subSet(Collection<E> set, double part) { 107 return subSet(set, part, 1); 108 } 109 110 /** Swaps two elements in the list*/ 111 private static <E> void swap(List<E> list, int first, int second) { 112 E o = list.get(first); 113 list.set(first, list.get(second)); 114 list.set(second, o); 115 } 116 117 /** 118 * Returns a randomly generated subset of the given set 119 * 120 * @param set 121 * set 122 * @param part 123 * probability of selection of an element into the resultant 124 * subset 125 * @param minSize 126 * minimal size of the returned subset 127 * @param <E> some type 128 * @return randomly selected subset 129 */ 130 public static <E> Collection<E> subSet(Collection<E> set, double part, int minSize) { 131 if (set.size() <= minSize || part >= 1.0) 132 return set; 133 ArrayList<E> subSet = new ArrayList<E>(set); 134 int size = set.size(); 135 int numberToSelect = Math.max(minSize, (int) (part * set.size())); 136 for (int idx = 0; idx < numberToSelect; idx++) { 137 swap(subSet, idx, idx + (int) (random() * (size - idx))); 138 } 139 return subSet.subList(0, numberToSelect); 140 } 141 142 /** Trim a string to have given length 143 * @param s a string to trim 144 * @param length a length to trim to 145 * @return trimmed and padded string of the given length 146 **/ 147 public static String trim(String s, int length) { 148 if (s.length() > length) 149 return s.substring(0, length); 150 StringBuffer sb = new StringBuffer(s); 151 while (sb.length() < length) 152 sb.append(" "); 153 return sb.toString(); 154 } 155 156 /** Multiline representation of a collection 157 * @param col a collection 158 * @param tab tab size 159 * @return string representation 160 **/ 161 public static String col2string(Collection<?> col, int tab) { 162 StringBuffer tabsb = new StringBuffer(); 163 while (tabsb.length() < 2 * tab) 164 tabsb.append(" "); 165 StringBuffer sb = new StringBuffer("[\n"); 166 for (Iterator<?> i = col.iterator(); i.hasNext();) { 167 sb.append(tabsb + " " + i.next() + (i.hasNext() ? "," : "") + "\n"); 168 } 169 sb.append(tabsb + "]"); 170 return sb.toString(); 171 } 172 173 /** Multiline representation of a dictionary 174 * @param dict a map 175 * @param tab tab size 176 * @param <K> a key class 177 * @param <V> a value class 178 * @return string representation 179 */ 180 public static <K, V> String dict2string(Map<K, V> dict, int tab) { 181 StringBuffer tabsb = new StringBuffer(); 182 while (tabsb.length() < 2 * tab) 183 tabsb.append(" "); 184 StringBuffer sb = new StringBuffer("[\n"); 185 TreeSet<K> keys = new TreeSet<K>(dict.keySet()); 186 for (K key : keys) { 187 V value = dict.get(key); 188 sb.append(tabsb + " " + key + ": " + value + "\n"); 189 } 190 sb.append(tabsb + "]"); 191 return sb.toString(); 192 } 193 194 /** 195 * Root mean square 196 * 197 * @param n 198 * number of tests 199 * @param x 200 * total value of all tests 201 * @param x2 202 * total value^2 of all tests 203 * @return root mean square 204 */ 205 public static double rms(int n, double x, double x2) { 206 double var = x2 / n; 207 double mean = x / n; 208 return Math.sqrt(Math.abs(var - mean * mean)); 209 } 210 211 /** Merge source with target 212 * @param target target list 213 * @param source source list 214 * @param <E> some object 215 **/ 216 public static <E> void merge(List<E> target, Collection<E> source) { 217 for (E o : source) { 218 if (!target.contains(o)) 219 target.add(o); 220 } 221 } 222 223 /** Returns intersection of two collections 224 * @param source1 first collection 225 * @param source2 second collection 226 * @param <E> some object 227 * @return intersection 228 */ 229 public static <E> List<E> intersect(Collection<E> source1, Collection<E> source2) { 230 List<E> target = new ArrayList<E>(); 231 for (E o : source1) { 232 if (!source2.contains(o)) 233 target.add(o); 234 } 235 return target; 236 } 237 238 /** 239 * Sets seeds for {@link ToolBox#getRandom()} and {@link ToolBox#random()} 240 * methods. 241 * @param seed random seed 242 */ 243 public static void setSeed(long seed) { 244 sSeed = seed; 245 sRandom = new Random(sSeed); 246 } 247 248 /** Gets current seed 249 * @return random seed 250 **/ 251 public static long getSeed() { 252 return sSeed; 253 } 254 255 /** Gets random number generator 256 * @return random number generator 257 **/ 258 public static Random getRandom() { 259 return sRandom; 260 } 261 262 /** Generates random double number 263 * @return random number 264 **/ 265 public static double random() { 266 return sRandom.nextDouble(); 267 } 268 269 /** Configurates log4j loging */ 270 public static void configureLogging() { 271 if ("true".equalsIgnoreCase(System.getProperty("org.cpsolver.debug", "false"))) { 272 Configurator.setLevel("org.cpsolver", Level.DEBUG); 273 } 274 } 275 276 /** 277 * Setup log4j logging 278 * 279 * @param logFile log file 280 * @param debug true if debug messages should be logged (use -Ddebug=true to enable debug message) 281 */ 282 public static void setupLogging(File logFile, boolean debug) { 283 ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder(); 284 285 AppenderComponentBuilder console = builder.newAppender("stdout", "Console"); 286 console.addAttribute("target", "SYSTEM_OUT"); 287 LayoutComponentBuilder consoleLayout = builder.newLayout("PatternLayout"); 288 consoleLayout.addAttribute("pattern", "%-5p %c{2}: %m%n"); 289 console.add(consoleLayout); 290 builder.add(console); 291 292 RootLoggerComponentBuilder root = builder.newRootLogger(Level.INFO); 293 root.add(builder.newAppenderRef("stdout")); 294 builder.add(root); 295 296 if (logFile != null) { 297 AppenderComponentBuilder file = builder.newAppender("log", "File"); 298 file.addAttribute("fileName", logFile.getPath()); 299 300 LayoutComponentBuilder filePattern = builder.newLayout("PatternLayout"); 301 filePattern.addAttribute("pattern", "%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n"); 302 file.add(filePattern); 303 builder.add(file); 304 305 LoggerComponentBuilder logger = builder.newLogger("org.cpsolver", debug ? Level.DEBUG : Level.INFO); 306 logger.add(builder.newAppenderRef("log")); 307 logger.addAttribute("additivity", true); 308 builder.add(logger); 309 } 310 Configurator.reconfigure(builder.build()); 311 } 312 313 /** 314 * Configure log4j logging 315 * 316 * @param logDir 317 * output folder 318 * @param properties 319 * some other log4j properties 320 * @return name of the log file 321 */ 322 public static String configureLogging(String logDir, Properties properties) { 323 return configureLogging(logDir, properties, false); 324 } 325 326 /** 327 * Configure log4j logging 328 * 329 * @param logDir 330 * output folder 331 * @param properties 332 * some other log4j properties 333 * @param timeInFileName 334 * if true log file is named debug_yyyy-MM-dd_(HH.mm.ss).log, it 335 * is named debug.log otherwise 336 * @return name of the log file 337 */ 338 public static String configureLogging(String logDir, Properties properties, boolean timeInFileName) { 339 return configureLogging(logDir, properties, timeInFileName, true); 340 } 341 342 /** 343 * Configure log4j logging 344 * 345 * @param logDir 346 * output folder 347 * @param properties 348 * some other log4j properties 349 * @param timeInFileName 350 * if true log file is named debug_yyyy-MM-dd_(HH.mm.ss).log, it 351 * is named debug.log otherwise 352 * @param includeSystemOuts include system out and error in the log 353 * @return name of the log file 354 */ 355 public static String configureLogging(String logDir, Properties properties, boolean timeInFileName, boolean includeSystemOuts) { 356 (new File(logDir)).mkdirs(); 357 358 ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder(); 359 360 AppenderComponentBuilder console = builder.newAppender("stdout", "Console"); 361 LayoutComponentBuilder consoleLayout = builder.newLayout("PatternLayout"); 362 consoleLayout.addAttribute("pattern", "[%t] %m%n"); 363 console.add(consoleLayout); 364 builder.add(console); 365 366 AppenderComponentBuilder file = null; 367 String fileName = null; 368 if (timeInFileName) { 369 file = builder.newAppender("log", "File"); 370 String time = new java.text.SimpleDateFormat("yyyy-MM-dd_(HH.mm.ss)", java.util.Locale.US).format(new Date()); 371 fileName = logDir + File.separator + "debug_" + time + ".log"; 372 file.addAttribute("fileName", fileName); 373 } else { 374 file = builder.newAppender("log", "RollingFile"); 375 fileName = logDir + File.separator + "debug.log"; 376 file.addAttribute("fileName", fileName); 377 file.addAttribute("filePattern", logDir + File.separator + "debug-%d{MM-dd-yy}-%i.log.gz"); 378 ComponentBuilder<?> triggeringPolicies = builder.newComponent("Policies") 379 .addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?")) 380 .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "100M")); 381 file.addComponent(triggeringPolicies); 382 } 383 LayoutComponentBuilder filePattern = builder.newLayout("PatternLayout"); 384 filePattern.addAttribute("pattern", "%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n"); 385 file.add(filePattern); 386 builder.add(file); 387 388 RootLoggerComponentBuilder root = builder.newRootLogger("true".equals(System.getProperty("org.cpsolver.debug", "false")) ? Level.DEBUG : Level.INFO); 389 root.add(builder.newAppenderRef("log")); 390 builder.add(root); 391 392 LoggerComponentBuilder info = builder.newLogger("org.cpsolver", Level.INFO); 393 info.add(builder.newAppenderRef("stdout")); 394 info.addAttribute("additivity", true); 395 builder.add(info); 396 397 if (properties != null) { 398 for (Map.Entry<Object, Object> e: properties.entrySet()) { 399 String property = (String)e.getKey(); 400 String value = (String)e.getValue(); 401 if (property.startsWith("log4j.logger.")) { 402 if (value.indexOf(',') < 0) { 403 LoggerComponentBuilder logger = builder.newLogger(property.substring("log4j.logger.".length()), Level.getLevel(value)); 404 builder.add(logger); 405 } else { 406 String level = value.substring(0, value.indexOf(',')); 407 String appender = value.substring(value.indexOf(',') + 1); 408 LoggerComponentBuilder logger = builder.newLogger(property.substring("log4j.logger.".length()), Level.getLevel(level)); 409 for (String a: appender.split(",")) 410 logger.add(builder.newAppenderRef(a)); 411 builder.add(logger); 412 } 413 } 414 } 415 } 416 Configurator.reconfigure(builder.build()); 417 418 Logger log = LogManager.getRootLogger(); 419 log.info("-----------------------------------------------------------------------"); 420 log.info("IFS debug file"); 421 log.info(""); 422 log.info("Created: " + new Date()); 423 log.info(""); 424 log.info("System info:"); 425 log.info("System: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " " 426 + System.getProperty("os.arch")); 427 log.info("CPU: " + System.getProperty("sun.cpu.isalist") + " endian:" 428 + System.getProperty("sun.cpu.endian") + " encoding:" + System.getProperty("sun.io.unicode.encoding")); 429 log.info("Java: " + System.getProperty("java.vendor") + ", " + System.getProperty("java.runtime.name") 430 + " " + System.getProperty("java.runtime.version", System.getProperty("java.version"))); 431 log.info("User: " + System.getProperty("user.name")); 432 log.info("Timezone: " + System.getProperty("user.timezone")); 433 log.info("Working dir: " + System.getProperty("user.dir")); 434 log.info("Classpath: " + System.getProperty("java.class.path")); 435 log.info(""); 436 if (includeSystemOuts) { 437 System.setErr(new PrintStream(new LogOutputStream(System.err, org.apache.logging.log4j.LogManager.getLogger("STDERR"), Level.ERROR))); 438 System.setOut(new PrintStream(new LogOutputStream(System.out, org.apache.logging.log4j.LogManager.getLogger("STDOUT"), Level.DEBUG))); 439 } 440 return fileName; 441 } 442 443 /** 444 * Loads data properties. If there is INCLUDE property available, it is 445 * interpreted as semi-colon separated list of porperty files which should 446 * be also loaded (works recursively). 447 * @param propertyFile a file to read 448 * @return solver configuration 449 * 450 */ 451 public static DataProperties loadProperties(File propertyFile) { 452 FileInputStream is = null; 453 try { 454 DataProperties ret = new DataProperties(); 455 is = new FileInputStream(propertyFile); 456 ret.load(is); 457 is.close(); 458 is = null; 459 if (ret.getProperty("INCLUDE") != null) { 460 461 StringTokenizer stk = new StringTokenizer(ret.getProperty("INCLUDE"), ";"); 462 while (stk.hasMoreTokens()) { 463 String aFile = stk.nextToken(); 464 System.out.println(" Loading included file '" + aFile + "' ... "); 465 if ((new File(aFile)).exists()) 466 is = new FileInputStream(aFile); 467 else if ((new File(propertyFile.getParent() + File.separator + aFile)).exists()) 468 is = new FileInputStream(propertyFile.getParent() + File.separator + aFile); 469 else 470 System.err.println("Unable to find include file '" + aFile + "'."); 471 ret.load(is); 472 is.close(); 473 is = null; 474 } 475 ret.remove("INCLUDE"); 476 } 477 return ret; 478 } catch (Exception e) { 479 System.err.println("Unable to load property file " + propertyFile); 480 e.printStackTrace(); 481 return new DataProperties(); 482 } finally { 483 try { 484 if (is != null) 485 is.close(); 486 } catch (IOException e) { 487 } 488 } 489 } 490 491 public static boolean equals(Object o1, Object o2) { 492 return (o1 == null ? o2 == null : o1.equals(o2)); 493 } 494 495 private static class LogOutputStream extends OutputStream { 496 private Logger iLogger = null; 497 private Level iLevel = null; 498 private OutputStream iOldOutputStream; 499 private ByteArrayOutputStream iOut = new ByteArrayOutputStream(); 500 501 public LogOutputStream(OutputStream oldOutputStream, Logger logger, Level level) { 502 iLogger = logger; 503 iLevel = level; 504 iOldOutputStream = oldOutputStream; 505 } 506 507 @Override 508 public void write(int b) throws IOException { 509 iOldOutputStream.write(b); 510 if (b == '\r') 511 return; 512 if (b == '\n') { 513 iOut.flush(); 514 iLogger.log(iLevel, new String(iOut.toByteArray())); 515 iOut.reset(); 516 } else 517 iOut.write(b); 518 } 519 } 520 521 /** 522 * Convert array of elements into a list 523 * @param obj array of elements 524 * @return list of elements 525 */ 526 public static <E> List<E> toList(@SuppressWarnings("unchecked") E... obj) { 527 List<E> ret = new ArrayList<E>(obj == null ? 0 : obj.length); 528 if (obj != null) 529 for (E e: obj) 530 ret.add(e); 531 return ret; 532 } 533 534 /** 535 * Compute number of K-tuples of N elements 536 * @param N number of elements (e.g., number of room locations in a domain) 537 * @param K size of a tuple (e.g., number of rooms a class needs) 538 * @return number of different K-tupples of N elements 539 */ 540 public static long binomial(int N, int K) { 541 long ret = 1; 542 for (int k = 0; k < K; k++) 543 ret = ret * (N-k) / (k+1); 544 return ret; 545 } 546 547 /** 548 * Create random sample (m-tuple) of given list of elements 549 * @param items list of elements 550 * @param m size of a tuple 551 * @return random subset of the list of size m 552 */ 553 public static <E> Set<E> sample(List<E> items, int m) { 554 HashSet<E> res = new HashSet<E>(m); 555 int n = items.size(); 556 for(int i = n - m; i < n; i++){ 557 int pos = getRandom().nextInt(i+1); 558 E item = items.get(pos); 559 if (res.contains(item)) 560 res.add(items.get(i)); 561 else 562 res.add(item); 563 } 564 return res; 565 } 566 567 /** 568 * Generate given permutation 569 * @param items list of elements 570 * @param m size of a tuple (permutation) 571 * @param id position of the permutation in the list of all permutations of m-tuples of the given list of elements 572 * @return given subset of the list of size m 573 */ 574 public static <E> List<E> permutation(final List<E> items, int m, long id) { 575 List<E> ret = new ArrayList<E>(); 576 int n = items.size(); 577 int p = -1; 578 for (int i = 0; i < m - 1; i++) { 579 p ++; 580 for (long r = binomial(n - p - 1, m - i - 1); r <= id; r = binomial(n - p - 1, m - i - 1)) { 581 id -= r; p ++; 582 } 583 ret.add(items.get(p)); 584 } 585 ret.add(items.get(p + 1 + (int)id)); 586 return ret; 587 } 588 589 /** 590 * Generate a list of samples of the given list 591 * @param items list of elements 592 * @param m size of a sample 593 * @param count number of samples 594 * @return list of samples (m-tuples) of the given items 595 */ 596 public static <E> Enumeration<Collection<E>> sample(final List<E> items, final int m, final int count) { 597 final long limit = binomial(items.size(), m); 598 if (count >= limit) return permutations(items, m); 599 return new Enumeration<Collection<E>>() { 600 int el = 0; 601 Set<Long> used = new HashSet<Long>(); 602 603 @Override 604 public boolean hasMoreElements() { 605 return el < count && el < limit; 606 } 607 608 @Override 609 public Set<E> nextElement() { 610 int n = items.size(); 611 for (;;) { 612 HashSet<E> res = new HashSet<E>(m); 613 TreeSet<Integer> ids = new TreeSet<Integer>(); 614 for(int i = n - m; i < n; i++){ 615 int pos = getRandom().nextInt(i+1); 616 E item = items.get(pos); 617 if (res.contains(item)) { 618 res.add(items.get(i)); 619 ids.add(i); 620 } else { 621 res.add(item); 622 ids.add(pos); 623 } 624 } 625 long fp = 0; 626 for (Integer id: ids) { 627 fp = (n * fp) + id; 628 } 629 if (used.add(fp)) { 630 el ++; 631 return res; 632 } 633 } 634 } 635 }; 636 } 637 638 /** 639 * Generate a list of all permutations of size m of the given list 640 * @param items list of elements 641 * @param m size of a permutation 642 * @return list of all permutations (m-tuples) of the given items 643 */ 644 public static <E> Enumeration<Collection<E>> permutations(final List<E> items, final int m) { 645 return new Enumeration<Collection<E>>() { 646 int n = items.size(); 647 int[] p = null; 648 649 @Override 650 public boolean hasMoreElements() { 651 return p == null || p[0] < n - m; 652 } 653 654 @Override 655 public Collection<E> nextElement() { 656 if (p == null) { 657 p = new int[m]; 658 for (int i = 0; i < m; i++) 659 p[i] = i; 660 } else { 661 for (int i = m - 1; i >= 0; i--) { 662 p[i] = p[i] + 1; 663 for (int j = i + 1; j < m; j++) 664 p[j] = p[j - 1] + 1; 665 if (i == 0 || p[i] <= n - (m - i)) break; 666 } 667 } 668 List<E> ret = new ArrayList<E>(); 669 for (int i = 0; i < m; i++) 670 ret.add(items.get(p[i])); 671 return ret; 672 } 673 }; 674 } 675 676 /** 677 * Generate a list of random samples combined of the given two lists 678 * @param items1 list of first elements 679 * @param m1 size of a first sample 680 * @param items2 list of second elements 681 * @param m2 size of a second sample 682 * @param count number of samples 683 * @return list of samples where each sample contains m1 elements of the first list and m2 elements of the second list 684 */ 685 private static <E> Enumeration<Collection<E>> sample(final List<E> items1, final int m1, final List<E> items2, final int m2, final int count) { 686 final long c1 = binomial(items1.size(), m1); 687 final long c2 = binomial(items2.size(), m2); 688 final long limit = c1 * c2; 689 if (limit <= 10l * count && 10l * count < Integer.MAX_VALUE) { 690 return new Enumeration<Collection<E>>() { 691 Set<Integer> used = new HashSet<Integer>(); 692 693 @Override 694 public boolean hasMoreElements() { 695 return used.size() < count && used.size() < limit; 696 } 697 698 @Override 699 public Collection<E> nextElement() { 700 int id; 701 do { 702 id = getRandom().nextInt((int)limit); 703 } while (!used.add(id)); 704 List<E> res = new ArrayList<E>(m1 + m2); 705 if (m1 > 0) 706 res.addAll(permutation(items1, m1, id / c2)); 707 if (m2 > 0) 708 res.addAll(permutation(items2, m2, id % c2)); 709 return res; 710 } 711 }; 712 } else { 713 return new Enumeration<Collection<E>>() { 714 int n1 = items1.size(), n2 = items2.size(); 715 int el = 0; 716 Set<Long> used = new HashSet<Long>(); 717 718 @Override 719 public boolean hasMoreElements() { 720 return (count < 0 || el < count) && el < limit; 721 } 722 723 @Override 724 public Collection<E> nextElement() { 725 for (;;) { 726 HashSet<E> res = new HashSet<E>(m1 + m2); 727 TreeSet<Integer> ids1 = new TreeSet<Integer>(); 728 if (m1 == n1) { 729 // Special case 1: first permutation contains all elements 730 res.addAll(items1); 731 } else if (m1 + 1 == n1) { 732 // Special case 2: first permutation contains all elements but one 733 int pos = getRandom().nextInt(n1); 734 for (int i = 0; i < n1; i++) 735 if (i != pos) res.add(items1.get(i)); 736 ids1.add(pos); 737 } else { 738 for(int i = n1 - m1; i < n1; i++){ 739 int pos = getRandom().nextInt(i+1); 740 E item = items1.get(pos); 741 if (res.contains(item)) { 742 res.add(items1.get(i)); 743 ids1.add(i); 744 } else { 745 res.add(item); 746 ids1.add(pos); 747 } 748 } 749 } 750 TreeSet<Integer> ids2 = new TreeSet<Integer>(); 751 if (m2 == n2) { 752 // Special case 1: second permutation contains all elements 753 res.addAll(items2); 754 } else if (m2 + 1 == n2) { 755 // Special case 2: second permutation contains all elements but one 756 int pos = getRandom().nextInt(n2); 757 for (int i = 0; i < n2; i++) 758 if (i != pos) res.add(items2.get(i)); 759 ids2.add(pos); 760 } else { 761 for(int i = n2 - m2; i < n2; i++){ 762 int pos = getRandom().nextInt(i+1); 763 E item = items2.get(pos); 764 if (res.contains(item)) { 765 res.add(items2.get(i)); 766 ids2.add(n1 + i); 767 } else { 768 res.add(item); 769 ids2.add(n1 + pos); 770 } 771 } 772 } 773 long fp = 0; 774 for (Integer id: ids1) fp = (n1 * fp) + id; 775 for (Integer id: ids2) fp = (n2 * fp) + id; 776 if (used.add(fp)) { 777 el ++; 778 return res; 779 } 780 } 781 } 782 }; 783 } 784 } 785 786 /** 787 * Generate a list of random samples combined of the given two lists 788 * @param preferred list of preferred elements 789 * @param additional list of additional elements 790 * @param m size of a sample 791 * @param count number of samples 792 * @return list of samples of size m, preferring as many elements of the preferred list as possible 793 */ 794 public static <E> Enumeration<Collection<E>> sample(final List<E> preferred, final List<E> additional, final int m, final int count) { 795 return new Enumeration<Collection<E>>() { 796 long limit = (count < 0 ? binomial(preferred.size() + additional.size(), m) : Math.min(count, binomial(preferred.size() + additional.size(), m))); 797 int k = Math.min(m, preferred.size()); 798 int el = 0; 799 Enumeration<Collection<E>> e = sample(preferred, k, additional, m - k, count); 800 801 @Override 802 public boolean hasMoreElements() { 803 return el < limit; 804 } 805 806 @Override 807 public Collection<E> nextElement() { 808 if (e.hasMoreElements()) { 809 el ++; 810 return e.nextElement(); 811 } 812 k = Math.max(Math.min(k - 1, preferred.size() - 1), 0); 813 e = sample(preferred, k, additional, m - k, count); 814 el ++; 815 return e.nextElement(); 816 } 817 }; 818 } 819}