#region License Information /* HeuristicLab * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Problems.DataAnalysis; namespace HeuristicLab.DataPreprocessing { [Item("PreprocessingData", "Represents data used for preprocessing.")] public class TransactionalPreprocessingData : PreprocessingData, ITransactionalPreprocessingData { private class Snapshot { public IList VariableValues { get; set; } public IList VariableNames { get; set; } public IntRange TrainingPartition { get; set; } public IntRange TestPartition { get; set; } public IList Transformations { get; set; } public DataPreprocessingChangedEventType ChangedType { get; set; } public int ChangedColumn { get; set; } public int ChangedRow { get; set; } } private const int MAX_UNDO_DEPTH = 5; private readonly IList undoHistory = new List(); private readonly Stack eventStack = new Stack(); public bool IsInTransaction { get { return eventStack.Count > 0; } } public TransactionalPreprocessingData(IDataAnalysisProblemData problemData) : base(problemData) { } protected TransactionalPreprocessingData(TransactionalPreprocessingData original, Cloner cloner) : base(original, cloner) { } private void SaveSnapshot(DataPreprocessingChangedEventType changedType, int column, int row) { if (IsInTransaction) return; var currentSnapshot = new Snapshot { VariableValues = CopyVariableValues(variableValues), VariableNames = new List(variableNames), TrainingPartition = new IntRange(TrainingPartition.Start, TrainingPartition.End), TestPartition = new IntRange(TestPartition.Start, TestPartition.End), Transformations = new List(Transformations), ChangedType = changedType, ChangedColumn = column, ChangedRow = row }; if (undoHistory.Count >= MAX_UNDO_DEPTH) undoHistory.RemoveAt(0); undoHistory.Add(currentSnapshot); } #region NamedItem abstract Member Implementations public override IDeepCloneable Clone(Cloner cloner) { return new TransactionalPreprocessingData(this, cloner); } #endregion #region Overridden IPreprocessingData Members public override T GetCell(int columnIndex, int rowIndex) { return (T)variableValues[columnIndex][rowIndex]; } public override void SetCell(int columnIndex, int rowIndex, T value) { SaveSnapshot(DataPreprocessingChangedEventType.ChangeItem, columnIndex, rowIndex); for (int i = Rows; i <= rowIndex; i++) InsertRow(i); for (int i = Columns; i <= columnIndex; i++) InsertColumn(i.ToString(), i); variableValues[columnIndex][rowIndex] = value; if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.ChangeItem, columnIndex, rowIndex); } public override string GetCellAsString(int columnIndex, int rowIndex) { return variableValues[columnIndex][rowIndex].ToString(); } public override string GetVariableName(int columnIndex) { return variableNames[columnIndex]; } public override int GetColumnIndex(string variableName) { return variableNames.IndexOf(variableName); } public override bool VariableHasType(int columnIndex) { return columnIndex >= variableValues.Count || variableValues[columnIndex] is List; } [Obsolete("use the index based variant, is faster")] public override IList GetValues(string variableName, bool considerSelection) { return GetValues(GetColumnIndex(variableName), considerSelection); } public override IList GetValues(int columnIndex, bool considerSelection) { if (considerSelection) { var list = new List(); foreach (var rowIdx in selection[columnIndex]) { list.Add((T)variableValues[columnIndex][rowIdx]); } return list; } else { return (IList)variableValues[columnIndex]; } } public override void SetValues(int columnIndex, IList values) { SaveSnapshot(DataPreprocessingChangedEventType.ChangeColumn, columnIndex, -1); if (VariableHasType(columnIndex)) { variableValues[columnIndex] = (IList)values; } else { throw new ArgumentException("The datatype of column " + columnIndex + " must be of type " + variableValues[columnIndex].GetType().Name + " but was " + typeof(T).Name); } if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.ChangeColumn, columnIndex, -1); } public override bool SetValue(string value, int columnIndex, int rowIndex) { bool valid = false; if (VariableHasType(columnIndex)) { double val; valid = double.TryParse(value, out val); SetValueIfValid(columnIndex, rowIndex, valid, val); } else if (VariableHasType(columnIndex)) { valid = value != null; SetValueIfValid(columnIndex, rowIndex, valid, value); } else if (VariableHasType(columnIndex)) { DateTime date; valid = DateTime.TryParse(value, out date); SetValueIfValid(columnIndex, rowIndex, valid, date); } else { throw new ArgumentException("column " + columnIndex + " contains a non supported type."); } if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.ChangeColumn, columnIndex, -1); return valid; } public override bool Validate(string value, out string errorMessage, int columnIndex) { if (columnIndex < 0 || columnIndex > VariableNames.Count()) { throw new ArgumentOutOfRangeException("column index is out of range"); } bool valid = false; errorMessage = string.Empty; if (VariableHasType(columnIndex)) { double val; valid = double.TryParse(value, out val); if (!valid) { errorMessage = "Invalid Value (Valid Value Format: \"" + FormatPatterns.GetDoubleFormatPattern() + "\")"; } } else if (VariableHasType(columnIndex)) { valid = value != null; if (!valid) { errorMessage = "Invalid Value (string must not be null)"; } } else if (VariableHasType(columnIndex)) { DateTime date; valid = DateTime.TryParse(value, out date); if (!valid) { errorMessage = "Invalid Value (Valid Value Format: \"" + CultureInfo.CurrentCulture.DateTimeFormat + "\""; } } else { throw new ArgumentException("column " + columnIndex + " contains a non supported type."); } return valid; } private void SetValueIfValid(int columnIndex, int rowIndex, bool valid, T value) { if (valid) SetCell(columnIndex, rowIndex, value); } public override bool AreAllStringColumns(IEnumerable columnIndices) { return columnIndices.All(x => VariableHasType(x)); } public override void InsertRow(int rowIndex) { SaveSnapshot(DataPreprocessingChangedEventType.DeleteRow, -1, rowIndex); foreach (IList column in variableValues) { Type type = column.GetType().GetGenericArguments()[0]; column.Insert(rowIndex, type.IsValueType ? Activator.CreateInstance(type) : null); } if (TrainingPartition.Start <= rowIndex && rowIndex <= TrainingPartition.End) { TrainingPartition.End++; if (TrainingPartition.End <= TestPartition.Start) { TestPartition.Start++; TestPartition.End++; } } else if (TestPartition.Start <= rowIndex && rowIndex <= TestPartition.End) { TestPartition.End++; if (TestPartition.End <= TrainingPartition.Start) { TestPartition.Start++; TestPartition.End++; } } if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.AddRow, -1, rowIndex); } public override void DeleteRow(int rowIndex) { SaveSnapshot(DataPreprocessingChangedEventType.AddRow, -1, rowIndex); foreach (IList column in variableValues) { column.RemoveAt(rowIndex); } if (TrainingPartition.Start <= rowIndex && rowIndex <= TrainingPartition.End) { TrainingPartition.End--; if (TrainingPartition.End <= TestPartition.Start) { TestPartition.Start--; TestPartition.End--; } } else if (TestPartition.Start <= rowIndex && rowIndex <= TestPartition.End) { TestPartition.End--; if (TestPartition.End <= TrainingPartition.Start) { TestPartition.Start--; TestPartition.End--; } } if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.DeleteRow, -1, rowIndex); } public override void DeleteRowsWithIndices(IEnumerable rows) { SaveSnapshot(DataPreprocessingChangedEventType.AddRow, -1, -1); foreach (int rowIndex in rows.OrderByDescending(x => x)) { foreach (IList column in variableValues) { column.RemoveAt(rowIndex); } if (TrainingPartition.Start <= rowIndex && rowIndex <= TrainingPartition.End) { TrainingPartition.End--; if (TrainingPartition.End <= TestPartition.Start) { TestPartition.Start--; TestPartition.End--; } } else if (TestPartition.Start <= rowIndex && rowIndex <= TestPartition.End) { TestPartition.End--; if (TestPartition.End <= TrainingPartition.Start) { TestPartition.Start--; TestPartition.End--; } } } if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.DeleteRow, -1, -1); } public override void InsertColumn(string variableName, int columnIndex) { SaveSnapshot(DataPreprocessingChangedEventType.DeleteColumn, columnIndex, -1); variableValues.Insert(columnIndex, new List(Enumerable.Repeat(default(T), Rows))); variableNames.Insert(columnIndex, variableName); if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.AddColumn, columnIndex, -1); } public override void DeleteColumn(int columnIndex) { SaveSnapshot(DataPreprocessingChangedEventType.AddColumn, columnIndex, -1); variableValues.RemoveAt(columnIndex); variableNames.RemoveAt(columnIndex); if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.DeleteColumn, columnIndex, -1); } public override void RenameColumn(int columnIndex, string name) { SaveSnapshot(DataPreprocessingChangedEventType.ChangeColumn, columnIndex, -1); if (columnIndex < 0 || columnIndex > variableNames.Count) throw new ArgumentOutOfRangeException("columnIndex"); variableNames[columnIndex] = name; if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.ChangeColumn, -1, -1); } public override void RenameColumns(IList names) { if (names == null) throw new ArgumentNullException("names"); if (names.Count != variableNames.Count) throw new ArgumentException("number of names must match the number of columns.", "names"); SaveSnapshot(DataPreprocessingChangedEventType.ChangeColumn, -1, -1); for (int i = 0; i < names.Count; i++) variableNames[i] = names[i]; if (!IsInTransaction) OnChanged(DataPreprocessingChangedEventType.ChangeColumn, -1, -1); } public override Dataset ExportToDataset() { IList values = new List(); for (int i = 0; i < Columns; ++i) { values.Add(variableValues[i]); } var dataset = new Dataset(variableNames, values); return dataset; } public override void ClearSelection() { Selection = new Dictionary>(); } public override event EventHandler SelectionChanged; protected override void OnSelectionChanged() { var listeners = SelectionChanged; if (listeners != null) listeners(this, EventArgs.Empty); } #endregion #region TransactionalPreprocessingData members public bool IsUndoAvailable { get { return undoHistory.Count > 0; } } public void Undo() { if (IsUndoAvailable) { Snapshot previousSnapshot = undoHistory[undoHistory.Count - 1]; variableValues = previousSnapshot.VariableValues; variableNames = previousSnapshot.VariableNames; TrainingPartition = previousSnapshot.TrainingPartition; TestPartition = previousSnapshot.TestPartition; Transformations = previousSnapshot.Transformations; undoHistory.Remove(previousSnapshot); OnChanged(previousSnapshot.ChangedType, previousSnapshot.ChangedColumn, previousSnapshot.ChangedRow); } } public void InTransaction(Action action, DataPreprocessingChangedEventType type = DataPreprocessingChangedEventType.Any) { BeginTransaction(type); action(); EndTransaction(); } public void BeginTransaction(DataPreprocessingChangedEventType type) { SaveSnapshot(type, -1, -1); eventStack.Push(type); } public void EndTransaction() { if (eventStack.Count == 0) throw new InvalidOperationException("There is no open transaction that can be ended."); var @event = eventStack.Pop(); OnChanged(@event, -1, -1); } #endregion } }