SelectMany,GroupBy和First

时间:2019-06-30 19:18:38

标签: c# linq

今天我试图用LINQ解决https://projecteuler.net/problem=5

这可以正常工作,并且可以在我的计算机上不到2秒执行一次,但是有点冗长:

Enumerable.Range(1, 1000000000)
    .Where(i => i % 2 == 0
        && i % 3 == 0
        && i % 4 == 0
        && i % 5 == 0
        && i % 6 == 0
        && i % 7 == 0
        && i % 8 == 0
        && i % 9 == 0
        && i % 10 == 0
        && i % 11 == 0
        && i % 12 == 0
        && i % 13 == 0
        && i % 14 == 0
        && i % 15 == 0
        && i % 16 == 0
        && i % 17 == 0
        && i % 18 == 0
        && i % 19 == 0
        && i % 20 == 0
    )
    .First()

所以我也尝试将2-19范围也设为Enumerable,并像这样进行交叉连接

Enumerable.Range(1, 1000000000)
    .SelectMany(n =>
        Enumerable.Range(2, 19)
            .Select(d => (n, d))
    )
    .GroupBy(x => x.n)
    .Where(g => g.All(y => y.n % y.d == 0))
    .First()
    .Key

第二个解决方案的问题是它分配大量资源,在x86 LINQPad中崩溃,并带有OutOfMemoryException,并且在我手动杀死它之前吞噬了x64 LINQPad版本中的大量内存。

我的问题是为什么? 还有LINQ查询可以避免该问题吗?

CLR堆分配分析器插件告诉我,内部正在进行堆分配

.Select(d => (n, d))

由于捕获了“ n”。 所以我认为这是OutOfMemoryException的原因,但是... 因为我使用First()而不在其间实现查询,所以我认为这应该不成问题,因为linq会实现该组并再次丢弃它,因为它在释放内存时不满足条件。 selectmany或groupby是否发生一些时髦的事情,迫使所有数据首先被实现,还是我的思维模型在这里是错误的?

1 个答案:

答案 0 :(得分:2)

如果您尝试了以下代码,则会发生相同的问题:

Enumerable.Range(1, 1000000000)
    .GroupBy(x => x)
    .First();

这意味着在查询执行期间将实现所有组,这就是抛出OutOfMemoryException的原因。

要解决该问题,可以使用@ haim770在注释部分中提到的以下LINQ代码:

Enumerable
    .Range(1, 1000000000)
    .First(i => Enumerable
        .Range(2, 19)
        .All(j => i % j == 0))

为了进行更多优化,我找到了更好的解决方案。很抱歉没有使用LINQ,但是它已经进行了优化,也许有一种方法可以使用LINQ来实现。

与其循环浏览大量数字并检查每个数字,何不直接建立所需的答案。

所需的输出是数字x被所有数字1..n整除而没有任何余数。因此,它是1..n范围内数字的所有质数的倍数。通过仅使用这些主要因子的最小量,我们可以获得最小的x

例如,如果n = 10则:

i = 2: prime factors of 2 are [2] -> neededPrimes = [2]
i = 3: prime factors of 3 are [3] -> neededPrimes = [2, 3]
i = 4: prime factors of 4 are [2, 2] -> neededPrimes = [2, 2, 3] // we add just one 2 because the other already exists in neededPrimes
i = 5: prime factors of 5 are [5] -> neededPrimes = [2, 2, 3, 5]
i = 6: prime factors of 6 are [2, 3] -> neededPrimes = [2, 2, 3, 5] // we add nothing because [2, 3] are already in neededPrimes
i = 7: prime factors of 7 are [7] -> neededPrimes = [2, 2, 3, 5, 7]
i = 8: prime factors of 8 are [2, 2, 2] -> neededPrimes = [2, 2, 2, 3, 5, 7] // we add one 2 because the other 2's already exist in neededPrimes
i = 9: prime factors of 9 are [3, 3] -> neededPrimes = [2, 2, 2, 3, 3, 5, 7]
i = 10: prime factors of 10 are [2, 5] -> neededPrimes = [2, 2, 2, 3, 3, 5, 7]

x = 2 * 2 * 2 * 3 * 3 * 5 * 7 = 2520

这是一个代码,我希望很清楚:

public static void Main(string[] args)
{
    // The number to find its smallest multiple
    var n = 20;
    // A list that contains all primes that are founded across the calculation
    var calculatedPrimes = new List<int>();
    // Start through the numbers that x (the output number) should be divisible by
    for (var i = 2; i <= n; i++)
    {
        // Get primes of i
        var primes = GetPrimeFactors(i);
        // Loop through primes of i and add to "calculatedPrimes" the ones that are not 
        // in "calculatedPrimes"
        primes.ForEach(prime =>
        {
            if (!calculatedPrimes.Contains(prime) ||
                calculatedPrimes.Count(p => p == prime) < primes.Count(p => p == prime))
            {
                calculatedPrimes.Add(prime);
            }
        });
    }

    // The output number should be the multiple of all primes in "calculatedPrimes" list
    var x = calculatedPrimes.Aggregate(1, (res, p) => res * p);
    Console.WriteLine(x);

    Console.ReadLine();
}

// A function to get prime factors of a given number n
// (example: if n = 12 then this will return [2, 2, 3])
private static List<int> GetPrimeFactors(int n)
{
    var res = new List<int>();
    while (n % 2 == 0)
    {
        res.Add(2);
        n /= 2;
    }

    for (var i = 3; i <= Math.Sqrt(n); i += 2)
    { 
        while (n % i == 0)
        {
            res.Add(i);
            n /= i;
        }
    }

    if (n > 2)
        res.Add(n);
    return res;
}