Решение комбинаторных проблем с помощью LINQ / .NET4 - PullRequest
3 голосов
/ 17 мая 2010

Я увидел эту статью во всплывающем окне в своем RSS-канале MSDN, и после прочтения и найденной здесь статьи Я начал задумываться о решении.

Правила просты:

Найдите число, состоящее из 9 цифр, в котором каждая из цифр от 1 до 9 появляется только один раз.Это число также должно удовлетворять следующим требованиям делимости:

  1. Число должно делиться на 9.
  2. Если удаляется самая правая цифра, оставшееся число должно делиться на 8.
  3. Если удаляется самая правая цифра нового номера, оставшееся число должно делиться на 7.
  4. И так до тех пор, пока не останется только одна цифра (которая обязательно будет делиться на 1).

Это его предложенный монстр запрос 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);

Октавио заявляет: «Обратите внимание, что не было сделано никакой попытки оптимизировать код», что я хотел бы знатьэто то, что если бы мы пытались оптимизировать этот код.Это действительно лучшее, что может получить этот код?Я хотел бы знать, как мы можем сделать это лучше всего с .NET4, в частности делать столько параллельно, сколько возможно.Я не обязательно ищу ответ в чистом LINQ, допустим .NET4 в любой форме (управляемый c ++, c # и т. Д. Все приемлемо).

Ответы [ 3 ]

3 голосов
/ 18 мая 2010

Если у вас есть доступ к классу 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
1 голос
/ 18 мая 2010

Не думаю, что вы можете значительно улучшить этот запрос ... На самом деле он уже довольно эффективен, потому что каждый шаг имеет гораздо меньше возможных комбинаций, чем предыдущий.

То, что вы могли бы легко сделать, это разложить на части некоторый код, чтобы сделать запрос более читабельным. Например, используйте предикат, который проверяет инвариант алгоритма на каждом шаге, и помощник для построения чисел из цифр (вместо «встроенных» умножений и сложений).

Давайте назовем 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();
}

Все еще довольно большой, но гораздо более читабельный, чем оригинал ...

1 голос
/ 17 мая 2010

Ну, во-первых, последний бит (относительно i9) не является необходимым, поскольку все перестановки 1-9 делятся на 9 ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...