【问题标题】:Having a hard time figuring out logic behind array manipulation很难弄清楚数组操作背后的逻辑
【发布时间】:2021-12-30 13:43:30
【问题描述】:

我得到了一个大小为WxH 的填充数组,需要通过将宽度和高度都缩放2 的幂来创建一个新数组。例如,当缩放4 时,2x3 变为8x12,2^2。我的目标是确保数组中的所有旧值都放在新数组中,以便旧数组中的 1 个值填充缩放数组中的多个新对应部分。例如:

old_array = [[1,2],
             [3,4]]

变成

new_array = [[1,1,2,2],
             [1,1,2,2],
             [3,3,4,4],
             [3,3,4,4]]

当按 2 倍缩放时。有人可以向我解释我将如何进行编程的逻辑吗?

【问题讨论】:

  • 这个问题不适合堆栈溢出。但是,您是否正在寻找一种在时间上有效或不需要另一个缓冲区数组的解决方案?您想要一段易于阅读和理解的代码,还是优化后不太透明的版本?
  • @Tudor 我不介意有正确的代码来解决我的问题。我将自己阅读它以理解它。谢谢!
  • 这里有一个提示。查看运算符 /% 的工作原理。然后想想如何使用它们。

标签: c++ arrays


【解决方案1】:

其实很简单。为简单起见,我使用向量向量,注意二维矩阵效率不高。但是,任何使用[] 索引语法的二维矩阵类都可以而且应该被替换。

#include <vector>
using std::vector;

int main()
{
    vector<vector<int>> vin{ {1,2},{3,4},{5,6} };
    size_t scaleW = 2;
    size_t scaleH = 3;
    vector<vector<int>> vout(scaleH * vin.size(), vector<int>(scaleW * vin[0].size()));
    for (size_t i = 0; i < vout.size(); i++)
        for (size_t ii = 0; ii < vout[0].size(); ii++)
            vout[i][ii] = vin[i / scaleH][ii / scaleW];

    auto x = vout[8][3];        // last element s/b 6

}

【讨论】:

  • 在这种情况下,除了缓存之外,向量是否比矩阵更受欢迎? (我的意思是,为了代码的可读性,这显然是为什么)。
  • 非常感谢!我很欣赏发布的所有解决方案,但到目前为止,这是最容易遵循的。谢谢大家,尤其是@doug
  • @Tudor C++ 中没有“矩阵”类型。
【解决方案2】:

这是我的看法。它与@Tudor 非常相似,但我认为介于两者之间,你可以选择你喜欢或最了解的。

首先,让我们定义一个合适的二维数组类型,因为 C++ 的标准库在这方面非常缺乏。我将自己限制在一个相当简单的结构中,以防您对面向对象编程感到不舒服。

#include <vector>
// using std::vector

struct Array2d
{
  unsigned rows, cols;
  std::vector<int> data;
};

这个打印函数应该让您了解索引是如何工作的:

#include <cstdio>
// using std::putchar, std::printf, std::fputs

void print(const Array2d& arr)
{
  std::putchar('[');
  for(std::size_t row = 0; row < arr.rows; ++row) {
    std::putchar('[');
    for(std::size_t col = 0; col < arr.cols; ++col)
      std::printf("%d, ", arr.data[row * arr.cols + col]);
    std::fputs("]\n ", stdout);
  }
  std::fputs("]\n", stdout);
}

现在说到重点,数组缩放。嵌套的数量……很麻烦。

Array2d scale(const Array2d& in, unsigned rowfactor, unsigned colfactor)
{
  Array2d out;
  out.rows = in.rows * rowfactor;
  out.cols = in.cols * colfactor;
  out.data.resize(std::size_t(out.rows) * out.cols);
  for(std::size_t inrow = 0; inrow < in.rows; ++inrow) {
    for(unsigned rowoff = 0; rowoff < rowfactor; ++rowoff) {
      std::size_t outrow = inrow * rowfactor + rowoff;
      for(std::size_t incol = 0; incol < in.cols; ++incol) {
        std::size_t in_idx = inrow * in.cols + incol;
        int inval = in.data[in_idx];
        for(unsigned coloff = 0; coloff < colfactor; ++coloff) {
          std::size_t outcol = incol * colfactor + coloff;
          std::size_t out_idx = outrow * out.cols + outcol;
          out.data[out_idx] = inval;
        }
      }
    }
  }
  return out;
}

让我们一起来做一个小演示:

int main()
{
  Array2d in;
  in.rows = 2;
  in.cols = 3;
  in.data.resize(in.rows * in.cols);
  for(std::size_t i = 0; i < in.rows * in.cols; ++i)
    in.data[i] = static_cast<int>(i);
  print(in);
  print(scale(in, 3, 2));
}

打印出来

[[0, 1, 2, ]
 [3, 4, 5, ]
 ]
[[0, 0, 1, 1, 2, 2, ]
 [0, 0, 1, 1, 2, 2, ]
 [0, 0, 1, 1, 2, 2, ]
 [3, 3, 4, 4, 5, 5, ]
 [3, 3, 4, 4, 5, 5, ]
 [3, 3, 4, 4, 5, 5, ]
 ]

【讨论】:

    【解决方案3】:

    说实话,我的算法非常糟糕,但我试了一下。
    我不确定这是否可以仅使用一个矩阵来完成,或者是否可以在更短的时间复杂度内完成。
    编辑:您可以使用W*H*S*S 估计这将进行的操作数,其中S 是比例因子,W 是宽度,H 是输入矩阵的高度。

    我使用了 2 个矩阵 mr,其中 m 是您的输入,r 是您的结果/输出。所需要做的就是从m 的位置[i][j] 复制每个元素,并将其转换为在r 内具有相同大小值scale_factor 的元素的正方形。

    简单地说:

    int main()
    {
        Matrix<int> m(2, 2);
    
        // initial values in your example
        m[0][0] = 1;
        m[0][1] = 2;
        m[1][0] = 3;
        m[1][1] = 4;
        m.Print();
    
        // pick some scale factor and create the new matrix
        unsigned long scale = 2;
        Matrix<int> r(m.rows*scale, m.columns*scale);
    
        // i know this is bad but it is the most
        //  straightforward way of doing this
        // it is also the only way i can think of :(
        for(unsigned long i1 = 0; i1 < m.rows; i1++)
            for(unsigned long j1 = 0; j1 < m.columns; j1++)
                for(unsigned long i2 = i1*scale; i2 < (i1+1)*scale; i2++)
                    for(unsigned long j2 = j1*scale; j2 < (j1+1)*scale; j2++)
                        r[i2][j2] = m[i1][j1];
    
        // the output in your example
        std::cout << "\n\n";
        r.Print();
    
        return 0;
    }
    

    我认为这与问题无关,但我使用了 Matrix 类来存储扩展矩阵的所有元素。我知道这会分散注意力,但这仍然是C++,我们必须管理内存。如果scale_factor 很大,那么您尝试使用此算法实现的目标需要大量内存,因此我使用以下方法将其包裹起来:

    template <typename type_t>
    class Matrix
    {
        private:
            type_t** Data;
        public:
            // should be private and have Getters but
            //  that would make the code larger...
            unsigned long rows;
            unsigned long columns;
    
            // 2d Arrays get big pretty fast with what you are
            //  trying to do.
            Matrix(unsigned long rows, unsigned long columns)
            {
                this->rows = rows;
                this->columns = columns;
    
                Data = new type_t*[rows];
                for(unsigned long i = 0; i < rows; i++)
                    Data[i] = new type_t[columns];
            }
    
            // It is true, a copy constructor is needed
            //  as HolyBlackCat pointed out
            Matrix(const Matrix& m)
            {
                rows = m.rows;
                columns = m.columns;
    
                Data = new type_t*[rows];
                for(unsigned long i = 0; i < rows; i++)
                {
                    Data[i] = new type_t[columns];
                    for(unsigned long j = 0; j < columns; j++)
                        Data[i][j] = m[i][j];
                }
            }
    
            ~Matrix()
            {
                for(unsigned long i = 0; i < rows; i++)
                    delete [] Data[i];
                delete [] Data;
            }
    
            void Print()
            {
                for(unsigned long i = 0; i < rows; i++)
                {
                    for(unsigned long j = 0; j < columns; j++)
                        std::cout << Data[i][j] << " ";
                    std::cout << "\n";
                }
            }
    
            type_t* operator [] (unsigned long row)
            {
                return Data[row];
            }
    };
    

    【讨论】:

    • 您的课程违反了the rule of three(即,如果您尝试复制它,将会在您的脸上爆炸)。 “这让人分心,但这仍然是 C++,我们必须管理内存” 如果您使用 std::vector(它也可以正确处理复制),则不会。
    • 您不应该进行自己的低级内存管理。不要写this-&gt; 供会员访问!
    • @JDługosz 为什么不使用this?有害吗?只需明确区分参数rows 和成员变量rows
    • 关于编辑:“确实,需要一个拷贝构造函数”也是拷贝赋值。更好的是,还有一个移动构造函数和移动赋值。或者你可以忘记这一切,使用std::vector...
    • @HolyBlackCat 这个类是为了适应这个问题,而不是所有使用矩阵的编程情况。为我写的 sn-p 中没有发生的某些情况添加数千段代码是没有意义的。
    【解决方案4】:

    首先,假定有一个合适的二维矩阵类,但这不是问题。但是我不知道你的API,所以我会用典型的东西来说明:

    struct coord {
        size_t x;  // x position or column count
        size_t y;  // y position or row count
    };
    
    template <typename T>
    class Matrix2D {
           ⋮  // implementation details
    public:
           ⋮  // all needed special members (ctors dtor, assignment)
        Matrix2D (coord dimensions);
    
        coord dimensions() const;  // return height and width
        const T& cell (coord position) const;  // read-only access
        T& cell (coord position);  // read-write access
        // handy synonym:
        const T& operator[](coord position) const { return cell(position); }
        T& operator[](coord position) { return cell(position); }
    };
    

    我刚刚展示了我需要的公共成员:创建一个给定大小的矩阵、查询大小以及对各个元素的索引访问。

    因此,鉴于此,您的问题描述是:

    template<typename T>
    Matrix2D<T> scale_pow2 (const Matrix2D& input, size_t pow)
    {
        const auto scale_factor= 1 << pow;
        const auto size_in = input.dimensions();
        Matrix2D<T> result ({size_in.x*scale_factor,size_in.y*scale_factor});
             ⋮ 
             ⋮  // fill up result
             ⋮ 
        return result;
    }
    

    好的,现在问题已经精确定义了:上面的大空白处是什么代码?

    输入中的每个单元格都被放入输出中的一堆单元格中。因此,您可以遍历输入并在输出中写入一组具有相同值的单元格,或者您可以遍历输出并在输入中查找您需要其值的每个单元格。

    后者更简单,因为您不需要嵌套循环(或一对循环)来编写丛集。

        for (coord outpos : /* ?? every cell of the output ?? */) {
            coord frompos {
                outpos.x >> pow,
                outpos.y >> pow };
            result[outpos] = input[frompos];
        }
    

    现在很简单!
    计算给定输出的 from 位置必须与定义比例的方式相匹配:您将有 pow 位给出相对于该块的位置,而较高的位将是该位置的索引丛来自

    现在,我们要将outpos 设置为输出矩阵索引中的每个合法位置。这就是我需要的。如何真正做到这一点是另一个子问题,可以通过 自上而下的分解来解决。

    更高级一点

    也许嵌套循环是完成此任务的最简单方法,但我不会将它们直接放入此代码中,从而将我的嵌套级别推得更深。并且循环 0..max 不是用没有库的裸 C++ 编写的最简单的事情,所以这只会让人分心。而且,如果您正在使用矩阵,这是您普遍需要的东西,包括(比如说)打印出答案!

    所以这是双循环,放入自己的代码中:

        struct all_positions {
            coord current {0,0};
            coord end;
            all_positions (coord end) : end{end} {}
            bool next() {
                if (++current.x < end.x)  return true; // not reached the end yet
                current.x = 0;  // reset to the start of the row
                if (++current.y < end.y)  return true;
                return false;  // I don't have a valid position now.
                }
        };
    

    遵循可以在基于范围的for 循环中使用的迭代器/集合 API。有关如何执行此操作的信息,请参阅 my article on Code Project 或使用 C++20 标准库中的 Ranges 内容。

    鉴于这个“老式”迭代助手,我可以将循环编写为:

        all_positions scanner {output.dimensions};  // starts at {0,0}
        const auto& outpos= scanner.current;
        do {
               ⋮ 
           } while (scanner.next());
    

    由于实现简单,从 {0,0} 开始,同时推进也进行测试,不能推进时返回false。因此,您必须声明它(给出第一个单元格),使用它,然后进行高级和测试。也就是说,一个测试在末端的循环。 C++ 中的for 循环在每次使用之前检查条件之前,并在最后使用不同的函数前进。因此,使其与for 循环兼容需要更多工作,而令人惊讶的是,使其与 ranged-for 兼容并没有更多工作。分离测试,以正确的方式推进,才是真正的工作;剩下的只是命名约定。

    只要这是“自定义”,您可以根据需要进一步修改它。例如,在内部添加一个标志来告诉您该行何时更改,或者它是一行的第一行或最后一行,以便于进行漂亮的打印。

    总结

    除了您真正想要编写的一小段代码之外,您还需要大量工作。在这里,它是一个可用的 Matrix 类。很多时候,它会提示输入、打开文件、处理命令行选项等等。它分散了真正问题的注意力,所以首先解决这个问题

    你的代码(你得到的真正代码)写在它自己的函数中,与你为了容纳它而需要的任何其他东西分开。如果可以的话,到别处去;这不是课程的一部分,只是分散注意力。更糟糕的是,这可能是您没有准备好(或做得好)的“困难”,因为它与正在学习的实际课程无关。

    在将算法转换为您正在使用的对象的合法语法和 API 之前,以一般方式找出算法(流程图、伪代码等)。如果你只是在学习 C++,当你试图弄清楚逻辑时,不要陷入正式的语法中。除非您在进行此类规划时自然地开始考虑in C++,否则不要强迫它。使用白板涂鸦、修补玩具,任何适合你的东西。

    在您花时间编码之前,从您的同行和导师(如果有)那里获得对想法的反馈和审查,以及如何实现它的逻辑。为什么要写一个行不通的想法?修复逻辑,而不是代码

    最后,画出你需要的控制流、函数和数据结构。使用伪代码和占位符注释。

    然后填写占位符并将替换为合法语法。您已经计划好了,所以现在您可以专注于学习编程语言的语法和库细节。您可以专注于“我如何在 C++ 中表达(一些微小的细节)”,而不是将整个程序留在脑海中。更一般地说,隔离你将要学习的部分;学习/练习一件事,而不用担心整个大厦。

    在很大程度上,其中一些想法也会转化为代码。 自上而下的设计意味着您在高层次上陈述事物,然后在别处单独实施。它使代码具有可读性和可维护性,并且首先更容易编写。函数应该这样写:函数解释了如何做(它做了什么)作为一个细节列表,这些细节只是更进一步的细节层次。然后,这些步骤中的每一个都成为一个新功能。函数应该简短,并在一个抽象的语义级别上表达。 不要深入了解函数中最原始的细节,这些细节将任务解释为一组更简单的步骤。

    祝你好运,继续努力!

    【讨论】:

      猜你喜欢
      • 2016-05-01
      • 1970-01-01
      • 2016-07-05
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多