2

I was doing a prime no# class (runs a function to check if inout is prime) and tried to take it a little further by printing what the prime numbers are for inputs that are prime. I managed to hack together an inelegant solution as you can see in the code, by wedging a for-loop in the middle of a cout.

I want to write a function that would create an array of all numbers that input num can be divided by, and then call the function in my main func.

int main() {
    
    int num;
    
    cout<<"Enter a number and we'll check if it's prime: ";
    cin>>num;
    
    isPrime(num);
    
    if (isPrime(num) == true)
        cout<<"Number is prime!";
    else
    {
        cout<<"Not a prime number :( ";
        cout<<"Infact, "<<num<<" can be divided by: ";
        for(int i=2; i<num; i++) {
            if(num % i == 0)
            cout<<i<<", ";
        }
        
        //What I want:> cout<<"Infact, "<<num<<" can be divided by: "<<arrayOfDiiders;
    }
    return 0;
}

The below is quasi human language/c++ code, written to describe what I wanted to achieve.

int allDividers(int num) {

int arrayOfDiiders[]; //the array i wanted to make
for(int i=2; i<num; i++) {
    if(num % i == 0)
    // add i to the arrayOfDiiders[];
}

return arrayOfDiiders[];
}

Other func that assertions if user input is prime number. Included incase relevant.

bool isPrime(int num){
    int countDivs = 0; 
    for(int i=1; i<=num; i++) {
        if (num % i == 0) 
            countDivs++;
    }
    if (countDivs == 2) 
        return true;
    else 
        return false;
} 

Thanks in advance.

  • 3
    Use `std::vector` unless something prevents you from doing that? – UnholySheep Jan 04 '22 at 00:23
  • If you can't use vectors then your professor most likely wants you to use new []. Although then I wonder how the calling code is supposed to know how many items are in the dynamic array. – drescherjm Jan 04 '22 at 00:24
  • Have you considered returning a pointer? – Ryan Zhang Jan 04 '22 at 00:28
  • Side note: it is very hard to return an array in c++. Arrays decay to pointers and are not copied. Unless the array was passed in, taken from somewhere else with sufficiently wide scope, or `static`, the array will go out of scope and expire at the end of the function and the pointer received by the caller references an invalid object. The nasty part is sometimes this will look like it works because the memory has not yet been reclaimed or reused and looks like a valid object. Until suddenly, at the worst possible time, it doesn't work. – user4581301 Jan 04 '22 at 00:38
  • 2
    You are calling `isPrime()` twice. Isn't once good enough? Does `isPrime` maintain a history? You may want to try something like this: `const bool yesItsPrime = isPrime(num); if (yesItsPrime)` – Thomas Matthews Jan 04 '22 at 00:42
  • BTW, you should check if the number is 2, and then 3, then increment by 2. The only even prime is 2; all remaining are odd. – Thomas Matthews Jan 04 '22 at 00:45
  • Thank you for all the incredible feedback, so much knowledge here. – Kernel Bash Jan 04 '22 at 00:48
  • I am not yet familiar with how to use vectors or static arrays yet, although I am covering them now. Is there another solution than creating and calling an array that would be better? Btw I don’t know why I called the isPrime func twice - i must have had a slow moment. – Kernel Bash Jan 04 '22 at 00:56

4 Answers4

3

I managed to hack together an inelegant solution as you can see in the code, by wedging a for-loop in the middle of a cout.

Actually, this solution is much more elegant since it avoids the unnecessary cost of a dynamic array.

  1. Create dynamically filled array in a function. 2) Return the array content

You can do it like this:

std::vector<int> v; // a dynamic array
// fill the vector here
return v;
eerorika
  • 232,697
  • 12
  • 197
  • 326
1

You can use a std::vector:

#include <vector>
std::vector<int> factors;
for(int i=2; i<num; i++) {
    if(num%i==0) {
        factors.push_back(i);
    }
}
for(int i:factors) cout<<i;
  • Thanks, I am working this in. A question though, how does 'for(int i:factors)' work? I have not come across the semi-colon ':' before and don't understand this syntax. – Kernel Bash Jan 05 '22 at 20:31
  • It's basically a shorter way to iterate through the list instead of `for(int i =0; i – Aprameya Tirupati Jan 11 '22 at 21:53
1

Actually you can combine both your requirements(checking if a num is prime ad getting all the dividors) into the same function.

I would do it this way

#include <iostream>
#include <set>
#include <cmath>

using namespace std;

static void getDividors(int num, set<int> &divs)
{
    divs.insert(1);
    divs.insert(num);

    if (num == 2)
        return;

    // It is enough to check until the sqrt num..
    int sqRootNum = floor(sqrt(num));

    for (int i = 2; i <= sqRootNum; i++) {
        if (num % i == 0) {
            divs.insert(i);
            divs.insert(num/i);
        }
    }
}

int main() {
    
    int num;
    
    cout<<"Enter a number and we'll check if it's prime: ";
    cin>>num;

    set<int> divs;
    getDividors(num, divs);
    bool isPrime = divs.size() > 2 ? false : true;
    if (isPrime)
        cout<<"Number is prime!";
    else
    {
        cout<<"Not a prime number :( ";
        cout<<"Infact, "<<num<<" can be divided by: ";
        for (const auto &n : divs) {
            cout<< n <<" ";
        }
    }

    return 0;
}

Here is the sample output:

Enter a number and we'll check if it's prime: 16
Not a prime number :( Infact, 16 can be divided by: 1 2 4 8 16 

The complexity of above code is O(sqrt(n)) which makes it very efficient too.

For insertion of elements, i am using set instead of vectors as it will simplify the logic as to not check for duplicates when adding into vector and also will be ordered(elements added to set will be in the ascending order) making it easy to nicely print them.

Sandeep
  • 18,356
  • 16
  • 68
  • 108
1

Since you wrote

The below is quasi human language/c++ code, written to describe what I wanted to achieve.

I'm adding this answer to show a solution that (maybe accepting to change our midset a bit) is truly like human language, because it tells a story, rather then using low lever for loops and if conditionals for which you have to pay attention on whether you've mistakely written <= instead of < and other stuff.

Here it is:

auto dividers = iota(2, num) | transform(both(id, mod(num)))
                             | filter(compose(equal_to(0), get_second))
                             | transform(get_first);

What does it do?

In short:

  • takes numbers from 2 to num-1,
  • std::pairs them up with the modulo of num by each of them,
  • retains only those with 0 modulo,
  • pulls the original number throwing away the modulo.

More in detail:

  • Iota is the ninth letter of the Greek alphabet, and ranges::views::iota (for which you can refer to std::ranges::views::iota) is such that iota(n) gives you back n, n+1, n+2, n+3, ..., whereas if you pass it a second argument, as in our usecase, it gives you back only numbers up to and not including that: iota(n, n+m) gives n, n+1, n+2, ..., n+m-1.
  • For ranges::views::transform you can refer to std::ranges::views::transfrom; we are passing to it both(id, mod(num)) as the transforming function, which, as the name should imply, applies both the identity funtion and mod(num) to its argument and gives you back a pair of those two results (it is not a standard function, I've written it at the bottom of this answer); mod(num), guess what, is such that nod(num)(n) == num % n (ditto, I've defined mod below, as a hana::curryed wrapper to %).
  • For ranges::views::filter you can refer to std::ranges::views::filter; we are passing to it the mathematical composition, via boost::hana::compose of two functions: get_second, which takes the second entry of the pair coming from the transform, i.e. the modulo, and equal_to(0) which is predicate testing if something is zero (equal_to is a curried std::equal_to<>{} function object).
  • Finally, we are pulling out only the first element of the pairs, via transform(get_first).

As an improvement to the code above, you could add | cache1 between the first of the two tranform and the filter after it. The reason why, is explained in this post here on Stack Overflow and in this post on FluentC++.


Below are the helper functions I've used with each header they need.

std::get is a template function, so it can't be passed around as an object could. BOOST_HOF_LIFT can make an object out of it:

#include <boost/hof/lift.hpp>
#include <utility>
template<int N>
auto constexpr get = BOOST_HOF_LIFT(std::get<N>);

so we can write more verbose getters for our usecase of working on pairs:

auto constexpr get_first = get<0>;
auto constexpr get_second = get<1>;

std::equal_to is already a function object, but we want to be able to pass its two arguments one by one, so we curry it:

#include <boost/hana/functional/curry.hpp>
#include <functional>
auto constexpr equal_to = curry<2>(std::equal_to<>{});

We want to do the same for %, but that's on operator, so we'd have to manually wrap it; luckily the STL offers an object already, namely std::modulus<>{}:

// same two headers as previous one
auto constexpr mod = curry<2>(std::modulus<>{});

Finally, here's my hand-made both (which could be greately improved, generalized):

#include <utility>
auto constexpr both = [](auto const& f1, auto const& f2){
    return [&f1, &f2](auto const& x){
        return std::make_pair(f1(x), f2(x));
    };
};

Here's a working demo on Compiler Explorer.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Thank you, this is certainly a different approach, albeit so far above my level of comprehension! So far I have mostly only learnt about primary data types and functions. I have not seen so many of these concepts before. For example, what is 'auto'? I like the implementation of cache1, could you explain what data exactly is being sent to cache memory? – Kernel Bash Jan 04 '22 at 13:16
  • Given you ask what `auto` is, I don't think there's an easy way for me to convey what `cache1` is for in a comment. To give a hint: `v | transform(f) | filter(g)` is the same as `filter(transform(v, f) g)`, where both `filter` and `transform` will not do computations unless you actually try to ask them for elements of the result; when you ask `filter` for the first element, it will ask `transform(v, f)` "what's your first element? I need to check if I need it", so `transform` will compute `f(v[0])`, and `filter` will run `g(f(v[0]))`; – Enlico Jan 04 '22 at 14:11
  • (cont.) if it comes `false`, `filter` will discard that value and go on, but if it's `true`, then `filter` will ask `transform(v, f)` "can you give me the first element?", so `transform` will recompute `f(v[0])`. Shoving a `cache1` in between will prevent this double-computation. – Enlico Jan 04 '22 at 14:11