Решение задачи (https://wiki.haskell.org/Ru/Problem_K).
Представьте, что это требования к первой фазе проекта. Необходимо реализовать только то, что нужно на этой фазе. Однако, известно, что планируется вторая фаза, в которой требования будут расширены следующими:
- Расширить формулы операциями над строками,
- Оптимизировать производительность для громадных таблиц.
- Для начала, чтобы можно было создавать большие таблицы, необходимо переписать эти две функции. Они переводят формат ячеек из A0 в позицию в массиве вида X,Y.
public static string GetCharFromNumber(int number)
{
return $"{(char)('A' + number)}";
}
public static int[] GetNumberFromChar(string str)
{
return new int[2] { int.Parse(string.Join("", str.Skip(1))), (int)str[0] - 65 };
}
- Сделать динамическую подгрузку таблицы из файла, путем добавления в интерфейс ISerialize новых функций направленых на это.
public IEnumerable<Row> GetFromTop(string path, int start, int count);
- Изменить способ создания таблиц. Не создавать все ячейки сразу. а добавлять их постепенно по мере нужды.
- Сделать все вычисления в ячейках асинхронными.
Для реализации операций над строками необходимо будет создать новый класс наследуя интерфейс IExpressionSolver, в котором будет реализация работы со строками.
internal interface IExpressionSolver
{
public string Calculate(string expression);
}
Пример реализации. Тут используется DataTable для вычисления выражения. Так как главная задача стояла не в написании функционала для вычисления выражения, я использовал встроеные.
internal class ExpressionSolver : IExpressionSolver
{
public string Calculate(string expression)
{
return new DataTable().Compute(expression, null).ToString();
}
}
В случаях если необходима реализация нового типа ячеек (допустим для обработки выражения со строками), нужно создать новый класс наследуя интерфейс IData.
internal interface IData
{
public string Calculate(Table table, IExpressionSolver expressionSolver = null);
public string GetString();
}
Пример реализации.
internal class DataCell : IData
{
public string Data { get; set; }
public string Calculate(Table table, IExpressionSolver expressionSolver)
{
try
{
if (CheckSelfreference(table, Data))
{
return "#ССЫЛКАНАСЕБЯ";
}
if (Data.IndexOfAny(new char[] { '+', '-', '*', '/' }) == -1)
{
var pos = Cell.GetNumberFromChar(Data);
var cell = table.Rows[pos[0]].Cells[pos[1]];
return cell.GetCalculate(table);
}
var data = Data;
var tmp = Data.Replace("+", ",").Replace("-", ",").Replace("*", ",").Replace("/", ",").Replace("=", "").Split(",");
foreach (var i in tmp)
{
try
{
var pos = Cell.GetNumberFromChar(i);
var cell = table.Rows[pos[0]].Cells[pos[1]];
data = data.Replace(i, cell.GetCalculate(table));
}
catch { }
}
return expressionSolver.Calculate(data);
}
catch { return "#НЕВЫРАЖЕНИЕ"; }
}
public string GetString()
{
return $"={Data}";
}
private bool CheckSelfreference(Table table, string data)
{
var tmp = data.Replace("+", ",").Replace("-", ",").Replace("*", ",").Replace("/", ",").Replace("=", "").Split(",");
foreach (var i in tmp)
{
Cell cell;
try
{
var pos = Cell.GetNumberFromChar(i);
cell = table.Rows[pos[0]].Cells[pos[1]];
}
catch { continue; }
if (cell.Data.GetString() == GetString())
return true;
if (CheckSelfreference(table, cell.Data.GetString()))
return true;
}
return false;
}
}
internal class DataNumber : IData
{
public string Data { get; set; }
public string Calculate(Table table, IExpressionSolver expressionSolver = null)
{
if (int.TryParse(Data, out _))
{
return Data;
}
else
return $"#НЕЧИСЛО";
}
public string GetString()
{
return $"{Data}";
}
}
internal class DataString : IData
{
public string Data = string.Empty;
public string Calculate(Table table, IExpressionSolver expressionSolver = null)
{
return Data;
}
public string GetString()
{
return $"'{Data}";
}
}
Если нужна валидация вводимых данных, можно создать класс с наследованием интерфейса IValidate.
internal interface IValidate
{
public bool Validate(string text);
}
Пример реализации
internal class SimpleValidate : IValidate
{
public bool Validate(string text)
{
return text.IndexOfAny(new char[] { ';', '\t' }) == -1;
}
}
Интерфейс ISerialize предназначен для сериализации и десериализации таблиц.
internal interface ISerialize
{
public string SerializeToText(Table table);
public Table DeserializeFromText(string text, TableSettings settings);
public void Serialize(Table table, string path);
public Table Deserialize(string path, TableSettings settings);
public Table Deserialize(Stream stream, TableSettings settings);
}
Пример реализации. Здесь сериализациия и десериализация происходит в .txt файл.
internal class SerializerTXT : ISerialize
{
public Table Deserialize(string path, TableSettings settings)
{
return DeserializeFromText(File.ReadAllText(path),settings);
}
public Table Deserialize(Stream stream, TableSettings settings)
{
byte[] array = new byte[stream.Length];
stream.Read(array, 0, array.Length);
return DeserializeFromText(Encoding.UTF8.GetString(array), settings);
}
public void Serialize(Table table, string path)
{
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
fileStream.Write(Encoding.UTF8.GetBytes(SerializeToText(table)));
}
}
public Table DeserializeFromText(string text, TableSettings settings)
{
var rows = text.Split("\n");
Table table = new Table(0, 0, new TableSettings());
table.Rows.Clear();
foreach (var row in rows)
{
Row row1 = new Row();
foreach (var col in row.Split("\t"))
{
var cel = new Cell(settings.ExpressionSolver, settings.ValidateManager);
cel.Write(col);
row1.Cells.Add(cel);
}
table.AddRow(row1);
}
return table;
}
public string SerializeToText(Table table)
{
var data = "";
foreach (var row in table.Rows)
{
var line = "";
foreach (var col in row.Cells)
{
line += $"{col.Data.GetString()}\t";
}
data += $"{line.TrimEnd('\t')}\n";
}
return data.TrimEnd('\n');
}
}
ITableManager интерфейс для создания фабрики таблиц.
internal interface ITableManager
{
public Table Create();
public Table Create(int width, int height);
public Table Create(int width, int height, TableSettings settings);
}
Пример реализации.
internal class TableManager: ITableManager
{
/// <summary>
/// создает 5 на 5 таблицу.
/// </summary>
/// <returns></returns>
public Table Create()
{
return Create(5, 5);
}
public Table Create(int width, int height)
{
return Create(width, height, new TableSettings());
}
public Table Create(int width, int height, TableSettings settings)
{
return new Table(width, height, settings);
}
}