【问题标题】:how to count nonzero elements in a matrix?如何计算矩阵中的非零元素?
【发布时间】:2019-04-17 09:36:15
【问题描述】:

我有一个包含两个值 (0,1) 的矩阵,我必须计算这个矩阵中“1”的数量,所以我尝试检查所有元素,但是对于 [1000,1000] 矩阵,它需要的时间太长,另一个问题是,我应该为不同的矩阵多次这样做,所以我希望任何人都可以帮助我更快的模式。

这是我的代码:

for (int i = 0; i < matrix.height; i++)
{
    for (int j = 0; j < matrix.width; j++)
    {
        if (matrix[j, i] == 1)
        {
            count++;
        }
    }
}

【问题讨论】:

  • matrix 是您的自定义类的实例吗?如果是这样,请edit您的问题并包括该类定义
  • 如果 1 的数量远低于 0 的数量(反之亦然),您可以使用 Sparse Matrix
  • 这是作为大学项目完成的。使用链接对象创建了一个稀疏矩阵。见维基:en.wikipedia.org/wiki/Sparse_matrix

标签: c# .net matrix


【解决方案1】:

如果你自己实现矩阵类,你实际上有多种选择:

public class BoleanMatrix
{
     public bool this[int i, int j] {get;set;}
}
  1. 缓存它。这简单。在进行任何修改时,只需更新高位的缓存值。实施无关紧要。

    public class BoleanMatrix
    {
        private int _highBitCount = 0;
        public bool this[int i, int j] 
        {
            get;
            set
            {
                if(prev != value)
                { 
                    if(value)
                        _highBitCount++;
                    else
                        _highBitCount--;
                }
                //set here
             }
        }
    }
    
  2. 将实现更改为任何稀疏变体,例如,您可以将矩阵值作为位存储在 byte[] 数组中。如果它仍然太多 - 使用运行长度编码压缩它。它具有诸如这些矩阵的更新和分布问题之类的缺点,并且通常它们比内存宽矩阵慢得多。高效的算法很大程度上取决于矩阵的性质(值的分布)以及使用它们的方式(乘法、除法、减法等)。

【讨论】:

    【解决方案2】:

    尝试并行 for 循环?如果无法缓存。

            object mylock = new object();
            int count = 0;
    
            Parallel.For(0, matrix.height, i =>
            {
                int forcount = 0;
    
                for (int j = 0; j < matrix.width; j++)
                {
                    if (matrix[j, i] == 1)
                    {
                        forcount++;
                    }
                }
    
                lock (mylock)
                {
                    count += forcount;
                }
            }
            );
    

    【讨论】:

      【解决方案3】:

      鉴于您只存储位,您可以通过将位存储到 uint 值中来提高存储使用率,与为每个值使用 int 相比,这将减少 32 倍所需的空间量.

      如果您这样做,那么您还可以使用多种不同的"Hamming Weight" 算法之一更有效地计算设置位的数量。

      这种方法的缺点是使用数组BitMatrix 索引器访问单个位可能会更慢,但设置位数量的计算要快得多(RELEASE 模式建立在我的电脑)。

      这是示例代码;重要的类是BitMatrix:

      using System;
      using System.Diagnostics;
      
      namespace Demo
      {
          class Program
          {
              static void Main()
              {
                  int[,] matrix = new int[1000, 1000];
                  BitMatrix bitMatrix = new BitMatrix(1000, 1000);
      
                  // Randomly populate matrices and calculate expected count.
      
                  var rng = new Random(985912);
                  int expected = 0;
      
                  for (int r = 0; r < 1000; ++r)
                  {
                      for (int c = 0; c < 1000; ++c)
                      {
                          if ((rng.Next() & 1) == 0)
                              continue;
      
                          ++expected;
                          matrix[r, c]    = 1;
                          bitMatrix[r, c] = true;
                      }
                  }
      
                  Console.WriteLine("Expected = " + expected);
      
                  // Time the explicit matrix loop.
      
                  var sw = Stopwatch.StartNew();
      
                  for (int i = 0; i < 1000; ++i)
                      if (count1(matrix) != expected)
                          Console.WriteLine("count1() failed");
      
                  var elapsed1 = sw.ElapsedTicks;
                  Console.WriteLine(sw.Elapsed);
      
                  // Time the hamming weight approach.
      
                  sw.Restart();
      
                  for (int i = 0; i < 1000; ++i)
                      if (bitMatrix.NumSetBits() != expected)
                          Console.WriteLine("NumSetBits() failed");
      
                  var elapsed2 = sw.ElapsedTicks;
                  Console.WriteLine(sw.Elapsed);
      
                  Console.WriteLine("BitMatrix matrix is " + elapsed1 / elapsed2 + " times faster");
              }
      
              static int count1(int[,] matrix)
              {
                  int h = 1 + matrix.GetUpperBound(0);
                  int w = 1 + matrix.GetUpperBound(1);
      
                  int c = 0;
      
                  for (int i = 0; i < h; ++i)
                      for (int j = 0; j < w; ++j)
                          if (matrix[i, j] == 1)
                              ++c;
      
                  return c;
              }
          }
      
          public sealed class BitMatrix
          {
              public BitMatrix(int rows, int cols)
              {
                  Rows = rows;
                  Cols = cols;
                  bits = new uint[(rows*cols+31)/32];
              }
      
              public int Rows { get; }
              public int Cols { get; }
      
              public int NumSetBits()
              {
                  int count = 0;
      
                  foreach (uint i in bits)
                      count += hammingWeight(i);
      
                  return count;
              }
      
              public bool this[int row, int col]
              {
                  get
                  {
                      int n = row * Cols + col;
                      int i = n / 32;
                      int j = n % 32;
      
                      uint m = 1u << j;
      
                      return (bits[i] & m) != 0;
                  }
      
                  set
                  {
                      int n = row * Cols + col;
                      int i = n / 32;
                      int j = n % 32;
      
                      uint m = 1u << j;
      
                      if (value)
                          bits[i] |= m;
                      else
                          bits[i] &= ~m;
                  }
              }
      
              static int hammingWeight(uint i)
              {
                  i = i - ((i >> 1) & 0x55555555);
                  i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
                  return (int)((((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
              }
      
              readonly uint[] bits;
          }
      }
      

      如果您正在运行 64 位代码,那么使用 ulong 的数组并计算 64 位汉明权重实际上效率更高。

      当我在我的 PC 上尝试时,它的速度提高了 120 倍以上。

      这是BitMatrix 的 64 位版本:

      public sealed class BitMatrix
      {
          public BitMatrix(int rows, int cols)
          {
              Rows = rows;
              Cols = cols;
              bits = new ulong[(rows*cols+63)/64];
          }
      
          public int Rows { get; }
          public int Cols { get; }
      
          public int NumSetBits()
          {
              int count = 0;
      
              foreach (ulong i in bits)
                  count += hammingWeight(i);
      
              return count;
          }
      
          public bool this[int row, int col]
          {
              get
              {
                  int n = row * Cols + col;
                  int i = n / 64;
                  int j = n % 64;
      
                  ulong m = 1ul << j;
      
                  return (bits[i] & m) != 0;
              }
      
              set
              {
                  int n = row * Cols + col;
                  int i = n / 64;
                  int j = n % 64;
      
                  ulong m = 1ul << j;
      
                  if (value)
                      bits[i] |= m;
                  else
                      bits[i] &= ~m;
              }
          }
      
          static int hammingWeight(ulong i)
          {
              i = i - ((i >> 1) & 0x5555555555555555UL);
              i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL);
              return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56);
          }
      
          readonly ulong[] bits;
      }
      

      观察:在NumSetBits() 的循环中使用for() 而不是foreach 会稍微快一些,例如:

      public int NumSetBits()
      {
          int count = 0;
      
          for (var index = 0; index < bits.Length; index++)
              count += hammingWeight(bits[index]);
      
          return count;
      }
      

      在我的 PC 上,这会将性能从 120 倍提高到 130 倍。

      最后:如果你想利用多线程,你可以这样做(注意使用Partitioner - 这是为了增加每个线程计算的数据块大小,使其更高效):

      public int NumSetBits()
      {
          int count = 0;
          var partitioner = Partitioner.Create(0, bits.Length);
      
          Parallel.ForEach(partitioner, (range, loopState) =>
          {
              int subtotal = 0;
      
              for (int i = range.Item1; i < range.Item2; ++i)
              {
                  subtotal += hammingWeight(bits[i]);
              }
      
              Interlocked.Add(ref count, subtotal);
          });
      
          return count;
      }
      

      有了这个变化,汉明方法快了近 200 倍(对于 2000x2000 矩阵来说快了近 300 倍),但请注意,它的速度快多少取决于设置的 1 位的比例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多