tags:

views:

363

answers:

2

Given an array of prime factors of a natural number, how can I find the total number of divisors using LINQ upon the original array? I've already figured out most of this problem, but I'm having trouble with my LINQ statement.


Math Background:

The prime factors of a number are the prime integers that divide evenly into the number without a remainder. e.g. The prime factors of 60 are 2,2,3,5.

The divisors of a number are all integers (prime or otherwise) that divide evenly into the number without a remainder. The divisors of 60 are 1,2,3,4,5,6,10,12,15,20,30,60.

I am interested in finding the total number of divisors. The total number of divisors for 60 is 12.

Let's express the prime factorization using exponents:

60 = 2^2 * 3^1 * 5*1

To find the total number of divisors given the prime factorization of the number, all we have to do is add 1 to each exponent and then multiply those numbers together, like so:

(2 + 1) * (1 + 1) * (1 + 1) = 12;

That's how you find the number of divisors given the prime factorization of a number.

The Code I Have So Far:

I already have good code to get the prime factors of a number, so I'm not concerned about that. Using LINQ, I want to figure out what the total number of divisors is. I could use a few loops, but I'm trying to use LINQ (if possible).

I'm going to:

  • Use Distinct() to find the unique values in the array.
  • Use Count() to find how many time the unique values occur (this is equal to the exponent).
  • Use an Aggregate() function to multiply the values together.

Here's the code I have:

class Program
{
    static void Main(string[] args)
    {
        var primeFactors = new int[] { 2, 2, 3, 5 };
        Console.WriteLine(primeFactors.Distinct().PrintList("", ", "));
            //Prints: 2, 3, 5
        Console.WriteLine("[2]:{0} [3]:{1} [5]:{2}"
            , primeFactors.Count(x => x == 2)
            , primeFactors.Count(x => x == 3)
            , primeFactors.Count(x => x == 5)
            );
            //Prints: [2]:2 [3]:1 [5]:1

\\THIS IS WHERE I HAVE TROUBLE:

        Console.WriteLine(primeFactors.Distinct().Aggregate((total,next) =>  
            (primeFactors.Count(x => x ==next) + 1)* total));
            //Prints: 8


        Console.ReadLine();
    }
}

Specifically, I'm having trouble with this part of code:

primeFactors.Distinct().Aggregate((total,next) =>  
    (primeFactors.Count(x => x ==next) + 1)* total)

Since the numbers in my array are not stored in the form of x^n, but rather in the form of n instances of x in the array, my thinking is to use Count() to find what n ought to be on a distinct array of x. The Aggregate function is intended to iterate through each distinct item in the array, find its Count + 1, and then multiply that by the total. The lambda expression in Count is supposed to use each distinct number as a parameter (next).

The above code should return 12, but instead it returns 8. I have trouble "stepping through" LINQ in debug mode and I can't figure out how I might better write this.

Why doesn't that portion of my code return the correct number of divisors as I expect? Is there a different (better) way to express this using LINQ?

+2  A: 

If I understand what you're looking to do, you may want to use GroupBy() instead.

var primeFactors = new int[]{ 2, 2, 3, 5 };
var numFacs = primeFactors.GroupBy(f => f, f => f, (g, s) => s.Count() + 1)
                          .Aggregate(1, (x, y) => x * y);
LBushkin
Your answer works great. Why are there 3 lambdas in the `GroupBy` clause and (more significantly) why are the first 2 lambdas identical?
Ben McCormack
There are only so many overloads of the GroupBy function. The first lambda identifies the key by which to group. The second identifies what result to select for each item within a group. The third allows you to transform an entire group (list of grouped items) into a single result - otherwise the list of groups would be the result. So, the first two lambdas are identical because I want to select each item without altering it. I wish I could omit the second lambda, but GroupBy doesn't have a corresponding overload for that.
LBushkin
+3  A: 

Try this:

int[] factors = new int[] { 2, 2, 3, 5 };
var q = from o in factors
        group o by o into g
        select g.Count() + 1;
var r = q.Aggregate((x, y) => x * y);

The specific problem with your suggested query is that your aggregate call fails to count the very first element (not to mention doesn't increment the count by 1). What it erroneously does is take the first factor and multiplies its value instead of its count + 1 with the next one.

Aviad P.
+1 however, i would prefer it to be one-liner without intermediate q variable
empi
That's great. I rewrote it to be: `primeFactors.GroupBy(f => f).Select(x => x.Count()+1).Aggregate((total, next) => next * total)` . Why is it that putting the count() + 1 in the select works correctly but in the aggregate it does not?
Ben McCormack
Because on the first iteration of the aggregate you're starting with two of your base values, so you need to count them both, but thereafter you only need to count the 'next' argument, since the first one is the aggregated value.
Aviad P.
Ahhh! Isn't there a way to "seed" the base value so it applies the iteration to each value?
Ben McCormack
There is, it's one of the overloads of Aggregate :)
Aviad P.