2013-02-26 4 views
3

Вот упрощенный сценарий, с которым я имею дело. Существует несколько методов с циклической структурой.Есть ли способ встроить делегата в C#?

for (int i=0; i<I; i++) { 
    // Do something 
    for (int j=0; j<J; j++) { 
     // Do something 
     for (int k=0; k<K; k++) { 
      // Do something 
      Update(a,b,c); 
     } 
    } 
} 

В одном способе обновления (а, б, в) является

a[i] += b[j] * c[k] 

В другом способе, это

b[j] += a[i] * c[k] 

И тем не менее в другом методе, это

c[k] += a[i] * b[j] 

В настоящий момент мой код дублируется каждый Вот. Есть ли шаблон в C#, чтобы я не дублировал код? У меня возникло соблазн использовать делегата, но кажется, что делегат снизит производительность (что очень важно в моем случае).

Есть ли способ написать макрос или встроенную функцию делегата для такого сценария?

+0

Никогда, НИКОГДА не жертвуйте удобочитаемостью для производительности, если только вы не уверены в узком месте. Преждевременная оптимизация - это корень всего зла. – l46kok

+1

Можете ли вы объяснить, что это за код? Или что такое контекст? Трудно понять, как делегаты вписываются в это ... – rae1

+0

@ l46kok: Это не так. См. Полный текст, где эта цитата была вырезана. – abatishchev

ответ

11

Что-то вроде этого?

void DoUpdates(Action<int, int, int> update) 
{ 
    for (int i=0; i<I; i++) { 
    // Do something 
    for (int j=0; j<J; j++) { 
     // Do something 
     for (int k=0; k<K; k++) { 
     // Do something 
     update(i, j, k); 
     } 
    } 
    } 
} 

, а затем в вызывающем

DoUpdates((int i, int j, int k) => { a[i] += b[j] * c[k]; }); 

Это то, что вы ищете?

+0

Я чувствую, что это лучший ответ, учитывая, что мы не знаем, что должен делать код. – l46kok

+0

Спасибо за красивую схему. Я думаю о применении этой схемы к моей конкретной проблеме. Один ранний вопрос: действие является делегатом. Будет ли это иметь какой-либо эффект производительности? Включит ли компилятор действие? – zer0ne

+4

Ответ на ваш первый вопрос: получите секундомер, запустите код, и вы узнаете, насколько это быстро. Ответ на второй вопрос - нет. –

4
void Update<T>(T[] x, T[] y, T[] z, int i, int j, int k) 
{ 
    x[i] += y[j] * z[k]; 
} 

Использование:

Update(a, b, c, i, j, k); 
Update(b, a, c, j, i, k); 
Update(c, a, b, k, i, j); 

Я вижу, что a всегда доступ i (и так далее - b по j, c по k). Вы можете попытаться оптимизировать код, используя этот факт.

+0

Я собирался сказать, что дженерики могут помочь, но это лучше иллюстрирует мою идею. –

+0

Я думал, что идея состоит в том, чтобы устранить дублированный цикл, а не метод ... –

+4

Этот пример не работает. '+ =' и '*' не определены для типа 'T' – shf301

2

Если производительность критична вы могли бы избежать вызова метода во внутреннем цикле, как это:

void Update(int[]x, int[]y, int[]z, int I, int J, int K) 
{ 
    for (int i = 0; i < I; i++) 
    { 
     // Do something 
     for (int j = 0; j < J; j++) 
     { 
      // Do something 
      for (int k = 0; k < K; k++) 
      { 
       // Do something 
       x[i] += y[j] * z[k]; 
      } 
     } 
    } 
} 

и код вызова:

Update(a, b, c, I, J, K); 
Update(b, a, c, J, I, K); 
Update(c, a, b, K, I, J); 
+0

Спасибо. Я, вероятно, слишком упростил проблему. Порядок цикла не может быть изменен, т. Е. Порядок «сделать что-то» на самом деле имеет значение. – zer0ne

+0

А я боялся этого. Я думаю, вы могли бы попробовать обернуть каждый «сделать что-нибудь» в лямбда. Это вряд ли будет довольно (потому что это увеличит количество параметров для 'Update' до 9), но может дать вам требуемую скорость. – groverboy

0

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

public void Update(int destinationIndex, int[][] arrays, int[] indices) { 
    var product=1; 

    for(var i=indices.Length; i-->0;) 
     if(destinationIndex!=i) 
      product*=arrays[i][indices[i]]; 

    arrays[destinationIndex][indices[destinationIndex]]+=product; 
} 

public void PerformUpdate(
    int destinationIndex, int[] counts, int[][] arrays, Action<int, int>[] actions, 
    List<int> indices=null, int level=0 
    ) { 
    if(level==counts.Length) 
     Update(destinationIndex, arrays, (indices??new List<int>()).ToArray()); 
    else 
     for(int count=counts[level], i=0; i<count; i++) { 
      if(null!=actions&&level<actions.Length) 
       actions[level](i, count); // do something according to nesting level 

      (indices=indices??new List<int>()).Add(i); 
      PerformUpdate(destinationIndex, counts, arrays, actions, indices, 1+level); 
      indices.RemoveAt(indices.Count-1); 
     } 
} 

Этот код реализуется рекурсивным образом. int[][] array может быть заменен на общий массив до тех пор, пока вы собираетесь определить расчет operator * и operator +, а скорее название методов в MutiplyScalar и AddScalar.

Итак, мы не будем использовать делегат Update для управления пунктом назначения. Вместо этого мы просто используем destinationIndex, чтобы выполнить это.Ниже приводится тестовый пример:

int[] a=new[] { 1, 2 }, b=new[] { 3, 4, 5 }, c=new[] { 6 }; 
Action<int, int> m=(index, count) => Debug.Print("index={0}; count={1}", index, count); 
PerformUpdate(2, new[] { a.Length, b.Length, c.Length }, new[] { a, b, c }, new[] { m, m, m }); 

У нас есть еще инлайн делегатов там, которые называются Lambda Expressions в C#. Согласно исходному коду, который вы указали, между вложенными for-loops есть Do something. Однако мы не можем найти много информации, которая не известна глобально, Update; наиболее существенной разницей, которую мы видим, является индекс итерации и конечный номер, которые являются i, I, j, J и k, K. Таким образом, мы просто принимаем их как аргументы для перехода к Action<int, int> для выполнения чего-либо, и они являются переменными для каждого уровня for-loop.

Выполнение в значительной степени зависит от indices. Он сохраняет итерирующий индекс текущего цикла for и переходит на следующий уровень рекурсивного вызова. Кроме того, если вы передали arrays со счетом, меньшим, чем его Length, в indices, он рассматривается как массив с длиной того счета, в который вы перешли. Не пропускайте отрицательный счет, ни один более крупный. Это может быть недостаток Action<int, int>, это означало бы только ничего не делать вместо что-то делать.

0

Это, вероятно, будет встроено в него.

interface IFunc<T> 
{ 
    void Invoke(ref T a, ref T b, ref T c); 
} 

void DoUpdates<TFunc>(TFunc update) 
    where TFunc : IFunc<int> 
{ 
    for (int i = 0; i < I; i++) 
     for (int j = 0; j < J; j++) 
      for (int k = 0; k < K; k++) 
       update.Invoke(ref i, ref j, ref k); 
} 
Смежные вопросы