1

I made this code to solve this problem:

"For a give number (NUM), find the number of ordered sequences of numbers out of {1,2,3,5,7,11,14,23} so that the sum of the numbers in the sequence equals to the given number. (1. The order that each number appears in the sequence matters - counted as different sequences, 2. Same numbers are allowed to appear more than once in a given sequence)"

The original problem is a bit more complicated but I simplified to ask this question, as I get the same error message anyway.

My code is:

#include <thread>
#include <vector>
#define DIM     8
#define NUM     30   // This can be 300 for DIM of 16

long val[8] = {1,2,3,5,7,11,14,23}; // This is for DIM of 8
// Example for DIM of 16:
// long val[16] = {1,2,3,5,7,11,14,23,34,45,67,88,142,301,431,547};

int main()
{
        void sub(long, long*, int);
        long sum;

        sub(NUM, &sum, 0);
        printf("%d\n", sum);
}

void sub(long in, long *sum, int level)
{
        if(in < 0)
                *sum = 0;
        else if(in == 0)
                *sum = 1;
        else
        {
                std::vector<std::thread> th;
                th.reserve(DIM);
                int i;
                long psum[DIM];

                *sum = 0;
                for(i=0; i<DIM; i++)
                        th[i] = std::thread(sub, in - val[i], &psum[i], level+1);
                for(i=0; i<DIM; i++)
                        th[i].join();
                for(i=0; i<DIM; i++)
                        *sum += psum[i];
        }
}

But I get this error message:

terminate called without an active exception
Abort(coredump)

or sometimes

terminate called without an active exception
terminate called recursively
Abort(coredump)

I searched and found some questions relative to this error message including this that has exactly same subject to my question. However, I am wondering why I get this although I did join the threads (I think). I should also say that I am almost a dummy to the multi-thread programming. This is my first multi-thread code. I tried a non-multi-thread version of the code but it took forever to finish for DIM > 15.

Additional info are:

  1. For a smaller NUM (e.g. 3), the code finishes with a correction answer (i.e. 4).
  2. My g++ version seems to be 8.5.0
  3. I compiled as 'g++ -pthread file.cpp'
  4. I tried to clear() or std::terminate() or detach(), etc., but didn't get any luck.

Thanks for any help.


I changed the subject from asking for error fix into inclusion of suggestion for better algorithm, as I realized that what I need is probably a better algorithm to reduce the run time. (The example of DIM=16 and NUM=300 is still running for more than an hour. The original problem is DIM=20...).

Kay K.
  • 127
  • 8
  • 4
    The shown code suffers from multiple fundamental design flaws. A good C++ textbook explains every key, fundamental concept of multithreading and all other advanced C++ topics one step at a time as part of a structured curriculum, introducing each concept in logical order, and after explaining each one introduce the next harder one, one step at a time. Attempting to learn C++ by doing random coding puzzles, and keyword searches, always end in tears, such as the case here, with the resulting program forkbombing itself (well, threadbombing, I suppose...) – Sam Varshavchik Jul 03 '23 at 23:09
  • @SamVarshavchik I agree and thanks for your suggestions. BTW, I am not actually in the middle of trying to study programming. I had some question to solve for my field. I've been pretty happy with C for my work for the past 25+ years, but encountered this problem and was thinking this approach. The quiz I showed is a simplified version of the original problem that shows the same error message. – Kay K. Jul 03 '23 at 23:13
  • 1
    I killed the whole thing after it spawned about 20000 threads in gdb (a good C++ textbook will also teach debugging techniques, btw). Note that each recursive call creates 8 execution threads. `NUM` is 30. The first value in the array is 1. So, it will take 30 recursive calls to reach 30, using `1` as the starting point. So, this is going to create 240 execution threads, right off the bat. You can start crunching some numbers, for the rest, to see things spiral downhill, pretty quickly.... – Sam Varshavchik Jul 03 '23 at 23:17
  • Given that it took ages for the larger example, just how many threads would this explode into, as (apart from the corner cases) spawn 15 threads for each thread... – Mikael Öhman Jul 03 '23 at 23:18
  • I see. I was thinking that the too many threads might be the problem here. Is there any way to make the program wait until it can allocate necessary resources to create a new thread (if it runs out of resources)...? – Kay K. Jul 03 '23 at 23:22
  • 3
    Recursion *and* threads? I'd go with one of those, not both. – tadman Jul 03 '23 at 23:35
  • I think that you should use a better algorithm instead... I think that you are trying way too much options at each level. Instead of testing 8! cases, you are testing 8^8 cases (for a DIM of 8) which is like 400 times more operations. If DIM is even greater, it become much worst rapidly. – Phil1970 Jul 04 '23 at 01:12
  • 3
    Using a simple recursive algorithm `int process(int sum, int index)` where `sum` is the cumulative sum at that point and `index` is which number I want to possibly add, I can easily get an answer of 5 with your sample data in no time at all. And with 16 as DIM, I can get the sum in the order of at most a few milliseconds in DEBUG build on a old laptop. – Phil1970 Jul 04 '23 at 01:42
  • @Phil1970 Thanks for the suggestion. I'll try to do the way you described. – Kay K. Jul 04 '23 at 01:54
  • @Phil1970 I'm afraid I think that you may have misunderstood the problem. The answer for the NUM=30 DIM=8 is 131292855. – Kay K. Jul 04 '23 at 02:42
  • As an example, for NUM=5, DIM=8, the answer is 14 = n({11111, 1112, 1121, 1211, 2111, 221, 212, 122, 113, 131, 311, 23,32, 5} – Kay K. Jul 04 '23 at 02:45
  • For DIM=16, the set (var) size for the candidate numbers becomes 16. I realized I hard coded it as 8 in the above, so let me fix it. – Kay K. Jul 04 '23 at 02:49
  • @KayK. Thus, you want to add the same number multiple times? And have sequence in every distinct order? And you also want the sequences (and not just the count)? All my optimization are based on sequence of unique number in increasing number (and no tracking of final sequences). Why do you pass level to the recursive function if you don't use it? – Phil1970 Jul 05 '23 at 12:00
  • Thus you question is misleading by requiring that source be ordered but not the result. Also, there is no indication if source can have duplicates while obviously any of those detaisl count dor optimization since anything that can reduce problem size might increase performance. – Phil1970 Jul 05 '23 at 12:32
  • @Phil1970 In my post, I clearly said "ordered sequence". I didn't clearly said repeat is allowed or not, but that was not relevant to the original question about the error message. And if you understood what my code was doing, you would have caught that the repeat was allowed. Whether repeat was allowed or not, your answer of '5' was wrong (too small). And your suggested method was not basically different from what I was doing in my code. – Kay K. Jul 05 '23 at 17:28
  • @Phil1970 The reason why I had the level as an argument was for debug purpose to see why I was getting the "terminate called without an active exception" error. – Kay K. Jul 05 '23 at 17:29
  • @Phil1970 Thanks for your down voting. (I did up vote on your original comment.) – Kay K. Jul 05 '23 at 17:32
  • @KayK. The five possibility I got without duplicate and with answer in increasing order were : (A) 1, 3, 5, 7, 14 (B) 2, 3, 11, 14 (C) 2, 5, 23 (D) 5, 11, 14 (E) 7, 23. I only count those and confirm that my program give 5 as a total. Maybe, if you improve your quesion, I might remove the down vote. There is very little information in the question itself and I cannot trust a code that crash and has major flaws to be correct... If the input is ordered, there must be a reason as if you want all permutations, the initial order does not matters much. – Phil1970 Jul 07 '23 at 01:23
  • @Phil1970 I won't bother to change something on my post to get your down vote removed. Are you sure that even if you are willing to remove your down vote you can do so at this time? Remember that when you wrote your comment, the title of my post was "C++ error message ...". The posting was not asking to suggest an algorithm. And I was already clearly stating 1. "Ordered Sequence", 2. "For NUM=3, answer is 4". – Kay K. Jul 07 '23 at 01:52
  • @Phil1970 And you misunderstood the question in my post which was even not I was asking about. And then you downvoted as I pointed out you may have been misunderstanding the question? – Kay K. Jul 07 '23 at 01:53
  • @Phil1970 Is that a reasonable reason to downvote on someone's posting? – Kay K. Jul 07 '23 at 01:54
  • @Phil1970 Okay. I decided to improve the description on the question, and I added clarification. Please remove your downvote as you said. Then I may change my misunderstanding on what kind of person you are. Thanks. – Kay K. Jul 07 '23 at 01:59
  • I remove the downvote even though I think that one should put a lot more effort when asking a question. Already, is is bad to change a question as it might invalidate some answers. It seems more like an homework than something useful in real life. As the problem become very large, there should be some indication about limitations or what to do when it would overflow. One might be able to reduce the problem size by using mathematics to compute permutations and combinations. Also, original problem should have been easy to figure out by a debugger (or code review if professional work) – Phil1970 Jul 08 '23 at 12:52
  • Is it reasonable to down vote? Yes because it was not easy to understand what you want. **For almost any question, it should be clear what you want to do even without reading the code.** One cannot assume that the code works properly when there is a core dump! – Phil1970 Jul 08 '23 at 13:13
  • @Phil1970 Okay, thanks. – Kay K. Jul 09 '23 at 02:01

1 Answers1

2

You can do it by dynamic programming, but you have to watch out for overflow.

Let A[n] be the number of sequences for which the sum is n. Initialise it to 0. Then, for each n from 0 to target consider all the elements e in your collection. There are only two cases: either e is equal to n (in which case just add 1 to A[n]) or the A[n-e] previous sequences can be added to the tally A[n] (provided e < n).

The number of sequences can grow rapidly. I think the DIM=16, NUM=300 case will actually overflow an unsigned long long.

#include <iostream>
#include <vector>
#include <limits>
#include <cstdlib>
using namespace std;

const unsigned long long MAX = numeric_limits<unsigned long long>::max();

unsigned long long targetSum( const vector<int> &values, int target )     // all values assumed positive
{
   vector<unsigned long long> A( 1 + target, 0 );
   vector<bool> overflow( 1 + target, false );                            // is current sum overflowing?

   for ( int n = 0; n <= target; n++ )
   {
      for ( int e : values )
      {
         int toAdd = 0;
         if      ( e == n ) toAdd = 1;
         else if ( e < n  ) { toAdd = A[n-e];   overflow[n] = overflow[n] || overflow[n-e]; }

         if ( A[n] > MAX - toAdd ) overflow[n] = true;
         else                      A[n] += toAdd;
      }
   }

   if ( overflow[target] )   // frequency was made up of tallies which overflowed at some point
   {
      cout << "Overflow";
      exit(1);
   }

   return A[target];
}

//======================================================================

int main()
{
   cout << targetSum( { 1, 2, 3, 5, 7, 11, 14, 23 }, 5  ) << '\n';
   cout << targetSum( { 1, 2, 3, 5, 7, 11, 14, 23 }, 30 ) << '\n';
   cout << targetSum( { 1, 2, 3, 5, 7, 11, 14, 23, 34, 45, 67, 88, 142, 301, 431, 547 }, 300 ) << '\n';
}

Output (final case overflows):

14
131292855
Overflow
lastchance
  • 1,436
  • 1
  • 3
  • 12
  • This is great. Thanks a lot – Kay K. Jul 04 '23 at 16:06
  • This is ingenious.. – Kay K. Jul 04 '23 at 16:40
  • 1
    It's easier to see what is going on if you take all allusions to overflowing out. (You might like to try it.) However, adding unsigned integers will always produce an answer (essentially, by modular arithmetic) and won't tell you if it actually overflowed. In this case that happens for quite small sets of numbers. My first attempt (which I have long ago removed) looked a lot simpler, but got caught out by the third test case. – lastchance Jul 04 '23 at 16:46
  • Unless biggest number in data plus searched number could overflow, then explicitly checking overflow is essentialy useless... And if really a problem, then one could use 64 bits integer instead of 32 bits... – Phil1970 Jul 05 '23 at 12:14
  • 1
    @Phil1970, unsigned long long is already (on most systems) 64 bits. The third test case still overflows. I think you have to test because unsigned integers will happily add ... and overflow ... and never tell you. My original code didn't have an overflow check before addition, and it gave the wrong answer as a result. – lastchance Jul 05 '23 at 13:04