Я использую собственный класс Matrix в своем приложении и часто добавляю несколько матриц:
Matrix result = a + b + c + d; // a, b, c and d are also Matrices
Однако это создает промежуточную матрицу для каждой операции сложения. Поскольку это простое добавление, можно избежать промежуточных объектов и создать результат, добавив элементы всех 4 матриц одновременно. Как я могу этого добиться?
ПРИМЕЧАНИЕ. Я знаю, что могу определить несколько функций, таких как Add3Matrices(a, b, c), Add4Matrices(a, b, c, d) и т. д., Но я хочу сохранить элегантность result = a + b + c + d.





Это невозможно с использованием операторов.
В C++ можно использовать Шаблонные метапрограммы, а также здесь, используя для этого шаблоны. Однако программирование шаблона нетривиально. Я не знаю, доступен ли подобный метод в C#, вполне возможно, что нет.
Этот метод в C++ делает именно то, что вы хотите. Недостатком является то, что если что-то не так, сообщения об ошибках компилятора обычно занимают несколько страниц и их практически невозможно расшифровать.
Я подозреваю, что без таких методов вы ограничены такими функциями, как Add3Matrices.
Но для C# эта ссылка может быть именно тем, что вам нужно: Эффективное матричное программирование на C#, хотя, похоже, она работает несколько иначе, чем выражения шаблона C++.
Что-то, что, по крайней мере, позволило бы избежать боли
Matrix Add3Matrices(a,b,c) //and so on
было бы
Matrix AddMatrices(Matrix[] matrices)
Вместо передачи массива объектов Matrix вы можете использовать ключевое слово params, чтобы принять столько объектов, сколько необходимо.
Вы не можете избежать создания промежуточных объектов.
Однако вы можете использовать шаблоны выражений, как описано здесь, чтобы минимизировать их и выполнять причудливую ленивую оценку шаблонов.
На простейшем уровне шаблон выражения может быть объектом, который хранит ссылки на несколько матриц и вызывает соответствующую функцию, например Add3Matrices (), при назначении. На самом продвинутом уровне шаблоны выражений будут делать такие вещи, как вычисление минимального количества информации ленивым способом по запросу.
Вы можете ограничиться одним небольшим промежуточным звеном, используя ленивое вычисление. Что-то типа
public class LazyMatrix
{
public static implicit operator Matrix(LazyMatrix l)
{
Matrix m = new Matrix();
foreach (Matrix x in l.Pending)
{
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
m.Contents[i, j] += x.Contents[i, j];
}
return m;
}
public List<Matrix> Pending = new List<Matrix>();
}
public class Matrix
{
public int[,] Contents = { { 0, 0 }, { 0, 0 } };
public static LazyMatrix operator+(Matrix a, Matrix b)
{
LazyMatrix l = new LazyMatrix();
l.Pending.Add(a);
l.Pending.Add(b);
return l;
}
public static LazyMatrix operator+(Matrix a, LazyMatrix b)
{
b.Pending.Add(a);
return b;
}
}
class Program
{
static void Main(string[] args)
{
Matrix a = new Matrix();
Matrix b = new Matrix();
Matrix c = new Matrix();
Matrix d = new Matrix();
a.Contents[0, 0] = 1;
b.Contents[1, 0] = 4;
c.Contents[0, 1] = 9;
d.Contents[1, 1] = 16;
Matrix m = a + b + c + d;
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < 2; ++j)
{
System.Console.Write(m.Contents[i, j]);
System.Console.Write(" ");
}
System.Console.WriteLine();
}
System.Console.ReadLine();
}
}
Очень мило спасибо. Я даже вижу, как я могу лениво оценивать с помощью этого более сложные операции.
Это не самое чистое решение, но если вы знаете порядок оценки, вы можете сделать что-то вроде этого:
result = MatrixAdditionCollector() << a + b + c + d
(или то же самое с разными названиями). Затем MatrixCollector реализует + как + =, то есть начинается с 0-матрицы неопределенного размера, принимает размер после оценки первого + и складывает все вместе (или копирует первую матрицу). Это уменьшает количество промежуточных объектов до 1 (или даже до 0, если вы правильно реализуете присваивание, потому что MatrixCollector может немедленно содержать / содержать результат).
Я не совсем уверен, ужасно ли это, черт возьми, или один из лучших хаков, которые можно было бы сделать. Определенным преимуществом является то, что происходящее становится отчасти очевидным.
Моим первым решением было бы что-то в этом роде (если возможно, добавить в класс Matrix):
static Matrix AddMatrices(Matrix[] lMatrices) // or List<Matrix> lMatrices
{
// Check consistency of matrices
Matrix m = new Matrix(n, p);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
foreach (Maxtrix mat in lMatrices)
m[i, j] += mat[i, j];
return m;
}
У меня это было в классе Matrix, потому что вы можете полагаться на частные методы и свойства, которые могут быть полезны для вашей функции в случае реализации изменения матрицы (связанный список непустых узлов вместо большого двойного массива, например ).
Конечно, вы потеряете элегантность result = a + b + c + d. Но у вас будет что-то вроде result = Matrix.AddMatrices(new Matrix[] { a, b, c, d });.
Я подумал, что вы могли бы просто сделать явным желаемое поведение надстройки:
Matrix result = a;
result += b;
result += c;
result += d;
Но, как указал Дуг в комментариях к этому сообщению, этот код обрабатывается компилятором, как если бы я написал:
Matrix result = a;
result = result + b;
result = result + c;
result = result + d;
так что временные конструкции все еще создаются.
Я бы просто удалил этот ответ, но похоже, что у других может быть такое же заблуждение, поэтому рассмотрите этот контрпример.
Согласно спецификации, такой способ не влияет на временные конструкции. Невозможно обеспечить отдельную перегрузку оператора + =, поэтому он синтезируется из перегруженного оператора + с использованием временного результата. См. Раздел 14.14.2 для более подробной информации.
Могу я предложить MatrixAdder, который ведет себя так же, как StringBuilder. Вы добавляете матрицы в MatrixAdder, а затем вызываете метод ToMatrix (), который будет выполнять дополнения за вас в ленивой реализации. Это даст вам желаемый результат, может быть расширен до любого типа LazyEvaluation, но также не приведет к появлению каких-либо умных реализаций, которые могли бы сбить с толку других разработчиков кода.
У Бьярна Страуструпа есть небольшая статья под названием Абстракция, библиотеки и эффективность в C++, в которой он упоминает методы, используемые для достижения того, что вы ищете. В частности, он упоминает библиотеку Блиц ++, библиотеку для научных вычислений, которая также имеет эффективные операции с матрицами, наряду с некоторыми другими интересными библиотеками. Также я рекомендую прочитать беседа с Бьярном Страуструпом на сайте artima.com по этой теме.
Для этого есть несколько способов реализовать ленивую оценку. Но важно помнить, что не всегда ваш компилятор сможет получить лучший из них код.
Я уже сделал реализации, которые отлично работали в GCC и даже превосходили по производительности традиционные несколько для нечитаемого кода, потому что они заставляют компилятор наблюдать, что между сегментами данных нет псевдонимов (что-то трудно понять, когда массивы появляются из ниоткуда). Но некоторые из них полностью провалились в MSVC и, наоборот, в других реализациях. К сожалению, их слишком много, чтобы публиковать их здесь (не думайте, что здесь уместятся несколько тысяч строк кода).
Очень сложная библиотека с обширными встроенными знаниями в этой области - это библиотека Blitz ++ для научных вычислений.
Ответ Иэна Джи отличный, но следует подумать: ПОЧЕМУ вы хотите избегать создания временных объектов? Накладные расходы минимальны, и сборщик мусора без проблем соберет их. (да, это эффективнее не делать, но насколько ремонтопригоден полученный код?)