public bool RemoveNonReachableRows() { if (_reachableCount == _initialCount) { return(false); } if (!_databaseCollector.MaintainObjectModel) { // Clean up all non-reachable rows GarbageCollector.Collect(_table, _refsToTable.Select((c) => c.Column), _reachableRows); } else { IDatabase database = _databaseCollector.Database; // Construct 'successor' Table with the same columns ITable successor = (ITable)Activator.CreateInstance(_table.GetType(), database, _table.Columns); // Update the database to refer to the successor table (in new object model instances returned) database.Tables[_tableName] = successor; database.GetOrBuildTables(); // Find the 'temp' copy of this table which unreachable rows were copied to ITable temp = _databaseCollector.TempDatabase.Tables[_tableName]; // Build a RowUpdater to redirect object model instances to the successor or temp tables RowUpdater updater = new RowUpdater(successor, temp); // Clean up all non-reachable rows, and fill out the updater with where to redirect them GarbageCollector.Collect(_table, _refsToTable.Select((c) => c.Column), _reachableRows, updater, _rowIndexToTempIndex); // Tell the previous table to redirect object model objects _table.Updater = updater; // Tell the successor table about the new row count (Collect removes happened after it was created) successor.SetCount(_table.Count); } return(true); }
/// <summary> /// Garbage Collect a column (or table) given a pre-filled set of rowsToKeep. /// Used both to clean up unused values in columns, and to clean up unused rows in tables. /// /// - Swap and Remove unused rows in the column itself. /// - Ensure no row is ever swapped more than once. /// - Update references in all 'refsToColumn' to new indices for all swapped rows. /// - Populate RowUpdater, if passed, with new indices for swapped and kept rows. /// - Populate RowUpdater, if passed, with new external indices for non-kept rows. /// </summary> /// <typeparam name="T">Type of indices used to refer to rows</typeparam> /// <param name="column">Column (or Table) containing rows to Garbage Collect</param> /// <param name="refsToColumn">Set of all columns pointing to 'column'; indices will be updated for all swapped rows</param> /// <param name="rowsToKeep">Booleans identifying which rows to keep</param> /// <param name="updater">Optional RowUpdater; if passed, mappings are added for every swapped row kept and removed row with the new index for the row.</param> /// <param name="rowIndexToTempIndex">Optional array providing the index in another table for each removed row; used to add mapping to RowUpdater to correctly redirect removed rows.</param> /// <returns></returns> public static bool Collect <T>(IColumn column, IEnumerable <INumberColumn <T> > refsToColumn, IReadOnlyList <bool> rowsToKeep, RowUpdater updater = null, int[] rowIndexToTempIndex = null) where T : unmanaged, IEquatable <T> { int[] replacements = null; int smallestToRemove = 0; int biggestToKeep = column.Count - 1; int removeCount = 0; // Walk backward and forward in rowsToKeep, swapping the earliest row to remove with the latest row to keep. // This logic is needed to ensure rows are never swapped more than once, so that indices to them are updated correctly. // This logic also ensures the minimum number of swaps are made. Swaps can be expensive for tables. while (smallestToRemove <= biggestToKeep) { // Walk backward, finding the first row to keep while (smallestToRemove <= biggestToKeep && rowsToKeep[biggestToKeep] == false) { // While rows already at the end are being removed, tell the updater where in the temp table they've gone updater?.AddMapping(biggestToKeep, rowIndexToTempIndex[biggestToKeep], movedToTemp: true); biggestToKeep--; removeCount++; } // Walk forward, finding the first row to be removed while (smallestToRemove < biggestToKeep && rowsToKeep[smallestToRemove] == true) { smallestToRemove++; } if (smallestToRemove >= biggestToKeep) { break; } removeCount++; // Swap these (the first row to remove with the last row to keep) column.Swap(smallestToRemove, biggestToKeep); // Tell the updater about the kept row (moved from biggestToKeep to smallestToRemove) updater?.AddMapping(biggestToKeep, smallestToRemove, movedToTemp: false); // Tell the updater about the removed row (originally at smallestToRemove, now in temp) updater?.AddMapping(smallestToRemove, rowIndexToTempIndex?[smallestToRemove] ?? -1, movedToTemp: true); // Set up a map array identifying the new row index for each previous row index if (refsToColumn != null) { if (replacements == null) { replacements = new int[column.Count]; for (int i = 0; i < replacements.Length; ++i) { replacements[i] = i; } } replacements[biggestToKeep] = smallestToRemove; replacements[smallestToRemove] = biggestToKeep; } smallestToRemove++; biggestToKeep--; } // Remove the rows (now swapped to the end) to be removed if (removeCount > 0) { column.RemoveFromEnd(removeCount); } // Update columns to refer to the updated indices if (refsToColumn != null && replacements != null) { IRemapper <T> remapper = RemapperFactory.Build <T>(); refsToColumn.ForEach((column) => column.ForEach((slice) => remapper.Remap(slice, replacements))); } return(removeCount > 0); }