/* Called in a background thread. Used to generate bitstrings of a specified length. These strings * are represented as an array of bools - true = 1, false = 0. This method is more efficient. * EX: ("11001") * * We use these bitstrings to represent all possible combinations of the sections (even invalid ones). * * @param e.Argument This value passed through the BackgroundWorker call is the length of the bitstring. */ private void generateAllBitStrings(object sender, DoWorkEventArgs e) { Dictionary<string, List<int>> classes = new Dictionary<string, List<int>>(); //Separate sections by class int i = 0; foreach (ClassSection section in possibleSections) { List<int> indices = new List<int>(); if (!classes.Keys.Contains(section.parentClass)) { indices.Add(i); classes.Add(section.parentClass, indices); } else { classes.TryGetValue(section.parentClass, out indices); indices.Add(i); } i++; } //Check each class for working combinations i = 0; Dictionary<int, List<DepartmentClass>> groups = new Dictionary<int, List<DepartmentClass>>(); foreach (var kv in classes) { short n = (short)kv.Value.Count; //The length of the bitstrings List<DepartmentClass> validClassCombinations = new List<DepartmentClass>(); List<ClassSection> sectionPool = getSectionPool(kv.Value, possibleSections); /* Progress tracking */ double targetStringCount = Math.Pow(2, n); //This is the target number of strings to generate double totalResults = 0; /* Begin generating bitstrings */ bool[] first = new bool[n]; //First string is all false for (short x= 0; x < n; x++) { first[x] = false; } bool[] intermediate = first; List<ClassSection> firstSections = checkBitstring(first, sectionPool); if (firstSections != null) { DepartmentClass temp = new DepartmentClass(); temp.sections = firstSections; validClassCombinations.Add(temp); } totalResults++; //For user-facing progress updates currentClassRunning++; ulong rollingTotal = 0; short multiplier = 1; ulong progressStep = (ulong)Math.Ceiling(targetStringCount / 100); this.Dispatcher.BeginInvoke(delegate { progress.Value = 0; }); MyStopwatch timer = new MyStopwatch(); /* Generating loop */ do { //Start timing if (rollingTotal == 0) { timer.start(); } //Make the next bitstring intermediate = generateNextBitstring(intermediate); //Update timeleft if (rollingTotal == Math.Pow(2, multiplier)) { float averageTime = (float)timer.stop() / (float)Math.Pow(2, multiplier); secondsRemaining = (int)((averageTime * (targetStringCount - totalResults)) / 1000.0); multiplier++; rollingTotal = 0; } //Update progress bar if (totalResults % progressStep == 0) { this.Dispatcher.BeginInvoke(delegate { progress.Value += 1; }); } //Increment counters totalResults++; rollingTotal++; //Check the bitstring we generated List<ClassSection> nextSections = checkBitstring(intermediate, sectionPool); if (nextSections != null) { DepartmentClass temp = new DepartmentClass(); temp.sections = nextSections; validClassCombinations.Add(temp); } } while (totalResults < targetStringCount); groups.Add(i, validClassCombinations); i++; } i = 1; //Combination two classes and weed out the combinations that aren't valid while (groups.Keys.Count > 1) { //Tournament! List<DepartmentClass> champCombination = new List<DepartmentClass>(); groups.TryGetValue(0, out champCombination); List<DepartmentClass> challengerCombination = new List<DepartmentClass>(); groups.TryGetValue(i, out challengerCombination); List<DepartmentClass> winnersCircle = new List<DepartmentClass>(); //Fight! foreach (DepartmentClass champ in champCombination) { foreach (DepartmentClass challenger in challengerCombination) { List<ClassSection> contender = new List<ClassSection>(); foreach (ClassSection section in champ.sections) { contender.Add(section); } foreach (ClassSection section in challenger.sections) { contender.Add(section); } if (combinationSuccessful(contender)) { DepartmentClass success = new DepartmentClass(); success.sections = contender; winnersCircle.Add(success); } } } groups.Remove(i); groups.Remove(0); groups.Add(0, winnersCircle); i++; } //Prepare the confirmed classed to be displayed List<DepartmentClass> confirmed = new List<DepartmentClass>(); groups.TryGetValue(0, out confirmed); i = 0; foreach (DepartmentClass parent in confirmed) { confirmedSections.Add(parent.sections); } //Report that we're done and ready for GUI update (sender as BackgroundWorker).ReportProgress(100, null); //Because the time remaining isn't an exact science, we cheat at the end and force the values to be where they should // at the end. secondsRemaining = 1; this.Dispatcher.BeginInvoke(delegate { progress.Value = 100; }); }
/* Called in a background thread. Used to generate bitstrings of a specified length. These strings * are represented as an array of bools - true = 1, false = 0. This method is more efficient. * EX: ("11001") * * We use these bitstrings to represent all possible combinations of the sections (even invalid ones). * * @param e.Argument This value passed through the BackgroundWorker call is the length of the bitstring. */ private void generateAllBitStrings(object sender, DoWorkEventArgs e) { short n = Convert.ToInt16(e.Argument as string); //The length of the bitstrings /* Progress reporting */ double nsquared = Math.Pow(2,n); //This is the target number of strings to generate //TODO cleanup double percentStep = nsquared / 100; if (percentStep == 0) percentStep = 1; double percentJump = nsquared / percentStep; if (percentJump == 0) percentJump = 1; else percentJump = 100 / percentJump; //Start timekeeping //MyStopwatch timer = new MyStopwatch(); double totalResults = 0.0; //Total results for progress reporting int multiplier = 2; //Increases the time between timeleft updates (exponential) double rollingTotal = 0.0; //Total results generated that resets after timeleft update /* Begin generating bitstrings */ bool[] first = new bool[n]; //First string is all false bool[] target = new bool[n]; //End string is all true for (short i = 0; i < n; i++) { first[i] = false; target[i] = true; } bool[] intermediate = first; checkBitstring(first); totalResults++; /* Generating loop */ MyStopwatch timer = new MyStopwatch(); do { if (rollingTotal == 0) { timer.start(); } //Make the next bitstring intermediate = generateNextBitstring(intermediate); //Increment counters totalResults++; rollingTotal++; //Check the bitstring we generated checkBitstring(intermediate); //Update the time left in exponential time if (rollingTotal == Math.Pow(2, multiplier)) { float averageTime = (float)timer.stop() / (float)Math.Pow(2, multiplier); secondsRemaining = (int)((averageTime * (nsquared - totalResults)) / 1000.0) + 2; multiplier++; rollingTotal = 0; } //Update the progress bar and report progress to update the GUI with valid combinations if (isBetween(0.0, totalResults % percentStep, 0.99)) { this.Dispatcher.BeginInvoke(delegate { progress.Value += percentJump; }); (sender as BackgroundWorker).ReportProgress(0, null); } //If we're performing too quickly, we need to slow down for the other threads to catch up. if ((nsquared < 10000) && isBetween(0, totalResults % 2, 0.99)) { Thread.Sleep(5); } } while (totalResults < nsquared); timer.stop(); Debug.WriteLine("Process took "+ (float)(timer.interval / 1000f) + " seconds."); /* After generating loop */ //Because the math for the percent step/jump isn't 100% accurate, when we are done checking combinations // we should "cheat" and set the progress bar to 100. this.Dispatcher.BeginInvoke(delegate { progress.Value = 100; }); (sender as BackgroundWorker).ReportProgress(100, null); //Cancel the time reporting functions counterDown.CancelAsync(); secondsRemaining = 0; this.Dispatcher.BeginInvoke(updateTimeLabel); }
/* Called when the submit button is clicked. Initiliazes some data and starts the schedule * generation process. */ private void btnSubmit_Click(object sender, RoutedEventArgs e) { //Clear all of the generated tabs tabBase.SelectedIndex = 0; for (int tabIter = (tabBase.Items.Count-1); tabIter > 1; tabIter--) { tabBase.Items.RemoveAt(tabIter); } totalClassCount = lstFinalClasses.Items.Count; currentClassRunning = 0; possibleSections = new List<ClassSection>(); //All of the possible courses confirmedSections = new List<List<ClassSection>>(); progress.Value = 0; //Reset the progress bar tabCount = 0; //Reset the tab numberings. secondsRemaining = 0; //Reset timeleft sectionsInGrid = new Dictionary<Grid, List<ClassSection>>(); //Reset the grid dictionary //Start finding bitstrings in the background BackgroundWorker bitstringworker = new BackgroundWorker(); bitstringworker.WorkerSupportsCancellation = false; bitstringworker.WorkerReportsProgress = true; bitstringworker.DoWork += new DoWorkEventHandler(generateAllBitStrings); bitstringworker.ProgressChanged += new ProgressChangedEventHandler(reportProgress); //Count how many courses there are so we know how many bitstrings to generate int totalCourses = 0; const int MAXCOURSES = 23; Random randomGen = new Random(); foreach (DepartmentClass finalClass in lstFinalClasses.Items) { //Random number of courses to pick from: // Take max courses. Remove the courses we have already. Make sure one section is leftover for the last class. int randomNumberOfSectionsFromThisClass = 0; if (finalClass != lstFinalClasses.Items.Last()) { randomNumberOfSectionsFromThisClass = (int)Math.Round(randomGen.NextDouble() * (MAXCOURSES - totalCourses - 1)); } else { randomNumberOfSectionsFromThisClass = MAXCOURSES - totalCourses; } foreach (ClassSection section in finalClass.sections) { //If course is not in exlusions if (!section.excluded) { //temporary crappy code to fill in the parent class information for sections section.parentClass = finalClass.ToString(); //end temporary crappy code } } List<ClassSection> possible = pickSections(finalClass, randomNumberOfSectionsFromThisClass, randomGen); MyStopwatch timeout = new MyStopwatch(); timeout.start(); while (!allLinksPresent(possible) && !ignoreLinks) { if (!limitRandom) { YesNoPopup popupLinkRemoved = new YesNoPopup("You have excluded a linked section in " + possible[0].parentClass + ".\nWould you like to exclude the sections that require it?"); popupLinkRemoved.show(); popupLinkRemoved.onYes = delegate { removeMissingLinks(possible); btnSubmit_Click(sender, e); }; popupLinkRemoved.onNo = delegate { ignoreLinks = true; btnSubmit_Click(sender, e); }; return; } possible = pickSections(finalClass, randomNumberOfSectionsFromThisClass); // After 2 seconds, just try submitting again in case there's a situation where // there's not enough random sections to pick all the links. IE: 3 linked, but only allowed 2 if (timeout.stop() > 2000) { btnSubmit_Click(sender, e); return; } //Slow down the repetitions - gives a little more randomness. Thread.Sleep(100); } totalCourses += possible.Count; foreach (ClassSection section in possible) { possibleSections.Add(section); } } if (totalCourses < 1) { InfoPopup popupNoCourses = new InfoPopup("There are 0 sections included!"); popupNoCourses.show(); } //Start counting down. counterDown = new BackgroundWorker(); counterDown.WorkerSupportsCancellation = true; counterDown.WorkerReportsProgress = false; counterDown.DoWork += new DoWorkEventHandler(decreaseTimeRemaining); counterDown.RunWorkerAsync(); //Start generating bitstrings in background bitstringworker.RunWorkerAsync("" + totalCourses); //Disable the submit button while we're generating. btnSubmit.IsEnabled = false; //Reset link ignore ignoreLinks = false; }