【问题标题】:Get all blocks of contiguous same numbers for an array获取数组的所有连续相同数字的块
【发布时间】:2021-04-08 05:19:06
【问题描述】:

我这里有一个数组

int arr[] = { 2, 6, 6, 3, -1, -1, -1, 4, -1, -1, 5, 5, -1, 7 };
//                        ^^^^^^^^^^     ^^^^^^    

我想编写一个代码来获取数组中连续 -1s 的块数,但只获取包含多个 -1 的块数。

在这个例子中,数组中有一个由三个连续的-1s 和两个连续的-1s 组成的序列,我想得到这两个序列的大小:

3 2

【问题讨论】:

  • 如果block > 0 and arr[i] != -1 push_back block 在向量中并将block 重置为0
  • 这有点棘手,但基本上你必须做两件不同的事情。首先,只有在值变为 -1 时才开始计数,然后在值从 -1 变为时停止计数。这意味着您不仅在查看当前元素,还在查看前一个元素。其次,因为可能有多个 -1 序列,因此您必须跟踪迄今为止发现的最长序列。我建议您尝试一下,您将通过自己尝试来学习最多。如果您遇到困难,请在此处发布您编写的代码并寻求帮助。
  • 在这段代码中:int arr[] = { 2, 3, -1, -1, -1, 4, -1, -1 }; 你想得到2 作为结果吗?你想得到什么结果?

标签: c++ arrays contiguous


【解决方案1】:

使用 range-v3 库,一旦您习惯了这种事情,您就可以通过一种非常易读的方式来实现它。先写一些方便的命名空间别名:

namespace rs = ranges;
namespace rv = ranges::views;

现在您需要实现的主要逻辑是确定相同数字的连续块是否有效。

auto is_valid_block = [](auto block)   // block of same numbers 
{
  return *rs::begin(block) == -1       // containing -1s
         and rs::size(block) > 1;      // and at least 2 of them
};

现在您可以将范围分组为相同数字的块,并根据您的约束过滤这些块。

for (auto block : arr                           // [ 2 6 6 3 -1 -1 -1 4 -1 -1 5 5 -1 7 ]     
                | rv::group_by(std::equal_to{}) // [ [2] [6 6] [3] [-1 -1 -1] [4] [-1 -1] [5 5] [-1] [7] ]     
                | rv::filter(is_valid_block))   // [ [-1 -1 -1] [-1 -1] ]     
    std::cout << rs::size(block) << " ";        // 3 2

这是demo

【讨论】:

    【解决方案2】:

    我想展示一个使用 C++ 语言元素的解决方案。

    我在这里看到的一切(除了 std::cout 的用法)都是纯 C 代码。很遗憾,因为 C++ 更强大,并且有很多“外壳”可用的算法。

    所以,作为第一个注释。您不应该在 C++ 中使用 C 样式数组。没有理由不使用 std::array 代替。或者,对于具有动态内存管理的用例,std::vector。使用std::vector 几乎总是更好的解决方案。

    无论如何。 C++ 也仍然适用于 C 样式的数组。在下面的示例中,我大量使用了 C++ 功能,并且仍然可以使用 C 样式数组。由于这种编程风格,我以后可以将 C-Style 数组与现代 C++ STL 容器交换,并且程序仍然可以运行。

    但是让我们先看看代码,我稍后会解释。

    #include <iostream>
    #include <iterator>
    #include <algorithm>
    #include <functional>
    
    // Test data
    int arr[] = { 2, 3, -1, -1, -1, 4, -1, -1 };
    unsigned int result[(sizeof(arr) / sizeof(arr[0]))/2];
    
    // A predicate function that checks for 2 elements to be -1
    bool twoMinusOnes(const int i, const int j) {
        return i == -1 && j == -1;
    }
    
    // Test program
    int main() {
    
        // Here we count, how many contiguos blocks of -1 are available
        unsigned int blockCounter = 0;
    
        // Some iterator to the begin and end of a -1 block in a source data
        decltype(std::begin(arr)) beginOfBlock{};
        decltype(std::begin(arr)) endOfBlock{};
    
        // Find all blocks with contiguos -1 in a simple for loop
        for (   beginOfBlock = std::adjacent_find(std::begin(arr), std::end(arr), twoMinusOnes); // Initial value. Start searching from beginning
                beginOfBlock != std::end(arr);                                                   // Check end condition 
                beginOfBlock = std::adjacent_find(endOfBlock, std::end(arr), twoMinusOnes)) {    // find next block
    
            // Ok. std::adjacent_found a block with at lease 2 repeating -1 for us
            // Now, find the position, where this block ends
            endOfBlock = std::adjacent_find(beginOfBlock, std::end(arr), std::not_equal_to<int>());
    
            // Set iterator to past the end of the contiguous -1 block (if we are not at the end)
            if (endOfBlock != std::end(arr)) std::advance(endOfBlock, 1);
    
            // Store number of elements in this block
            result[blockCounter++] = std::distance(beginOfBlock, endOfBlock);
        }
    
        // Show result to user. Number of blocks and number of elements in each block
        for (unsigned int i{}; i < blockCounter; ++i)
            std::cout << "Block " << i + 1 << " has " << result[i] << " elements\n";
    
        return 0;
    }
    

    好的,我们在这里做什么?

    首先,我们定义一个包含要检查的源数据的数组。 其次,我们还定义了一个“结果”数组,具有固定的最大大小,等于第一个数组元素数量的一半。这是因为不能有更多连续的 -1 块。

    好的,然后我们定义一个所谓的谓词函数,它只是检查两个给定元素是否都是-1。这就是我们在源数组中寻找的内容。然后可以在 C++ 标准算法中使用谓词函数。

    在 main 中,我们初始化一个 blockCounter,它将显示找到的连续 -1 块的数量。

    然后我们看到:

    decltype(std::begin(arr)) beginOfBlock{};
    decltype(std::begin(arr)) endOfBlock{};
    

    这将定义一个迭代器,与使用的 C++ STL 容器无关。对于我们的int[] 数组,它将是一个int*(所以,我们也可以写成int *beginOfBlock;

    这是一种非常灵活的方法,可以让我们以后使用不同的容器。


    接下来我们开始一个 for 循环,查找所有连续的 -1 块。

    为此,我们使用了一个专用函数,该函数旨在查找具有特定属性的相邻元素:std::adjacent:find。参考说明请见here。标准是找到相等的元素。但是我们使用我们的谓词函数来找到 2 个连续的 -1。

    所以,for-loop 的 init 语句从容器的开始开始寻找这样的块。 for 的“条件”检查是否可以找到一个块。

    “迭代表达式”在第一个块被评估后寻找下一个块。

    这是一个普通的 for 循环,相当直接。

    在循环体中,我们只有 3 条语句:

    • 搜索当前找到的-1块的结尾
    • 因为该函数将返回一对的第一个元素,它将指向找到的块的最后一个-1。我们将增加位置以指向找到的 -1 块之后的下一个元素(但前提是我们尚未位于数组的末尾)
    • 我们将生成的元素数量存储在一个块中

    就是这样。非常简单紧凑的代码。

    最后,我们在控制台上显示收集的结果。

    基本上就是这样。由于重用了标准 C++ 库中的现有函数,因此只需要几条语句。

    为了向您展示,这样的功能是多么通用,您可以 include 标头 vector 然后简单地替换您的 C 样式数组:

    // Test data
    std::vector arr{ 2, 3, -1, -1, -1, 4, -1, -1 };
    std::vector<size_t> result(arr.size()/2);
    

    并且程序将像以前一样运行。


    如果你想学习 C++,我强烈建议你开始使用 C++ 语言元素

    如有疑问,请提出。

    【讨论】:

    • 三个std::adjacent_find 调用来查找连续块?只有两个std::finddo the job
    • @Evg。总是有一百万种可能的解决方案。但是,“连续”的要求在逻辑上要求“相邻”之类的东西。而且,恕我直言,我只有 2 个电话需要 std::adjacent_find。一个找到开始,一个找到块的结尾。无论如何,如前所述,可能有大量不同的解决方案。但是,请随时添加一个额外的答案,甚至可能有类似的评论密度和解释细节。 OP 将很高兴看到更多或不同的解决方案。
    【解决方案3】:

    你可以这样做:

    template <typename IT, typename T>
    std::vector<std::size_t> count_contiguous_block(IT begin, IT end, const T& value)
    {
        std::vector<std::size_t> res;
        auto it = begin;
        do {
            it = std::find(it, end, value);
            auto endBlock = std::find_if(it, end, [](const auto& e){ return e != value; });
            if (it != endBlock) {
                res.push_back(std::distance(it, endBlock));
                it = endBlock;
            }
        } while (it != end)
        return res;
    }
    

    Demo

    【讨论】:

      【解决方案4】:
      #include <iostream>
      
      using namespace std;
      int main(int argc, char** argv) {
      
      int arr[] = { 2, 3, -1, -1, -1, 4, -1, -1 };
      int save[4]; // at the worth case we have 4 contiguous ( 4 single -1 )
      int counter_of_minus_one=0; // counter of -1 in every contiguous
      int counter_of_contiguous=0; // counter of contiguous we have 
      bool are_we_looking_for_new_contiguous=true; // this mean we are searching for new contiguous
      int n=8;
      
      for(int i=0;i<n;i++)
      {
          if(are_we_looking_for_new_contiguous==true && arr[i]==-1)  //this means the -1 we found is our first -1 in our new contiguous
          {
              counter_of_minus_one++;   // +1 for our -1 counter
              counter_of_contiguous++; // +1 for our contiguous counter
              are_we_looking_for_new_contiguous=false; // so we set flag as false (that means whenever we seen a -1 it's the countinue of current contiguous)
          }
          else if(are_we_looking_for_new_contiguous==false && arr[i]==-1) // as we said what is flag==false so if we seen -1 it's the countinue of current contiguous
          {
              counter_of_minus_one++;
          }
          else if(are_we_looking_for_new_contiguous== false && arr[i]!=-1)//if flag==false but the arr[i] isn't -1 this mean we are out of the -1's contiguous then we close this contiguous and search for a new one
          {
              save[counter_of_contiguous-1]=counter_of_minus_one; // saving count of -1 for this countiguous
              counter_of_minus_one=0; // reset the -1 counter
              are_we_looking_for_new_contiguous=true; // looking for new contiguous for next i
          }
          else if(are_we_looking_for_new_contiguous==true && arr[i]!=-1)// flag==true means we are searching for new contiguous so we just go for next element in array
          {
              // doing nothing 
          }
          
          if(i==n-1 && are_we_looking_for_new_contiguous== false && arr[i]==-1) // if the last element be -1 we should add another if seprate of others to just save the last search
          {
              save[counter_of_contiguous-1]=counter_of_minus_one; // saving count of -1 for this countiguous
          }
      }
      
      for(int i=0;i<counter_of_contiguous;i++)
      {
          cout<<save[i]<<endl; //printing result
      }
      
      return 0;}
      

      【讨论】:

      • if 中的bool 值比较是没有意义的。 if (a == true) 就是 if (a)。考虑简化此代码。现在很难理解。
      • 实际上我不是在寻找简化此代码。我写这篇文章是为了让初学者可以阅读,我选择了 varribales 的名字来展示他们的工作。就像我选择的 bool are_we_looking_for_new_contiguous 。所以在我看来,解决问题并确保用户完全理解代码是目标。
      • 问题不是变量名,而是非常复杂的逻辑。仅仅通过查看是不可能理解这段代码在做什么的。
      • 对不起,我想我听错了。回答错误:D 我尝试用代码解释并添加任何可能的方式。就像我在这里else if(are_we_looking_for_new_contiguous==true &amp;&amp; arr[i]!=-1)// flag==true means we are searching for new contiguous so we just go for next element in array { // doing nothing } 所做的一样,我们可以看到我在我的代码中添加了一个无用的部分!只是为了让初学者向他们展示我们需要考虑所有可能的方式。我还看到if ( a==true) 一种更简洁的方式来解释我想要做什么。
      【解决方案5】:

      试试下面的代码:

      int arr[] = { 2, 3, -1, -1, -1, 4, -1, -1};
      int curr_cnt = 0;    
      for(int i = 0; i < 8; i++){
          if(arr[i] == -1) curr_cnt++;
          else{
              if(curr_cnt != 0)   cout << curr_cnt << endl;
              curr_cnt = 0;
          }
      }
      if(curr_cnt != 0)   cout << curr_cnt << endl;
      

      这里我们维护一个 curr_cnt 变量来存储连续 -1 的计数。如果我们将 arr[i] 设为 -1,我们只需增加此计数,否则将此计数设置为 0,因为我们找到了其他元素。但是在将 count 更新为零之前,如果 count 大于 0,我们可以打印它的值。因此,我们将获得所有连续 -1 的计数。

      【讨论】:

      • 谢谢。每次curr_cnt 重置为 0 后,如何在不手动键入 int arr[0]==curr_cnt 的情况下自动将这些值添加到数组中?
      • @ahmadalibin 使用std::vector&lt;int&gt; 并将curr_cnt 的值推送到此答案代码中curr_cnt 输出到控制台的每个位置。
      • @WhozCraig 有没有办法自动将curr_cnt 的每个值输出到向量?因为这段代码也需要处理不同的数组。其他数组可以有不同数量的 curr_cnt 值,所以我不想以特定于该数组的方式对其进行编码。
      • 您决定在何处存储按定义定义的动态 计数器序列由您决定。向量很容易成为最简单的解决方案。
      猜你喜欢
      • 2019-04-08
      • 1970-01-01
      • 2020-02-19
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2017-09-14
      相关资源
      最近更新 更多