C#: создание объектов, которых нет в другом списке

У меня есть два списка двух разных типов объектов, представляющих строки данных из двух запросов sql. Первый список содержит данные, а второй содержит более подробные данные. Итак, в качестве примера:

List1:        List2:
 1   Alice     1   15
 2   Bob       1   19
 3   Carol     2    5
 4   Dave      2    7
               2   20
               4   16

Я хочу вставить строки в List2, чтобы у всех в List1 была хотя бы одна строка в List2. Поэтому, когда в List2 нет строк для определенного человека, я хочу вставить одну со значением по умолчанию. В примере мне пришлось бы вставить одну строку для Кэрол, поэтому я бы получил:

List1:        List2:
 1   Alice     1   15
 2   Bob       1   19
 3   Carol     2    5
 4   Dave      2    7
               2   20
               3    0
               4   16

У кого-нибудь есть умный, чистый и эффективный способ сделать это?

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

Примечание. Да, я знаю, что вопрос\название вроде... мля... но я не знаю, как лучше его сформулировать. Кто-нибудь, исправьте, если можете.

Примечание 2. Я не могу использовать SQL. Я не могу вставить эти строки в исходную таблицу. Я сообщаю о данных, что означает, что я не трогаю никакие данные. Я только что прочитал это. Данные должны использоваться в отчете master-detail, и моя проблема заключается в том, что, когда для определенной основной строки нет подробностей, вы получаете просто пустое место. Что не хорошо. Поэтому я хочу вставить строки с разумной информацией, чтобы пользователь мог видеть, что здесь нечего показывать.


person Svish    schedule 05.06.2009    source источник


Ответы (9)


Предполагая, что ваши списки отсортированы по значению Key, как в вашем примере (в данном случае целое число), должно работать что-то вроде этого:

int i = 0;

foreach (var item in List1)
{
    // Skip any items in List2 that don't exist in List1
    // (not sure this is needed in your case), or that we've
    // already passed in List1
    while (List2[i].Key < item.Key)
        i++;

    if (List2[i].Key > item.Key)
    {
        // Create new item in List2
        List2.Add(new List2Item(item.Key, 0));
    }
}

// TODO: resort List2

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

Обратите внимание, что это не удастся, если в List1 есть повторяющиеся записи Key. Вам нужно будет проверить это отдельно, чтобы предотвратить создание нескольких новых элементов в List2.

person Thorarin    schedule 05.06.2009

LINQ: из примера, который вы привели в ссылке, просто измените код:

foreach (var i in q) {
    Console.WriteLine("Customer: {0}  Order Number: {1}", 
        i.Name.PadRight(11, ' '), i.OrderNumber);
}

to

foreach (var i in q) {
    if (i.OrderNumber == "(no orders)")
        order.Add(new Order {Key = i.ID /* add your default values here*/});
}

Конечно, вы можете сохранить некоторые строки здесь и в предыдущем коде.

person van    schedule 05.06.2009

Хорошо, вот так: 1. Создайте тип для представления элемента из ваших списков:

 struct ListType
        {
            public object Id;
            public object Name;
        }

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

  1. Создайте свой List2 как IEnumerable‹ ListType> из вашего запроса LINQ

  2. Я предполагаю, что List1 имеет ту же структуру, что и List2, с полями Id и Name (вы можете использовать тот же тип ListType для элементов списка).

  3. С приведенными выше предположениями вот код для решения исходной проблемы:

    Список newLst2 = list2.ToList();

    Array.ForEach(list1.ToArray(), list1It =>
    {
       var isInList2 = from list2it in newLst2.ToArray()
                       where (string)list2it.Id == list1It.Id
                       select list2it;
    
       if (isInList2.Count() == 0)
           newLst2.Add(new ListType { Id = list1It.Id, Name = list1It.Name });
    
    });     
    

Комментарии: для каждого элемента в List1 сделайте запрос в List2 и проверьте, существует ли идентификатор. Если его нет, добавьте новый элемент.

Вероятно, есть более эффективные способы сделать это, но это должно помочь вам начать.

person AlexDrenea    schedule 05.06.2009

Вот решение с использованием LINQ.

public class List1
{
    public int ID { get; set; }
    public string Person { get; set; }
}

public class List2
{
    public int ID { get; set; }
    public int Value { get; set; }
}

var lList1 = new List<List1>
             {
                new List1 {ID = 1, Person = "Alice"},
                new List1 {ID = 2, Person = "Bob"},
                new List1 {ID = 3, Person = "Carol"},
                new List1 {ID = 4, Person = "Dave"}
             };


var lList2 = new List<List2>
             {
               new List2 {ID = 1, Value = 15},
               new List2 {ID = 1, Value = 19},
               new List2 {ID = 2, Value = 5},
               new List2 {ID = 2, Value = 7},
               new List2 {ID = 2, Value = 20},
               new List2 {ID = 4, Value = 16}
             };

var lOutput = lList1.SelectMany(pArg => 
                             lList2.Where(pArg1 => pArg1.ID == pArg.ID)
                                   .DefaultIfEmpty(new List2 { ID = pArg.ID, Value = 0})
                                   .Select(pArg1 => pArg1));
person Vasu Balakrishnan    schedule 05.06.2009

Э-э... Кажется, было бы просто использовать «Содержит», не так ли?

foreach (Key key in List1.Keys)
{
   if (!List2.Keys.Contains(key)) List2.Add(key, "0");
}

Это не будет иметь проблем с дубликатами ключей в List1.

person genki    schedule 05.06.2009

Реализация LINQ

public class Master
{
    public int ID;
}

public class Detail
{
    public int ID;
    public int Count;
}

public static void AddMissingDetails(IEnumerable<Master> masters, List<Detail> details)
{
    AddMissingDetails(masters, details, x => new Detail
        {
            ID = x,
            Count = 0
        });
}

public static void AddMissingDetails(IEnumerable<Master> masters, List<Detail> details, Func<int, Detail> createDefault)
{
    details.AddRange(
        masters
            .Select(x => x.ID)
            .Except(details.Select(x => x.ID).Distinct())
            .Select(createDefault));
}
person Handcraftsman    schedule 12.06.2009

Вам может не понравиться мое решение. Но я хотел бы поблагодарить вас за этот пост. Это дало мне возможность сделать несколько полезных вещей с помощью linq.

Я использую метод расширения и Linq, чтобы добавить недостающие элементы в ваш целевой список (List2). Я не уверен, что вы работаете над фреймворком 3.0/3.5, если да, то это решение подойдет вам, и это также «умный, чистый и эффективный способ сделать это» :).

 public static void MergeLists() {
      var listOne=new List<List1> {
        new List1 {ID=1, Person="Alice"},
        new List1 {ID=2, Person="Bob"},
        new List1 {ID=3, Person="Carol"},
        new List1 {ID=4, Person="Dave"},
        new List1 {ID=5, Person="Dave2"},
        new List1 {ID=6, Person="Dave3"},
      };
      var listTwo=new List<List2> {
        new List2 {ID=1, Value=15},
        new List2 {ID=1, Value=19},
        new List2 {ID=2, Value=5},
        new List2 {ID=2, Value=7},
        new List2 {ID=2, Value=20},
        new List2 {ID=4, Value=16}
      };

      var listTwoWithAddedItems=listOne.AddMissingItems(listTwo, (item1, item2) => item1.ID==item2.ID,
                                                    item2 => new List2 { ID=item2.ID, Value=-1 }).ToList();//For this value, you can put whatever default value you want to set for the missing items.

      Console.Read();
    }

     public static class AmbyExtends {

        public static List<Target> AddMissingItems<Source, Target>(this IEnumerable<Source> source, List<Target> target, Func<Source, Target, bool> selector, Func<Source, Target> creator) {
          foreach(var item in source) {
            if(!target.Any(x=>selector(item,x))) {
              target.Add(creator(item));
            }         
          }
          return target;
        }
      }
person Manish Basantani    schedule 13.06.2009

Используя SQL в базе данных, решение будет таким:

INSERT INTO List2 (ID)
SELECT      l1.ID
FROM        List1 l1
LEFT JOIN   List2 l2
        ON  l1.ID = l2.ID
WHERE       l2.ID IS NULL

Здесь предполагается, что другие столбцы в таблице List2 либо NOT NULL, либо имеют ограничение значения DEFAULT.

person van    schedule 05.06.2009
comment
Ответ должен быть в LINQ, а не в SQL, извините. - person Kane; 05.06.2009
comment
Если вам нужен LINQ, то в чем проблема?: Вы можете получить результаты, как вы упомянули в вопросе. Затем используйте те, что для тех, у которых нет совпадений, чтобы создать новые объекты (или добавить их в списки) - person van; 05.06.2009
comment
И это именно то, о чем я спрашиваю: P Как бы вы сделали это чисто и эффективно? - person Svish; 05.06.2009

person    schedule
comment
Наиболее подходящим языком является T-SQL, а не LINQ. - person richardtallent; 05.06.2009
comment
Не могу ничего вставить в базу. И я не могу использовать SQL =/ - person Svish; 05.06.2009