Как предотвратить создание промежуточных объектов в каскадных операторах?

Я использую собственный класс 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.

Ответ Иэна Джи отличный, но следует подумать: ПОЧЕМУ вы хотите избегать создания временных объектов? Накладные расходы минимальны, и сборщик мусора без проблем соберет их. (да, это эффективнее не делать, но насколько ремонтопригоден полученный код?)

Nic Wise 30.09.2008 16:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
402
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Это невозможно с использованием операторов.

В C++ можно использовать Шаблонные метапрограммы, а также здесь, используя для этого шаблоны. Однако программирование шаблона нетривиально. Я не знаю, доступен ли подобный метод в C#, вполне возможно, что нет.

Этот метод в C++ делает именно то, что вы хотите. Недостатком является то, что если что-то не так, сообщения об ошибках компилятора обычно занимают несколько страниц и их практически невозможно расшифровать.

Я подозреваю, что без таких методов вы ограничены такими функциями, как Add3Matrices.

Но для C# эта ссылка может быть именно тем, что вам нужно: Эффективное матричное программирование на C#, хотя, похоже, она работает несколько иначе, чем выражения шаблона C++.

Что-то, что, по крайней мере, позволило бы избежать боли

Matrix Add3Matrices(a,b,c) //and so on 

было бы

Matrix AddMatrices(Matrix[] matrices)

Вместо передачи массива объектов Matrix вы можете использовать ключевое слово params, чтобы принять столько объектов, сколько необходимо.

brock.holum 22.09.2008 17:39

Вы не можете избежать создания промежуточных объектов.

Однако вы можете использовать шаблоны выражений, как описано здесь, чтобы минимизировать их и выполнять причудливую ленивую оценку шаблонов.

На простейшем уровне шаблон выражения может быть объектом, который хранит ссылки на несколько матриц и вызывает соответствующую функцию, например 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();
    }
}

Очень мило спасибо. Я даже вижу, как я могу лениво оценивать с помощью этого более сложные операции.

Ozgur Ozcitak 22.09.2008 18:39

Это не самое чистое решение, но если вы знаете порядок оценки, вы можете сделать что-то вроде этого:

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 для более подробной информации.

Doug McClean 22.09.2008 18:20

Могу я предложить MatrixAdder, который ведет себя так же, как StringBuilder. Вы добавляете матрицы в MatrixAdder, а затем вызываете метод ToMatrix (), который будет выполнять дополнения за вас в ленивой реализации. Это даст вам желаемый результат, может быть расширен до любого типа LazyEvaluation, но также не приведет к появлению каких-либо умных реализаций, которые могли бы сбить с толку других разработчиков кода.

У Бьярна Страуструпа есть небольшая статья под названием Абстракция, библиотеки и эффективность в C++, в которой он упоминает методы, используемые для достижения того, что вы ищете. В частности, он упоминает библиотеку Блиц ++, библиотеку для научных вычислений, которая также имеет эффективные операции с матрицами, наряду с некоторыми другими интересными библиотеками. Также я рекомендую прочитать беседа с Бьярном Страуструпом на сайте artima.com по этой теме.

Для этого есть несколько способов реализовать ленивую оценку. Но важно помнить, что не всегда ваш компилятор сможет получить лучший из них код.

Я уже сделал реализации, которые отлично работали в GCC и даже превосходили по производительности традиционные несколько для нечитаемого кода, потому что они заставляют компилятор наблюдать, что между сегментами данных нет псевдонимов (что-то трудно понять, когда массивы появляются из ниоткуда). Но некоторые из них полностью провалились в MSVC и, наоборот, в других реализациях. К сожалению, их слишком много, чтобы публиковать их здесь (не думайте, что здесь уместятся несколько тысяч строк кода).

Очень сложная библиотека с обширными встроенными знаниями в этой области - это библиотека Blitz ++ для научных вычислений.

Другие вопросы по теме