使用linq来匹配集合中的成对行

本文关键字:集合 linq 使用 | 更新日期: 2024-10-20 11:28:33

在系统中,我使用RowMod标志对新旧行中的数据进行修改,例如删除、添加、更新和未更改的行显示为:

RowID Data    RowMod
Row1  "fish"  "" 
Row1  "fish"  "D" 
Row2  "cat"   "A"
Row3  "fox"   ""
Row3  "dog"   "U"
Row4  "mouse" ""

我想使用每一行的RowID来匹配这些,并得到类似的东西:

RowID OldData NewData RowMod
Row1  "fish"  null    "D"
Row2  null    "cat"   "A"
Row3  "fox"   "dog"   "U"
Row4  "mouse" "mouse" ""

使用linq来匹配集合中的成对行

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<DataRow> rows = new[]
        {
            new DataRow(1,"fish",""),
            new DataRow(1,"fish","D"),
            new DataRow(2,"cat","A"),
            new DataRow(3,"fox",""),
            new DataRow(3,"dog","U"),
            new DataRow(4,"mouse","")
        };
        var result = rows
            .GroupBy(x => x.Id)
            .Select(g => new 
            {
                Count = g.Count(),
                Id = g.First().Id,
                FirstRow = g.First(),
                LastRow = g.Last() 
            }).Select(item => new 
            {
                RowId = item.Id,
                OldData = item.Count == 1 && item.FirstRow.RowMod != "" ? null : item.FirstRow.Data,
                NewData = item.LastRow.RowMod == "D" ? null : item.LastRow.Data,
                RowMod = item.LastRow.RowMod 
            });
            //Or using query syntax
            var result2 = from x in rows
                          orderby x.Id, x.RowMod
                          group x by x.Id into g
                          select new
                          {
                              RowId = g.First().Id,
                              OldData = g.Count() == 1 && g.First().RowMod != "" ? null : g.First().Data,
                              NewData = g.Last().RowMod == "D" ? null : g.Last().Data,
                              RowMod = g.Last().RowMod
                          };
        // Test
        Console.WriteLine("RowID'tOldData'tNewData'tRowMod");
        foreach (var item in result)
        {
            Console.WriteLine("{0}'t'{1}''t'{2}''t'{3}'",item.RowId,item.OldData ?? "null",item.NewData ?? "null",item.RowMod);
        }
    }
}
public class DataRow
{
    public int Id { get; set; }
    public string Data { get; set; }
    public string RowMod { get; set; }
    public DataRow(int id, string data, string rowMod)
    {
        Id = id;
        Data = data;
        RowMod = rowMod;
    }
}

输出:

RowID   OldData NewData RowMod
1       'fish'  'null'  'D'
2       'null'  'cat'   'A'
3       'fox'   'dog'   'U'
4       'mouse'  'mouse' ''

我不确定这是否是实现您需求的最佳方式,但这就是我所拥有的:-

var result = rows.GroupBy(x => x.RowId)
                 .Select(x => 
          {
             var firstData = x.FirstOrDefault();
             var secondData = x.Count() == 1 ? x.First().RowMod == "A" ? firstData : null
                                             : x.Skip(1).FirstOrDefault();
          return new
          {
              RowId = x.Key,
              OldData = firstData.RowMod == "A" ? null : firstData.Data,
              NewData = secondData != null ? secondData.Data : null,
              RowMod = String.IsNullOrEmpty(firstData.RowMod) && secondData != null ?
                                      secondData.RowMod : firstData.RowMod
          };
      });

工作Fiddle。

获取目标对象的两个部分可以迭代完成:

foreach(var rowId in myList.Select(x => x.RowId).Distinct())
{
    //get the left item
    var leftItem = myList.SingleOrDefault(x => x.RowId == rowId && String.IsNullOrWhiteSpace(x.rowmod);
    //get the right item
    var rightItem = myList.SingleOrDefault(x => x.RowId == rowId && !String.IsNullOrWhiteSpace(x.rowmod);
}

您的问题没有具体说明如何创建第二个对象。这是另一个班吗?无论哪种方式,您都可以从上面的片段中推断出,如果原始集合中不存在任何一项,则该项都可能是null

你所需要做的就是使用那些找到的对象来创建你的新对象。

虽然我非常喜欢LINQ,但我认为这里不合适,因为您希望在迭代时缓冲一些值。如果使用LINQ执行此操作,往好了说它性能不好,往坏了说它会多次迭代集合。在我看来,这样看起来也干净多了。

IEnumerable<TargetClass> MapOldValues(IEnumerable<SourceClass> source)
{
    var buffer = new Dictionary<string, string>();
    foreach(var item in source)
    {
        string oldValue;
        buffer.TryGetValue(item.RowId, out oldValue); 
        yield return new TargetClass
                          {
                              RowId = item.RowId, 
                              OldData = oldValue, 
                              NewData = (item.RowMod == "D" ? null : item.Data), 
                              RowMod = item.RowMod  };
        // if the rows come sorted by ID, you can clear old values from
        // the buffer to save memory at this point:
        // if(oldValue == null) { buffer.Clear(); }
        buffer[item.RowId] = item.Data;
    }
}

如果你只想要最新的更新,你可以使用LINQ:

var latestChanges = MapOldValues(source).GroupBy(x => x.RowId).Select(x => x.Last());

我想还有更优雅的方法可以做到这一点,但这会产生您期望的输出:

public class MyClass
            {
                public int RowID { get; set; }
                public string Data { get; set; }
                public string RowMod { get; set; }
            }
  var result = (from id in myList.Select(x => x.RowID).Distinct()
            let oldData = myList.Where(x => x.RowID == id).SingleOrDefault(x => x.RowMod.Equals("")) != null
                ? myList.Where(x => x.RowID == id).Single(x => x.RowMod.Equals("")).Data
                : null
            let newData = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
                ? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).Data
                : null
            let rowMod = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
                ? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).RowMod
                : null
            select new
                   {
                       RowID = id,
                       OldData = oldData,
                       NewData = rowMod == null ? oldData : rowMod.Equals("D") ? null : newData,
                       RowMod = rowMod
                   });
foreach (var item in result)
            {
                Console.WriteLine("{0} {1} {2} {3}", item.RowID, item.OldData ?? "null", item.NewData ?? "null", item.RowMod ?? "-");
            }