Повторный where() против нескольких коллекций в c#

#c# #performance #coding-style

#c# #Производительность #стиль кодирования

Вопрос:

У меня есть коллекция объектов A. У каждого A есть поле, которое соотносится с объектом B, из которого у меня есть другая коллекция. Другими словами, каждый B присоединяется к подмножеству коллекции As (но только концептуально, а не в коде). Это поле, связывающее A с B, может меняться в течение срока службы системы. Существуют системные требования, которые препятствуют изменению этой структуры.

Если мне нужно многократно выполнять операции над каждым набором A для каждого B, было бы лучше повторно использовать метод Where() для коллекции A или создать другую коллекцию, которой владеет B, и класс, который управляет добавлением и удалением соответствующих элементов.

Позвольте мне посмотреть, смогу ли я зафиксировать это в коде:

     class A {
      public B owner;
      ...
    }

    class B {
      ...
    }

    class FrequentlyCalledAction {
      public DoYourThing(B current) {
        List<A> relevantItems = listOfAllAItems.Where(x => x.owner == current).ToList()
        foreach (A item in relevantItems) {
          ...
        }
      }
    }
  

Против:

     class A {
      public B owner;
      ...
    }

    class B {
      public List<A> itsItems;
    }

    class FrequentlyCalledAction {
      public void DoYourThing(B current) {
        foreach (A item in current.itsItems) {
          ...
        }
      }
    }

    class AManager {
      public void moveItem(A item, B from, B to) {
        from.itsItems.remove(item);
        to.itsItems.add(item);
      }
    }
  

Комментарии:

1. Как часто вы это делаете? Как часто меняются отношения A-> B? Насколько велик набор As и Bs? Разве у B не может быть ссылки на A?

2. DoYourThing — один из самых частых вызовов в системе. Отношения A-> B меняются, возможно, один раз за каждые 4 вызова DoYourThing. Они оба представляют собой небольшие наборы — может быть, 50 B, может быть, 100 A.

3. Как часто A должен знать, кто его владелец? Не могли бы вы просто сохранить A для их владельца и определить, кто их владелец, когда вам это нужно?

4. Существуют и другие части системы, которые требуют, чтобы A знал своего владельца.

5. Что вы решили сделать в итоге?

Ответ №1:

Это в первую очередь зависит от размера наборов. Если элементов всего несколько, накладные расходы, связанные с решением two, больше, чем прирост производительности.

В этом случае я бы использовал решение one, поскольку оно имеет лучшую читаемость и менее сложное в управлении.

Если в наборе тысячи элементов, я бы выбрал второе решение. moveItems Метод представляет собой операцию O (n), но кажется, что в вашем сценарии больше операций чтения, чем записи. Таким образом, вы получаете большую производительность за счет более структурированного дизайна.

Комментарии:

1. Я думаю, вы можете улучшить MoveItems до O (1), используя HashSets.

Ответ №2:

На самом деле все зависит от размера вашей коллекции. Sol 2 более сложный, но более быстрый для большой коллекции, в то время как sol1 может быть очень быстрым для менее чем 100/1000 элементов или около того.

Комментарии:

1. Есть ли недостаток в использовании пространства в решении 2? Имеет ли это значение, поскольку все они на самом деле просто ссылки? Перевешивает ли увеличение производительности?

2. Опять же, все зависит от размера коллекции, но на самом деле в списке будут сохранены только ссылки.

Ответ №3:

Поскольку наборы небольшие (~ 100 элементов) и они часто меняются (~ каждые 4 итерации), сделайте это, а затем посмотрите, есть ли у вас проблема.

 public DoYourThing(B current)
{
    foreach(A item in listOfAllAItems.Where(a => a.owner == current))
    {
        ...
    }
}
  

Я не вижу никакого смысла в приведении IEnumrable<A> к IList<A> .

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

Ответ №4:

Если вы выберете решение 2, возможно, стоит использовать HashSet, а не список. Хэш-набор равен O (1) для добавления и удаления, тогда как список равен O (1) для добавления и O (n) для удаления.

Другой вариант — это тот, преимущество которого в том, что пользователям A amp; B не нужно помнить об использовании AManager :

 static class GlobalDictionary
{
  private static Dictionary<B,HashSet<A>> dictionary = new Dictionary<B,HashSet<A>>();

  public static HashSet<A> this[B obj]
  {
    // You could remove the set and have it check to see if a Set exists for a B
    // if not you would create it.
    get {return dictionary[obj];}
    set {dictionary[obj] = value;}
  }
} 


class A 
{
  private B owner;  
  public B Owner
  {
    get{ return owner;}
    set
    {
      if (owner != null) GlobalDictionary[owner].Remove(this);

      owner = value;
      GlobalDictionary[owner].Add(this);
    }
  }
}

class B
{
  public B()
  {
    GlobalDictionary[this] = new HashSet<A>();
  }

  public IEnumerable<A> Children
  {
    get
    {
      return GlobalDictionary[this];
    }
  }
}
  

Я не пробовал это, так что, вероятно, это потребует некоторых настроек, но вы должны понять идею.

Комментарии:

1. Возможно, вы захотите подумать о том, как удалить B из глобального словаря, когда на них больше ничего не ссылается, чтобы сборщик мусора мог их удалить.