代码之家  ›  专栏  ›  技术社区  ›  slf

用LINQ/.NET4解决组合问题

  •  3
  • slf  · 技术社区  · 15 年前

    this article 在我的MSDN RSS feed中弹出,阅读之后, and the sourced article here 我开始想办法。

    规则很简单:

    1. 这个数字应该可以被9整除。
    2. 如果新数字的最右边的数字被删除,剩余的数字应该可以被7整除。

    这是他提议的怪物LINQ查询:

    // C# and LINQ solution to the numeric problem presented in:
    // http://software.intel.com/en-us/blogs/2009/12/07/intel-parallel-studio-great-for-serial-code-too-episode-1/
    
    int[] oneToNine = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    // the query
    var query = 
        from i1 in oneToNine
       from i2 in oneToNine
        where i2 != i1
           && (i1 * 10 + i2) % 2 == 0
        from i3 in oneToNine
        where i3 != i2 && i3 != i1
           && (i1 * 100 + i2 * 10 + i3) % 3 == 0
        from i4 in oneToNine
        where i4 != i3 && i4 != i2 && i4 != i1
           && (i1 * 1000 + i2 * 100 + i3 * 10 + i4) % 4 == 0
        from i5 in oneToNine
        where i5 != i4 && i5 != i3 && i5 != i2 && i5 != i1
           && (i1 * 10000 + i2 * 1000 + i3 * 100 + i4 * 10 + i5) % 5 == 0
        from i6 in oneToNine
        where i6 != i5 && i6 != i4 && i6 != i3 && i6 != i2 && i6 != i1
           && (i1 * 100000 + i2 * 10000 + i3 * 1000 + i4 * 100 + i5 * 10 + i6) % 6 == 0
        from i7 in oneToNine
        where i7 != i6 && i7 != i5 && i7 != i4 && i7 != i3 && i7 != i2 && i7 != i1
           && (i1 * 1000000 + i2 * 100000 + i3 * 10000 + i4 * 1000 + i5 * 100 + i6 * 10 + i7) % 7 == 0
        from i8 in oneToNine
        where i8 != i7 && i8 != i6 && i8 != i5 && i8 != i4 && i8 != i3 && i8 != i2 && i8 != i1
           && (i1 * 10000000 + i2 * 1000000 + i3 * 100000 + i4 * 10000 + 
               i5 * 1000 + i6 * 100 + i7 * 10 + i8) % 8 == 0
        from i9 in oneToNine
        where i9 != i8 && i9 != i7 && i9 != i6 && i9 != i5 && i9 != i4 && i9 != i3 && i9 != i2 && i9 != i1
        let number = i1 * 100000000 +
                     i2 * 10000000 +
                     i3 * 1000000 +
                     i4 * 100000 +
                     i5 * 10000 +
                     i6 * 1000 +
                     i7 * 100 +
                     i8 * 10 +
                     i9 * 1
        where number % 9 == 0
        select number;
    
    // run it!
    foreach (int n in query)
        Console.WriteLine(n);
    

    Octavio说“注意,根本没有人试图优化代码”,我想知道的是,如果我们真的试图优化这个代码会怎么样。这真的是这个代码能得到的最好的吗?我想知道我们怎样才能用.NET4做到最好,尤其是尽可能多地并行工作。我不一定要在纯LINQ中寻找答案,假设.NET4是任何形式的(托管c++、c#等都可以接受)。

    3 回复  |  直到 15 年前
        1
  •  3
  •   Gideon Engelberth    15 年前

    如果您有权访问ImmutableList类,那么它可以提供一个非常简短的解决方案。而不是在每个阶段都尝试每一个,你只传递剩余的可能性到下一个状态。此外,通过在每个阶段保持总数,计算的次数也减少了。

    Dim query = From i1 In Tuple.Create(0L, allNums).ChooseNextNumber(1)
                From i2 In i1.ChooseNextNumber(2) _
                From i3 In i2.ChooseNextNumber(3) _
                From i4 In i3.ChooseNextNumber(4) _
                From i5 In i4.ChooseNextNumber(5) _
                From i6 In i5.ChooseNextNumber(6) _
                From i7 In i6.ChooseNextNumber(7) _
                From i8 In i7.ChooseNextNumber(8) _
                From i9 In i8.ChooseNextNumber(9)
                Select i9.Item1
    
    <System.Runtime.CompilerServices.Extension()> _
    Private Function ChooseNextNumber(
          ByVal previous As Tuple(Of Integer, ImmutableList(Of Integer)),
          ByVal modulusBase As Integer) _
        As IEnumerable(Of Tuple(Of Integer, ImmutableList(Of Integer)))
        Return From i In previous.Item2
               Let newTotal = previous.Item1 * 10 + i
               Where newTotal Mod modulusBase = 0
               Select Tuple.Create(newTotal, previous.Item2.Remove(i))
    End Function
    
        2
  •  1
  •   BlueRaja - Danny Pflughoeft    15 年前

    首先,最后一点(关于 i9 )没有必要,因为 全部的

        3
  •  1
  •   slf    15 年前

    我不认为你能显著改进这个查询。。。它实际上已经相当有效了,因为每一步都比前一步有更少的可能组合。

    您可以轻松地对一些代码进行分解,以使查询更具可读性。例如,使用一个谓词检查每一步的算法不变性,并使用一个助手从数字中构建数字(而不是“内联”乘法和加法)。

    Dn公司 N ,和 Xn 数字由D1…Dn组成。在每个步骤N中,以下语句应为真:

    • Dn不在[D1…D(n-1)]
    • Xn可被N整除

    isValid

    // Delegate with variable number of arguments
    delegate TResult FuncWithParams<TArg, TResult>(params TArg[] args);
    
    void Main()
    {
    
        var oneToNine = Enumerable.Range(1, 9).ToArray();
    
        // Creates a number from its digits
        FuncWithParams<int, int> makeNumber =
            digits => digits.Aggregate(0, (acc, d) => acc * 10 + d);
    
        // Checks the invariant against a sequence of digits
        FuncWithParams<int, bool> isValid =
            digits => !digits.Take(digits.Length - 1).Contains(digits.Last())
                    && makeNumber(digits) % digits.Length == 0;
    
        var query = 
            from d1 in oneToNine
            from d2 in oneToNine
            where isValid(d1, d2)
            from d3 in oneToNine
            where isValid(d1, d2, d3)
            from d4 in oneToNine
            where isValid(d1, d2, d3, d4)
            from d5 in oneToNine
            where isValid(d1, d2, d3, d4, d5)
            from d6 in oneToNine
            where isValid(d1, d2, d3, d4, d5, d6)
            from d7 in oneToNine
            where isValid(d1, d2, d3, d4, d5, d6, d7)
            from d8 in oneToNine
            where isValid(d1, d2, d3, d4, d5, d6, d7, d8)
            from d9 in oneToNine
            where isValid(d1, d2, d3, d4, d5, d6, d7, d8, d9)
            select makeNumber(d1, d2, d3, d4, d5, d6, d7, d8, d9);
    
        query.Dump();
    }
    

    还是挺大的,但比原版可读性强多了。。。