如何通过 id 合并多个列表并获取特定数据?
How to merge multiple list by id and get specific data?
我有 3 个具有共同 ID 的列表。我需要在一个列表中按对象分组,并从其他两个列表中提取数据。举个例子加深理解
table 组名:
| Id | Name |
|--------------|
| 1 | Hello |
| 2 | Hello |
| 3 | Hey |
| 4 | Dude |
| 5 | Dude |
table 对于 countId:
| Id | whatever |
|---------------|
| 1 | test0 |
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
| 3 | test4 |
table 上次:
| 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 只查询其他对象数组以获得相同的 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 的解决方案。
是的,正如评论所说,请仔细考虑 LINQ 是否是您的最佳选择。虽然它有效,但它的性能并不总是比“简单”的 foreach 更好。 LINQ 的主要卖点是并且一直是保持可读性的简短单行查询语句。
我想我会为此跳过 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 space 是完整的,它总是存在的(我们永远不会在 groupNames 中没有的计数中有一个 id)
对于计数,我们不关心任何其他数据;仅存在 id 就足以增加计数
对于日期,这是一个简单的最大值算法,即“如果已知最大值小于新最大值,则已知最大值 = 新最大值”。如果你知道你的日期列表是升序排列的,你也可以跳过它..
嗯,有了
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<T>
(我的意思是countId
和lastTime
)不是高效的 这里的数据结构。在 Linq 查询中,我们必须扫描它们以获得 Sum
和 Max
。如果 countId
和 lastTime
是 long,将它们(通过 grouping)变成 Dictionary<int, T>
with id
正在 Key
我有 3 个具有共同 ID 的列表。我需要在一个列表中按对象分组,并从其他两个列表中提取数据。举个例子加深理解
table 组名:
| Id | Name |
|--------------|
| 1 | Hello |
| 2 | Hello |
| 3 | Hey |
| 4 | Dude |
| 5 | Dude |
table 对于 countId:
| Id | whatever |
|---------------|
| 1 | test0 |
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
| 3 | test4 |
table 上次:
| 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 只查询其他对象数组以获得相同的 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 的解决方案。
是的,正如评论所说,请仔细考虑 LINQ 是否是您的最佳选择。虽然它有效,但它的性能并不总是比“简单”的 foreach 更好。 LINQ 的主要卖点是并且一直是保持可读性的简短单行查询语句。
我想我会为此跳过 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 space 是完整的,它总是存在的(我们永远不会在 groupNames 中没有的计数中有一个 id)
对于计数,我们不关心任何其他数据;仅存在 id 就足以增加计数
对于日期,这是一个简单的最大值算法,即“如果已知最大值小于新最大值,则已知最大值 = 新最大值”。如果你知道你的日期列表是升序排列的,你也可以跳过它..
嗯,有了
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<T>
(我的意思是countId
和lastTime
)不是高效的 这里的数据结构。在 Linq 查询中,我们必须扫描它们以获得 Sum
和 Max
。如果 countId
和 lastTime
是 long,将它们(通过 grouping)变成 Dictionary<int, T>
with id
正在 Key