如何通过 id 合并多个列表并获取特定数据?

Posted

技术标签:

【中文标题】如何通过 id 合并多个列表并获取特定数据?【英文标题】:How to merge multiple list by id and get specific data? 【发布时间】:2022-01-18 16:57:11 【问题描述】:

我有 3 个具有通用 ID 的列表。我需要在一个列表中按对象分组,并从其他两个列表中提取数据。举个例子来加深理解

组名表:

| Id | Name    | 
|--------------|
| 1  | Hello   |
| 2  | Hello   |
| 3  | Hey     |
| 4  | Dude    |
| 5  | Dude    |

countId 表:

| Id | whatever | 
|---------------|
| 1  | test0    |
| 1  | test1    |
| 2  | test2    |
| 3  | test3    |
| 3  | test4    |

上次的表格:

| Id | timestamp  | 
|-----------------|
| 1  | 1636585230 |
| 1  | 1636585250 |
| 2  | 1636585240 |
| 3  | 1636585231 |
| 3  | 1636585230 |
| 5  | 1636585330 |

我希望得到这样的列表

| Name    | whateverCnt | lastTimestamp | 
|---------------------------------------|
| Hello   | 3           | 1636585250    |
| Hey     | 2           | 1636585231    |
| Dude    | 0           | 1636585330    |

现在我有类似的东西,但它不起作用

            return groupNames
              .GroupBy(x => x.Name)
              .Select(x =>
              
                  return new myElem
                  
                      Name = x.Name,
                      lastTimestamp = new DateTimeOffset(lastTime.Where(a => groupNames.Where(d => d.Name == x.Key).Select(d => d.Id).Contains(a.Id)).Max(m => m.timestamp)).ToUnixTimeMilliseconds(),
                      whateverCnt = countId.Where(q => (groupNames.Where(d => d.Name == x.Key).Select(d => d.Id)).ToList().Contains(q.Id)).Count()
                    ;
              )
             .ToList();

非常感谢您的建议。

【问题讨论】:

为什么要强制使用 LINQ? 据我所知,它具有更好的性能。这只是一个例子,我的列表比我在这个例子中使用的更大和不同。 ***.com/a/47262860/12999914 奇数;我认为 LINQ 解决方案的性能通常不如直接但更冗长的替代方案。如果您的列表真的“更大”,我想我会仔细考虑我将哪些部分(哈哈)推迟到 LINQ LINQ 的性能更好或更差,具体取决于您使用它的方式以及您要解决的具体问题。 当你说“桌子”时,你是什么意思?举例说明这些数据是如何存放在正在运行的程序的内存中的 【参考方案1】:

我想我大多会为此跳过 LINQ

class Thing
  public string Name get;set;
  public int Count get;set;
  public long LastTimestamp get;set;


...

var ids = new Dictionary<int, string>();
var result = new Dictionary<string, Thing>();
foreach(var g in groupNames) 
  ids[g.Id] = g.Name;
  result[g.Name] = new Whatever  Name = n ;


foreach(var c in counts)
  result[ids[c.Id]].Count++;

foreach(var l in lastTime)
  var t = result[ids[l.Id]];
  if(t.LastTimeStamp < l.Timestamp) t.LastTimeStamp = l.TimeStamp;

我们开始制作两个字典(你可以 ToDictionary 这个)。如果 groupNames 已经是一个映射 id:name 的字典,那么你可以跳过制作 ids 字典,直接使用 groupNames。这让我们可以快速从 ID 查找到 Name,但我们实际上想将结果收集到 name:something 映射中,所以我们也制作了其中一个。做result[name] = thing 总是成功,即使我们以前见过name。如果您愿意,我们可以在此处使用 ContainsKey 检查跳过某些对象创建

然后我们需要做的就是枚举我们的其他 N 个集合,构建结果。我们想要的结果是从result[ids[some_id_value_here]] 访问的,并且如果 groupnames id 空间完整,它总是存在(我们永远不会在 groupNames 中没有的计数中有 id)

对于计数,我们不关心任何其他数据;仅 id 的存在就足以增加计数

对于日期,这是一个简单的最大算法“如果已知最大值小于新最大值,则使已知最大值 = 新最大值”。如果您知道您的日期列表是按升序排序的,您也可以跳过它。

【讨论】:

【参考方案2】:

在您的示例中,最安全的是最后一个指定对象的列表,并且只需 LINQ 查询其他对象数组以获得相同的 id。

类似

public IEnumerable<SomeObject> MergeListsById(
  IEnumerable<GroupNames> groupNames,
  IEnumerable<CountId> countIds,
  IEnumerable<LastTime> lastTimes)

  IEnumerable<SomeObject> mergedList = new List<SomeObject>();

  groupNames.ForEach(gn => 
    mergedList.Add(new SomeObject 
      Name = gn.Name,
      whateverCnt = countIds.FirstOrDefault(ci => ci.Id == gn.Id)?.whatever,
      lastTimeStamp = lastTimes.LastOrDefault(lt => lt.Id == gn.Id)?.timestamp
    );
  );

  return mergedList;

在 Fiddle 或一次性项目中试用它并根据您的需要进行调整。为了可读性和可维护性,这里可能不需要纯 LINQ 的解决方案。

是的,正如 cmets 所说,请仔细考虑 LINQ 是否是您的最佳选择。虽然它有效,但它的性能并不总是比“简单”的 foreach 更好。 LINQ 的主要卖点是并且一直都是简短的、保持可读性的单行查询语句。

【讨论】:

【参考方案3】:

嗯,有

  List<(int id, string name)> groupNames = new List<(int id, string name)>() 
    ( 1, "Hello"),
    ( 2, "Hello"),
    ( 3, "Hey"),
    ( 4, "Dude"),
    ( 5, "Dude"),
  ;

  List<(int id, string comments)> countId = new List<(int id, string comments)>() 
    ( 1  , "test0"),
    ( 1  , "test1"),
    ( 2  , "test2"),
    ( 3  , "test3"),
    ( 3  , "test4"),
  ;

  List<(int id, int time)> lastTime = new List<(int id, int time)>() 
    ( 1  , 1636585230 ),
    ( 1  , 1636585250 ),
    ( 2  , 1636585240 ),
    ( 3  , 1636585231 ),
    ( 3  , 1636585230 ),
    ( 5  , 1636585330 ),
  ;

从技术上讲,您可以使用下面的 Linq

var result = groupNames
  .GroupBy(item => item.name, item => item.id)
  .Select(group => (Name          : group.Key,
                    whateverCnt   : group
                      .Sum(id => countId.Count(item => item.id == id)),
                    lastTimestamp : lastTime
                      .Where(item => group.Any(g => g == item.id))
                      .Max(item => item.time)));

让我们看看:

Console.Write(string.Join(Environment.NewLine, result));

结果:

(Hello, 3, 1636585250)
(Hey, 2, 1636585231)
(Dude, 0, 1636585330)

但要小心List&lt;T&gt;(我的意思是countIdlastTime)在这里不是高效的数据结构。在 Linq 查询中,我们必须扫描它们以获取 SumMax。如果countIdlastTimelong,则将它们(通过分组)变成Dictionary&lt;int, T&gt;idKey

【讨论】:

以上是关于如何通过 id 合并多个列表并获取特定数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Django 获取特定的 id 数据(多个 id 存储在列表中)

如何实现一个批量获取数据的dataloader,合并多个操作

如何获取通过特定频道订阅的模型的 ID

如何在android中获取特定列表视图项目的ID?

如何在pyspark中按列合并多个数据框?

从多个 csv 文件中获取一个特定列并合并为一个