Удалить foreach - c # оптимизация кода - PullRequest
7 голосов
/ 31 августа 2010

Как оптимизировать этот код?

ParentDoglist, ChildDoglistis - Ilist. dogListBox - список

foreach (Dog ParentDog in ParentDoglist)
{
 foreach (Dog ChildDog in ChildDoglist)
 {
  if(ParentDog.StatusID==ChildDog.StatusID)
  dogListBox.Items.Add(new ListItem(ParentDog.Name, ParentDog.Key));
 }
}

EDIT: ParentDogTypeList, DogTypeList были переименованы в ParentDoglist, ChildDoglist, где оба не связаны друг с другом

if(ParentDog.Key==ChildDog.Key)

был изменен на

if(ParentDog.StatusID==ChildDog.StatusID)

Полная история:

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

DD будет выглядеть как

Parent1
  Child11 (10)
  Child12 (12)
Parent2
  Child21 (23)
  Child22 (20)
Leaf1 (20)
Leaf2 (34)

Итак, ParentDoglist должен принести все элементы Child и leaf вместе со счетчиком, а ChildDogList будет иметь Parent и ID листьев, поэтому я смогу заполнить соответствующий Child для их Parent и напрямую связать лист.

Родитель, ребенок и листовая собака будут храниться в одной таблице и различаться по статусу, а число будет в другой таблице.

Ни у одного из родителей не будет счета, только ребенок и лист будут иметь счет

Схема таблицы:

alt text

Ответы [ 7 ]

8 голосов
/ 31 августа 2010

Вы можете отсортировать ParentDoglist и ChildDoglist и выполнить линейный O(n) алгоритм поиска вместо этого O(n^2).

Но вы можете отсортировать контейнеры в O((ParentDoglist.Size() + ChildDoglist.Size()) * log2(ParentDoglist.Size() + ChildDoglist.Size())).

Тогда, если вы запустите этот код ТОЛЬКО РАЗ , ваш алгоритм будет оптимальным . Но если вы ищете БОЛЬШЕ, ЧЕМ ОДИН ВРЕМЯ , оптимальным решением будет сортировка контейнеров и сравнение по линейному времени, но если ваш контейнер может измениться, между тем функции поиска были запущены, и вы использовали "более одного раза решение "Вы должны использовать контейнер RB-Tree для переноса этих элементов, потому что с обычным списком после изменения контейнера вы не сможете вернуться в отсортированное состояние во время O(log(n)).

2 голосов
/ 31 августа 2010

Я все еще думаю, что самый элегантный и оптимизированный способ - использовать для него Linq.

box.Items.AddRange(
   ParentDoglist.Where(p=>ChildDoglist.Any(c=>c.StatusID== p.StatusID))
    .Select(r=>new ListItem(r.StatusID, r.Name)).ToArray());

Вот и все, и это только одна строка.Если вы предпочитаете объединения, вы можете сделать это с помощью этого запроса.

box.Items.AddRange(
   ParentDoglist.Join(ChildDoglist, p => p.StatusID, c => c.StatusID, (p,c)=>p)
    .Select(r=>new ListItem(r.StatusID, r.Name)).ToArray());
2 голосов
/ 31 августа 2010

Ваша самая большая проблема, вероятно, dogListBox.Items.Add.Добавление каждого элемента по одному довольно дорого.ListBox.Items.AddRange более эффективен.

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

List<ListItem> listItems = new List<ListItem>();
ILookup<string, Dog> childDogsPerKey = ChildDoglist.ToLookup(dog => dog.Key);
foreach (Dog ParentDog in ParentDoglist)
{
    foreach (Dog ChildDog in childDogsPerKey[ParentDog.Key])
    {
        listItems.Add(new ListItem(ParentDog.Name, ParentDog.Key));
    }
}
dogListBox.Items.AddRange(listItems.ToArray());

Этот кодПредполагается, что несколько детей собак могут иметь один и тот же ключ.Если на ключ может быть только одна детская собака, вы можете использовать .ToDictionary() вместо

1 голос
/ 31 августа 2010

Это не foreach, который медлителен, это добавляет и рендерит новые элементы.

Добавить beginupdate / endupdate:

dogListBox.BeginUpdate();
foreach (Dog ParentDog in ParentDoglist) 
{ 
 foreach (Dog ChildDog in ChildDoglist) 
 { 
  if(ParentDog.Key==ChildDog.Key) 
  dogListBox.Items.Add(new ListItem(ParentDog.Name, ParentDog.Key)); 
 } 
} 
dogListBox.EndUpdate();
1 голос
/ 31 августа 2010

Поскольку это исходит от БД, обходы базы данных, скорее всего, приведут к снижению производительности. Также сравнение ParentDog.Key==ChildDog.Key может быть выполнено в SQL, так что вы не извлекаете все эти данные в свое приложение только для того, чтобы их отбросить.

Перепроектируйте его так, чтобы вы сделали один выбор, чтобы получить все данные в одном запросе.

Альбин упомянул AddRange, но вы даже можете пойти дальше и виртуализировать свою сетку, чтобы она вытягивала строки, отображаемые пользователю, только когда она смотрит на эту часть сетки.

EDIT

Чтобы сгенерировать ваш список, вам нужно вернуть что-то подобное из БД:

Parent1, null, null
Parent1, Child1, 110
Parent1, Child12, 12
Parent2, null, null
Parent2, Child21, 23
Parent2, Child22 ,20
Leaf1, null, 20
Leaf2, null, 34

Похоже, вам нужен какой-то left join и count, с потенциалом union all.

0 голосов
/ 31 августа 2010

Вы можете заменить вложенный цикл foreach простым выражением Linq. Чтобы это работало, вам нужно использовать System.Linq;

foreach (Dog ParentDog in 
            (from dog in ParentDogList
             from ChildDog in dog.StatusId
             where dog.StatusId == ChildDog.StatusId)
             select dog) )
{
    dogListBox.Items.Add(new ListItem(ParentDog.Name, ParentDog.Key));
}
0 голосов
/ 31 августа 2010
foreach (var ParentDog in ParentDoglist.Where(p=>ChildDoglist.Any(c=>c.Key== p.Key)).ToList())
    dogListBox.Items.Add(new ListItem(ParentDog.Name, ParentDog.Key));

Вот как бы вы сделали это с LinQ

...