2

I have an array here

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

and I want to write a code that gets the number of blocks of contiguous -1s in the array but only the ones that contain more than one -1.

In this example there is a sequence of three contiguous -1s and two contiguous -1s in the array and I want to get the size of both sequences like this:

3 2

cigien
  • 57,834
  • 11
  • 73
  • 112
ahmadalibin
  • 133
  • 8
  • 2
    if `block > 0 and arr[i] != -1` push_back `block` in a vector and reset `block` to `0` – The Philomath Jan 01 '21 at 08:53
  • It's a bit tricky but basically you have to do two things different. First you only start counting when the value changes to a -1, and then you stop counting when the value changes from a -1. This means you are not just looking at the current element, you are also looking at the previous element. Secondly because there could be more than one sequence of -1s you have to keep track of the longest sequence you've found so far.I suggest you have a go, you will learn most by trying for yourself. If you get stuck then post the code you have written here and ask for help with it. – john Jan 01 '21 at 08:54
  • in this code: `int arr[] = { 2, 3, -1, -1, -1, 4, -1, -1 };` you want to get `2` as result? what do you want to get as result? – SdSaati Jan 01 '21 at 09:09

5 Answers5

2

You might do:

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

I want to show a solution using C++ language elements.

Everything that I saw here is (except the usage of std::cout) pure C-code. And that is a pity, because C++ is much more powerful and has a lot of "of-the-shell" usable algorithms.

So, as a first note. You should not use C-Style arrays in C++. There can be no argument, to not use a std::array instead. Or, for use cases with dynamic memory management, a std::vector. Using a std::vector is nearly always the better solution.

Anyway. C++ will also still work with C-Style arrays. In my example below, I made heavy use of C++ functionality and can still work with C-Style arrays. Because of this programming style, I can exchange the C-Style array with modern C++ STL containers later, and, the program will still run.

But let us first see the code, which I will explain later.

#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;
}

OK, what are we doing here?

First, we define an array with the source data that shall be examined. Second, we also define a "result" array, with a fixed maximum size, equal to half the number of elements of the first array. This, because the there cannot be more consecutive -1 blocks.

Ok, then we define a so called predicate function, which simply checks if both given elements are -1. That is what we are looking for in our source array. The predicate function can then later be used in C++ standard algorithms.

In main, we initialize a blockCounter which will show us the number of contiguous -1 blocks found.

Then we see:

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

This will define an iterator, regardless of the used C++ STL container. For our int[] array, it will be a int* (So, we could have also written int *beginOfBlock;.

This is a very flexible approach and will allow us the use different containers later.


Next we start a for loop, to find all blocks of contiguous -1s.

For this we use a dedicated function that has been designed to find adjacent elements with a certain property: std::adjacent:find. Please see here for the reference description. Standard is to find equal elements. But we use our predicate function to find 2 consecutive -1s.

So, the init statement of the for-loop, looks for such a block, starting from the beginning of the container. The "condition" of the for, checks, if a block could be found or not.

And the "iteration_expression" looks for the next block, after the first block has been evealuated.

This is a normal for loop and rather straight forward.

In the loop body, we have only 3 statements:

  • Search for the end of the current found -1 block
  • Because the function will return the first element of a pair, it will point to the last -1 of the found block. We will increment the position to point to the next element after the found -1 block (but only, if we are not yet at the end of the array)
  • We store the resulting number of elements in one block

That is all. Very simple and compact code.

At the end we show the gathered results on the console.

That is basically it. Only a few statements needed, because of the reuse of existing functions from the standard C++ library.

And to show you, how versatile such a function is, you can include the header vector and then replace your C-Style arrays simply by:

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

And the program will work as before.


I would strongly recommend to you to start using C++ language elements, if you want to learn C++

In case of questions, please ask.

A M
  • 14,694
  • 5
  • 19
  • 44
  • Three `std::adjacent_find` calls to find contiguous blocks? Just two `std::find` will [do the job](https://godbolt.org/z/x49xKr). – Evg Jan 01 '21 at 14:08
  • @Evg. There are always one million possible solutions. But, the requirement "contiguous" cries logically for something like "adjacent". And, IMHO I have only 2 calls for ````std::adjacent_find````. One to find the beginning, and one to find the end of the block. Anyway, as said, tons of different solutions possible. But, please feel free to add an additional answer, maybe even with a similar comment density and explanation detail. The OP will be happy to see more or different solutions. – A M Jan 01 '21 at 14:18
1
#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;}
Saoshyant
  • 11
  • 4
  • Comparisons with `bool` values inside `if` are meaningless. `if (a == true)` is just `if (a)`. Consider simplifying this code. Now it is hardly comprehensible. – Evg Jan 01 '21 at 10:19
  • actually i'm not looking for simplifying this code. i wrote this to be readable for begginers and i choose varribales name to show what they do. like what i choose `bool are_we_looking_for_new_contiguous` . so in my opinion solving the issue and being sure the users will understand the code prefectly is the goal. – Saoshyant Jan 01 '21 at 13:09
  • The problem is not variable names, but very convoluted logic. It is impossible to understand what this code is doing by just looking through it. – Evg Jan 01 '21 at 13:51
  • sorry i think i got your words wrong.and answer wrong :D i try to explain in code and add any possible ways. like what i do here `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 }` as we can see i add a useless part in my code! just for the people who are beginners to show them we need to consider all the possible ways. and also i see `if ( a==true)` a cleaner way for explain what i want to do. – Saoshyant Jan 01 '21 at 14:48
1

With the range-v3 library you can implement this in a way that is very readable once you're used to this kind of thing. First write some convenience namespace aliases:

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

Now the primary logic that you need to implement is deciding whether a contiguous block of same numbers is valid.

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
};

Now you can just group the range into blocks of same numbers, and filter those blocks according to your constraint.

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

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
0

Try the following code:

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;

Here we maintain a curr_cnt variable to store the count of consecutive -1s. If we get arr[i] as -1 we simply increment this count otherwise set this count to 0, as we have found some other element. But before updating count to zero we can print the value of count if it's greater than 0. Hence, we will get counts of all consecutive -1s.

Madhur Vashistha
  • 329
  • 1
  • 2
  • 7
  • Thank you. How do I automatically add those values to an array without manually keying in `int arr[0]==curr_cnt` after everytime `curr_cnt` resets to 0? – ahmadalibin Jan 01 '21 at 09:24
  • @ahmadalibin use a `std::vector` and push the value of `curr_cnt` in each place in this answers code where `curr_cnt` is output to console. – WhozCraig Jan 01 '21 at 09:42
  • @WhozCraig is there a way where it can automatically output each value of `curr_cnt` to the vector? Because this code needs to work with different arrays too. Other arrays can have different number of values for `curr_cnt` so I want to not code it in a way that's specific to this array. – ahmadalibin Jan 01 '21 at 09:56
  • Where you decide to store a by-definition *dynamic* sequence of counters is up to you. A vector is easily the simplest solution. – WhozCraig Jan 01 '21 at 10:04