Лучший способ хранить разреженную матрицу в .NET

У нас есть приложение, которое хранит разреженную матрицу. Эта матрица имеет элементы, которые в основном существуют вокруг главной диагонали матрицы. Мне было интересно, есть ли какие-нибудь эффективные алгоритмы (или существующие библиотеки), которые могут эффективно обрабатывать разреженные матрицы такого типа? Желательно, чтобы это была общая реализация, в которой каждая запись матрицы может быть определяемого пользователем типа.

Редактировать в ответ на вопрос / ответ:

Когда я говорю в основном вокруг главной диагонали, я имею в виду, что характеристики большинства матриц будут состоять в том, что большинство элементов кластеризуется за пределами главной диагонали, но могут быть нули рядом с диагональю, и могут быть ненулевые значения далеко от диагональ. Я хочу здесь что-нибудь эффективное для «большинства» случаев.

Для чего я буду это использовать? Мне нужно иметь эффективный доступ ко всем значениям в строке или ко всем значениям в столбце. Сохраненные значения будут логическими значениями. Примером может быть:

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

Раньше все это делалось со связанными списками, но было очень сложно реализовать. Я надеялся, что с разреженной матрицей я смогу улучшить алгоритм, но найти «правильный» тип алгоритма разреженной матрицы оказалось трудным.

p.s. Спасибо за ответы на данный момент


person Jeffrey Cameron    schedule 16.04.2009    source источник
comment
Я обновил свой ответ. Так неужели эффективность работы важнее, чем экономия места? Вы говорите «эффективный способ обработки разреженных матриц», а затем в своих сценариях использования говорите о нескольких способах доступа к данным.   -  person Erich Mirabal    schedule 16.04.2009
comment
Я бы сказал, что производительность важнее эффективности использования пространства. В любом случае мы будем обрабатывать очень большие объемы данных, поэтому я не против использовать много места для матрицы, если она работает быстрее.   -  person Jeffrey Cameron    schedule 17.04.2009


Ответы (6)


Вы можете использовать индекс, основанный на [row, col] ячейки. Поскольку данные расположены по диагонали, типичный подход к хранению индекса строки и связанных индексов столбца с данными не является оптимальным. Вот код, который вы могли бы использовать для этого:

    public class SparseMatrix<T>
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        public long Size { get; private set; }

        private Dictionary<long, T> _cells = new Dictionary<long, T>();

        public SparseMatrix(int w, int h)
        {
            this.Width = w;
            this.Height = h;
            this.Size = w * h;
        }

        public bool IsCellEmpty(int row, int col)
        {
            long index = row * Width + col;
            return _cells.ContainsKey(index);
        }

        public T this[int row, int col]
        {
            get
            {
                long index = row * Width + col;
                T result;
                _cells.TryGetValue(index, out result);
                return result;
            }
            set
            {
                long index = row * Width + col;
                _cells[index] = value;
            }
        }
    }

    static void Main()
    {
        var sm = new SparseMatrix<int>(512, 512);
        sm[42, 42] = 42;
        int val1 = sm[13, 13];
        int val2 = sm[42, 42];

        Console.WriteLine("VAL1 = " + val1); // prints out 0
        Console.WriteLine("VAL2 = " + val2); // prints out 42

        Console.ReadLine();
    }

Обратите внимание, что когда T является структурой, вам может потребоваться вызвать IsCellEmpty, поскольку получение содержимого ячейки не будет нулевым и будет иметь значение по умолчанию для этого типа. Вы также можете расширить код, чтобы получить быстрое «SparseRatio» на основе свойства Size и _cells.Count.

РЕДАКТИРОВАТЬ:

Что ж, если вам интересна скорость, вы можете найти компромисс между пространством и скоростью. Вместо одного словаря используйте три! Это втрое увеличивает ваше пространство, но делает перечисление любым удобным для вас способом. Вот новый код, который показывает, что:

    public class SparseMatrix<T>
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        public long MaxSize { get; private set; }
        public long Count { get { return _cells.Count; } }

        private Dictionary<long, T> _cells = new Dictionary<long, T>();

        private Dictionary<int, Dictionary<int, T>> _rows = 
            new Dictionary<int, Dictionary<int, T>>();

        private Dictionary<int, Dictionary<int, T>> _columns = 
            new Dictionary<int, Dictionary<int, T>>();

        public SparseMatrix(int w, int h)
        {
            this.Width = w;
            this.Height = h;
            this.MaxSize = w * h;
        }

        public bool IsCellEmpty(int row, int col)
        {
            long index = row * Width + col;
            return _cells.ContainsKey(index);
        }

        public T this[int row, int col]
        {
            get
            {
                long index = row * Width + col;
                T result;
                _cells.TryGetValue(index, out result);
                return result;
            }
            set
            {
                long index = row * Width + col;
                _cells[index] = value;

                UpdateValue(col, row, _columns, value);
                UpdateValue(row, col, _rows, value);
            }
        }

        private void UpdateValue(int index1, int index2, 
            Dictionary<int, Dictionary<int, T>> parent, T value)
        {
            Dictionary<int, T> dict;
            if (!parent.TryGetValue(index1, out dict))
            {
                parent[index2] = dict = new Dictionary<int, T>();
            }
            dict[index2] = value;
        }
    }

Если вы хотите перебрать все записи, используйте _cells. Если вы хотите, чтобы все строки для данного столбца использовали _columns. Если вы хотите, чтобы все столбцы в данной строке использовали _rows.

Если вы хотите выполнить итерацию в отсортированном порядке, вы можете начать добавлять LINQ в микс и / или использовать отсортированный список с внутренним классом, который инкапсулирует запись (который должен был бы сохранить строку или столбец и реализовать IComparable<T> для сортировки для работы ).

person Erich Mirabal    schedule 16.04.2009
comment
Спасибо, мне нравится, к чему вы клоните. Использование словарей не дает мне эффективного доступа ко всем строкам или столбцам? (возможно, используя Linq ...?). См. Мою правку выше. - person Jeffrey Cameron; 16.04.2009
comment
Смотрите обновление для другого варианта. Если пространство не является проблемой, сделайте компромисс, чтобы получить более быстрый доступ, имея несколько словарей. - person Erich Mirabal; 16.04.2009

Думаю, хватит Dictionary<int, Dictionary<int, object >>.

person Autodidact    schedule 16.04.2009

Здесь есть два вопроса:

  • «В основном по главной диагонали» слишком расплывчато. Если элементы лежат в полосах, используйте полосовое хранение самих полос, поскольку векторы смещены от главной диагонали. Если элементы разбросаны случайным образом в окрестности главной диагонали, то либо используйте полосатую форму, которая может включать в себя некоторые нули в полосах, либо используйте чистую разреженную форму, в которой хранятся только элементы и их позиции в массиве.

  • Что будешь делать с матрицей? Если ваша цель - просто эффективное хранение, тогда будет эффективна полосатая форма с быстрым доступом к любому элементу. Если вы будете выполнять линейную алгебру с матрицей, но никогда не умножаетесь больше чем на вектор matrix , тогда полосатая форма все равно будет работать великолепно. Если вы работаете с матричным умножением матриц или матричными факторизациями, где заполнение становится проблемой, тогда чистая разреженная форма может быть более подходящей. Например, произведение двух матриц с полосами будет иметь дополнительные полосы, поэтому произведение двух трехдиагональных матриц будет пятидиагональным. Для факторизации иногда может быть полезно переупорядочить, чтобы свести к минимуму заполнение. (AMD - это один из вариантов, перестановка приблизительной минимальной степени, но есть и другие схемы.)

person Community    schedule 16.04.2009

Вот список общих схем структур данных. У каждого есть свои преимущества и недостатки, и они подходят для немного разных типов задач, где возникают разреженные матрицы. Вы, вероятно, захотите реализовать их поверх существующих структур данных, таких как List ‹> и Dictionary‹>.

person Pontus Gagge    schedule 16.04.2009

Я не использовал его, но Nmath Matrix справляется с этим (не бесплатно) .

Кроме того, числовые библиотеки экстремальной оптимизации для .NET (не бесплатно).

Вот бесплатный: Math.NET Project (в частности, пространство имен MathNet.Numerics.LinearAlgebra.Sparse)

person Mitch Wheat    schedule 16.04.2009

Я думаю, что это можно сделать, используя класс, содержащий простой массив, сохраняя горизонтальное смещение, применяемое между строками матрицы и определяя полосу строки, например количество действительных записей. Таким образом, для большой матрицы, где определены только диагональ и два соседних элемента, вы должны создать массив из 3 * строк и сохранить 3 в качестве ширины полосы. Смещение зависит от размера матрицы.

Я не знаю ничего бесплатного, что уже делает это.

person grover    schedule 16.04.2009
comment
Отличная идея. Я мог бы реализовать это как таковое: предполагая только положительный ввод, мы могли бы обрабатывать отрицательные числа как количество 0 записей между записями. Итак, следующее ... [1,2, -30,0,1,2, -29] ​​заменяется на [1,2,0,0 ...] [0,1,2,0 ...] To offset, array [m * row + column] - это (строка, столбец) матрицы mxn - person Stefan Kendall; 16.04.2009