【问题标题】:Iterate array elements in specific order按特定顺序迭代数组元素
【发布时间】:2020-06-01 08:06:02
【问题描述】:

我正在尝试以特定顺序迭代整数的一维数组/向量,但我无法绕开它来获得正确的循环条件。

输入数据是整数的一维向量:

{1, 5, 9, 13, 17, 21, 2, 6, 10, 14, 18, 22, 3, 7, 11, 15, 19, 23, 4, 8, 12, 16, 20, 24, 25, 29, 33, 37, 41, 45, 26, 30, 34, 38, 42, 46, 27, 31, 35, 39, 43, 47, 28, 32, 36, 40, 44, 48}

此输入数据本质上是具有子组的二维数组/网格的一维表示:

+----+----+----+----+----+----+
| 1  | 5  | 9  | 13 | 17 | 21 |
+----+----+----+----+----+----+
| 2  | 6  | 10 | 14 | 18 | 22 |
+----+----+----+----+----+----+
| 3  | 7  | 11 | 15 | 19 | 23 |
+----+----+----+----+----+----+
| 4  | 8  | 12 | 16 | 20 | 24 |
+----+----+----+----+----+----+
| 25 | 29 | 33 | 37 | 41 | 45 |
+----+----+----+----+----+----+
| 26 | 30 | 34 | 38 | 42 | 46 |
+----+----+----+----+----+----+
| 27 | 31 | 35 | 39 | 43 | 47 |
+----+----+----+----+----+----+
| 28 | 32 | 36 | 40 | 44 | 48 |
+----+----+----+----+----+----+

此表被分组为多个 2x4 子组:

我想按照数字指示的顺序迭代这个 1D 输入向量/数组。 (基本上是通过每个 2x4 子组,一个接一个)。 在循环内部,我想将当前处理的子组的 8 个元素加载到 vector/int 中以供进一步处理。 所以最后我应该处理 6 个子组,第一个持有数字 1 到 8,第二个持有数字 9 到 16,所以第四个.. 应该从左到右处理组。

注意:输入数据中的数字仅作为示例,以明确数据的处理顺序。输入数据可能包含完全随机的值。应保持每组中值的顺序。组内不应该有排序/重新排序。重要的是子组/数组维度等的约束。

连同一维数组/向量,我确实知道网格的尺寸和子组的大小:

int grid_width = 6; // Variable, but will always be a multiple of 2, to match subgroup width
int grid_height = 8;  // Variable, but will always be a multiple of 4, to match subgroup height
const int group_width = 2; // Constant, subgroup width is always 2.
const int group_height = 4; // Constant, subgroup height is always 4.

我的想法是有一个循环遍历整个数据,然后有两个循环相应地处理子组。但我真的很难让元素的循环条件和索引正确。我的思考过程是这样的:

// Iterate over entire data
for (int i = 0; i < grid_width * grid_height; i += (group_width * group_height))
{
    int block[8];
    // Iterate groups in correct order
    for (int j = 0; j < group_height; j++)
    {
        for (int k = 0; k < group_width; k++)
        {
            // Grab current element in group
            int value = input_array[i * grid_width + j]; // ?? This ain't right

            // Add element to group array/vector
            block[??] = ??;
        }
    }

    // Do further processing with the group array
}

有人可以帮我正确循环吗? 我希望这个问题足够清楚。如果没有,请告诉我。

【问题讨论】:

  • 您可以创建另一个数组(或向量),其中包含您想要的索引顺序。例如,如果可以像{ 0, 6, 12, ... etc } 一样开始。有了这个,您应该很快就会开始看到一种模式正在出现,这意味着您可以在运行时根据您拥有的有关矩阵和子矩阵的信息创建这个索引向量。
  • 如果您可以添加要为每个组处理的值的示例,问题会更清楚。
  • @alaniwi 提到了。第一组将包含值 1 到 8,第二组将包含值 9 到 16,第三组 17 到 24,第四组 25 到 32 等等......
  • @Kyu96 是的,但我的意思是您所说的“值 1 到 8”的示例。显然,在第一次迭代时输出值 1, 2, 3, 4, 5, 6, 7, 8、在第二次迭代时输出数字 9, 10, 11, 12, 13, 14, 15, 16 等的程序是微不足道的。
  • @alaniwi 这些数字应该清楚地表明我要处理数据的顺序。数字本身可以有任何值,并且不应该与迭代过程相关。我更新了问题,希望现在更清楚了。

标签: c++ arrays vector data-structures


【解决方案1】:

到目前为止我还没有看到一个可以理解的答案,因此我将添加一个并且还带有非常容易理解和逐步的代码。

没有cmets和解释的代码基本没有意义。

这个问题可以通过使用整数和模除法来解决,并且可能是一个训练任务。重要的是要了解我们当然需要计算指数。因此,作为第一个抽象,我展示了一个示例图片,但这里主要使用索引。请注意:在 C++ 中,索引以 0 开头。

您想要的是组,其索引如左侧所示。右侧简单显示根据给定源数据的索引。在绘制完所有水平组之后,我们只是有一个新行或新行。

重要的属性是:

  • 组宽度,
  • 组高,
  • 水平方向的组数
  • 垂直方向的组数

根据这些基本的给定源属性,我们可以简单地额外计算:

  • 每组的元素数 -> 组宽 * 组高
  • 整体元素数 -> 组宽 * 组高 * 水平方向组数 * 垂直方向组数

还有更多重要且可以轻松计算的值:例如

  • 下一行的偏移量 -> 组宽 * 水平方向的组数
  • 下一个垂直组的偏移量 -> 下一行的偏移量 * 组高
  • 生成的组数 -> 水平方向的组数 * 垂直方向的组数

这里的一切都简单易懂。现在让我们看一下这个例子。

组宽为 2,组高为 4,每组有 8 个元素。

在水平方向有 3 个组,我们观察到下一行元素的索引始终为:组宽度 * 水平方向的组数 = 2 * 3 = 6,比上一行大。

如果我们想知道任何行在一个组(高度为 4)中的行偏移量,无论组如何,那么我们可以首先计算 index % group height ,因此 index % 4. 这将导致编号顺序: 0,1,2,3,0,1,2,3,0,1,2,3 。 . .

如果我们将它与上面的 6 相乘,我们得到:0,6,12,18,0,6,12,18,0,6,12,18,...

请看图。如果我们在一组中下降一行,我们会下降 6 个索引。让我们将其称为行偏移量。

接下来我们需要检查列。如果我们查看列,我们会看到目标的前 4 个元素在第 0 列,接下来的 4 个元素在第 1 列,依此类推。所以,我们可以简单地通过整数除法计算列:索引/组高。我们得到一个序列:0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5, 5,5,6,6,6,6,7,7,7,7,8,8,8,8,...

此偏移量需要限制为每行的最大列数,在我们的示例 6 中,我们再次通过模除以先前计算的行宽来执行此操作。结果是 0,0,0,0,1,1 ,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0,1,1,1 ,1,2,2,2,2,...

我们称之为列偏移量。

如果我们将列偏移量与行偏移量相加(上面乘以 6 个值),那么我们已经得到 0,6,12,18,1,7,13,19,2,8,14,20 ,3,9,15,21, . . .

所以,这已经适用于第一个水平组。

垂直组偏移很简单。基本上偏移量是:Index-Offset-To-Next-Vertical-Group = Index-Offset-To-Next-Row * GroupHeight = 6 * 4 = 24。

而且,如果我们想要组行偏移量,那么我们只需将整数除以上述计算值(索引 / 24),然后再乘以 24。这会产生一个序列:0.. ..0,24....24,48....48,72....72,96....96,...

我们称之为垂直组偏移。

如果我们将所有偏移量相加,那么我们通过一个简单的计算就可以得到源数据中的索引。请注意:为了便于理解,我在下面显示的软件中将其拆分为描述的 3 个部分。

接下来是目标偏移量。我们需要计算组数:简单来说就是每组的索引/元素个数,很容易理解。对于正在运行的源索引,这是 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2 ,2,2,.....

而且组中的目标索引也很简单。它是每组元素的索引百分比数,因此:0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7,0,1,2, .....


现在,我们将一个大问题分解为许多更容易解决的小问题。

基本上,如果我们将所有计算的值直接放入赋值中并省略临时常量,则整个复制循环可以使用单行完成。

现在请查看完整且易于理解且注释良好的代码示例。

#include <iostream>
#include <vector>
#include <array>

int main() {

    // This you may change --------------------------------------------------------------------------------------

    // Source data
    std::vector values{1, 5, 9, 13, 17, 21, 2, 6, 10, 14, 18, 22, 3, 7, 11, 15, 19, 23, 4, 8, 12, 16, 20, 24, 25, 29, 33, 37, 41, 45, 26, 30, 34, 38, 42, 46, 27, 31, 35, 39, 43, 47, 28, 32, 36, 40, 44, 48};

    // Define the dimension of one group
    constexpr unsigned int GroupWidth =  2;
    constexpr unsigned int GroupHeight = 4;

    // Define, how many groups we have in horizontal and vertical direction
    constexpr unsigned int NumberOfGroupsHorizontal = 3;
    constexpr unsigned int NumberOfGroupsVertical   = 2;

    // Calculated values ---------------------------------------------------------------------------

    // Resulting capacity values
    constexpr unsigned int NumberOfElementsPerGroup = GroupWidth * GroupHeight;
    constexpr unsigned int NumberOfOverallElements = GroupWidth * GroupHeight * NumberOfGroupsHorizontal * NumberOfGroupsVertical;

    // Resulting offset values for indices
    constexpr unsigned int IndexOffsetToNextRow = GroupWidth * NumberOfGroupsHorizontal;
    constexpr unsigned int IndexOffsetToNextVerticalGroup = IndexOffsetToNextRow * GroupHeight;

    // How many groups do we have overall
    constexpr unsigned int NumberOfResultingGroups = NumberOfGroupsHorizontal * NumberOfGroupsVertical;

    // First make a sanity check, if parameters are ok. Handles also 0-values
    if (values.size() == NumberOfOverallElements) {

        // Copy algorithm ------------------------------------------------------------------------------

        // Here, we will store the result
        std::array<std::array<int, NumberOfElementsPerGroup>, NumberOfResultingGroups> result{};

        // Iterate over all source data
        for (unsigned int index = 0; index < NumberOfOverallElements; ++index) {


            // Calculate index in array of source data -------------------------------------------------
            // Row offset per group
            const unsigned int  o1 = (index % GroupHeight) * IndexOffsetToNextRow; 
            // Column offset
            const unsigned int  o2 = (index / GroupHeight) % IndexOffsetToNextRow; 
            // Vertical group offset
            const unsigned int  o3 = (index / IndexOffsetToNextVerticalGroup) * IndexOffsetToNextVerticalGroup; 

            // Overall calculated index / offset. (Above three lines could be put here as a one liner)
            const unsigned int sourceDataIndex = o1 + o2 + o3;

            // The target group index ------------------------------------------------------------------
            const unsigned int targetGroupIndex = index / NumberOfElementsPerGroup;

            // The index in the target group
            const unsigned int indexInTargetGroup = index % NumberOfElementsPerGroup;


            // Copy resulting value --------------------------------------------------------------------
            result[targetGroupIndex][indexInTargetGroup] = values[sourceDataIndex];
        }

        // Show result to the user
        for (const auto& a : result) {
            for (const int i : a) std::cout << i << ' ';
            std::cout << '\n';
        }
    }
    else {
        // Some problem occured
        std::cerr << "\n*** Error: Wrong source data size or parameters\n";
    }
    return 0;
}

有了以上知识,您现在可以构建一个简单的函数:

std::vector<std::vector<int>> split(const std::vector<int>& v, const size_t ngh, const size_t ngv, const size_t gw=2U, const size_t gh=4U) {

    // Define resulting 2d vector
    std::vector<std::vector<int>> result(ngh * ngv, std::vector<int>(gw * gh,0));

    // Sanity check. Will also handle parameters being 0
    if (v.size() == gw*gh*ngv*ngh)
        // Copy values in simple for loop
        for (size_t i = 0U; i < v.size(); ++i)
            result[i/(gw*gh)][i%(gw*gh)] = v[((i%gh)*gw*ngh) + ((i/gh)%(gw*ngh)) + ((i/(gh*gw*ngh))*(gh*gw*ngh))] ;

    return result;
}

【讨论】:

    【解决方案2】:

    我想这样就可以了:

    编辑:我添加了这个解释,因为到目前为止有些人无法理解这个解决方案,尽管它只包含一些简单的线性索引计算,而且我没有看到任何非 TL;DR 但完整的解决方案:强> 我们从头到尾遍历输入数据。我们总是计算我们读取的值所属的索引。我们将值存储到适当的索引中。 组是二次块。我们使用group_position_x 来存储我们在每个组内的水平位置。类似地,我们使用group_position_y 表示组内的垂直位置,从左上角开始。 我们使用col 表示我们连续在colth 组中。 类似地,我们使用row 表示我们在列中的rowth 组中。 然后我们可以很容易地计算出从输入中读取的值必须存储在哪里。 该组是所有col + [col-size] * rowth 组。 以类似的方式,我们使用组内的 x 和 y 坐标来查看刚刚读取的值必须存储在组内的哪个索引处。 请注意,索引从 0 开始。另外,请参阅下面代码中的 cmets。 col、row 的新值和组内的坐标可以说在每一步中递增。当然,我们会在每种情况下讨论对某个整数取模的增量。在某些情况下,如果满足其他索引的某些溢出条件,则会发生“+1”。

    
    using data_type = int // change type to correctly group data not being integers
    using group = std::vector<data_type>; // reprensents the values of one group
    const data_type DEFAULT{ -1 };
    
    /**
    @param data_begin begin iterator of the input data
    @param data_end end iterator of the input data
    @return a vector of all groups from top to bottom, from left to right.
    */
    template<class _Const_Iterator>
    std::vector<group> grouping(int grid_width, int grid_height, int group_width, int group_height, _Const_Iterator data_begin, _Const_Iterator data_end){
     const int GROUPS_PER_ROW{ grid_width / group_width };
     const int GROUPS_PER_COL{ grid_height / group_height };
     const int NUMBER_GROUPS{ GROUPS_PER_ROW * GROUPS_PER_COL };
     const int GROUP_SIZE{ group_width * group_height };
     std::vector<group> result(NUMBER_GROUPS, group(GROUP_SIZE, DEFAULT)); // create empty result vector with correct size.
     std::size_t group_index{ 0 }; // id of the current group
     std::size_t group_position_y{ 0 }; // currrent vertical position inside a group block
     std::size_t group_position_x{ 0 }; // current horizontal position inside a group block
     std::size_t row{ 0 }; // current row with respect to the groups
     std::size_t col{ 0 }; // current column with respect to the groups
    
     // iterate through all input data, copy data cell to target structur (result), calculate the correct indices for the next input data cell
     for (_Const_Iterator it{data_begin}; it != data_end; ++it){
      result[group_index][group_position_y + group_height * group_position_x] = *it;
    
      group_position_x = (group_position_x + 1) % group_width; // new horizontal position inside group
      auto old_col{ col };
      col = (col + (group_position_x == 0)) % GROUPS_PER_ROW; // new column with respect to the groups
      auto old_group_position_y{ group_position_y };
      group_position_y = (group_position_y  + (col == 0 && col != old_col)) % group_height; // new vertical position inside group
      row = (row + (group_position_y == 0 && group_position_y != old_group_position_y)) % GROUPS_PER_COL ; // new row with respect to the groups
      group_index = col + GROUPS_PER_ROW * row; 
     }
     return result;
    }
    

    在我个人看来,最好不要使用太多的嵌套循环,而是进行索引计算。

    【讨论】:

    • 您可以检查一下,此解决方案是否适用于您的环境?我只是把它放在这里,没有任何编译/测试。
    • 该解决方案似乎运行良好。我会用更大的输入等做一些进一步的测试。我不得不说,所有的模板和迭代器都很难阅读。
    • @Kyu96 我不确定,如果你真的想要正确地排序整数,或者它只是你解释问题的最小例子,所以我以稍微更一般的方式写了。或许可以对row、col、..x、..y等加一些解释,让大家更清楚理解。
    • 请不要在没有解释它的作用、它如何解决 OP 的问题、为什么 OP 的代码不起作用而你的代码的情况下编写仅代码的答案。请参阅how to write good answers。像这样的答案也倾向于宣传cargo cult programming,这很糟糕。
    • @Necktschnagge:不幸的是,您的回答仍然缺乏所需的信息。我仍然无法阅读代码,也无法理解您的解决方案背后的算法。出于这个原因,我添加了一个完整解释的答案,其中包含有据可查且可读的源代码。也许这有帮助。 . .
    【解决方案3】:

    这个解决方案足够简单吗?你可以在这里找到一个工作演示: https://wandbox.org/permlink/5Qv9HKzpaU9ec6gF

    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    constexpr int rowSize        = 8;
    constexpr int columnSize     = 6;
    constexpr int subGroupWidth  = 2;
    constexpr int subGroupHeigth = 4;
    
    void calculate(const vector<vector<int>>& arr, vector<vector<int>>& res)
    {
        int index = 0;
        for (int i = 0; i < rowSize; i+=subGroupHeigth)
        {
            for (int j = 0; j < columnSize; j+=subGroupWidth)
            {
                // This represents a sub-group!
                res[index].push_back(arr[i][j]);
                res[index].push_back(arr[i+1][j]);
                res[index].push_back(arr[i+2][j]);
                res[index].push_back(arr[i+3][j]);
                res[index].push_back(arr[i][j+1]);
                res[index].push_back(arr[i+1][j+1]);
                res[index].push_back(arr[i+2][j+1]);
                res[index].push_back(arr[i+3][j+1]);
                ++index;
            }
        }
    }
    
    int main()
    {
        vector<int> vec {1, 5, 9, 13, 17, 21, 2, 6, 10, 14, 18, 22, 3, 7, 11, 15, 19, 23, 4, 8, 12, 16, 20, 24, 25, 29, 33, 37, 41, 45, 26, 30, 34, 38, 42, 46, 27, 31, 35, 39, 43, 47, 28, 32, 36, 40, 44, 48};
    
        // Convert the vector to 2D array
        vector<vector<int>> arr (rowSize, std::vector<int>(columnSize));
        int row = -1;
        for (size_t i = 0; i < vec.size(); ++i)
        {
            if (i%columnSize == 0) ++row;
            arr[row][i%columnSize] = vec[i];
        }
    
        vector<vector<int>> result (columnSize);   // reserve the space for 6 arrays
        calculate(arr, result);                    // extract the 6 sub-groups in terms of arrays
    
        // Print the results
        for_each (begin(result), end(result), [](vector<int> &row){
            for (auto e : row) cout << e << " ";
            cout << endl;
        });
        return 0;
    }
    

    【讨论】:

    • vector&lt;vector&lt;int&gt;&gt; result (columnSize); 是一个错误。应该是vector&lt;vector&lt;int&gt;&gt; result ((rowSize * columnSize) / (subGroupWidth * subGroupHeigth));。仅适用于 subGroupWidth == 2 和 subGroupHeigth == 4,这没关系,因为 OP 声明这些是常量值。
    • 问题的作者可以轻松地更改此代码(实际上我希望任何人都可以使用从 StackOverflow 获得的所有代码)以适应、测试并使其更健壮。这里的目标是向他展示另一种解决方案。
    猜你喜欢
    • 1970-01-01
    • 2017-02-19
    • 1970-01-01
    • 2019-07-18
    • 1970-01-01
    • 1970-01-01
    • 2013-07-27
    • 1970-01-01
    • 2013-10-16
    相关资源
    最近更新 更多