我有一个项目列表(List<Tasks> tasks
),如下所示:
Id Action Source Target
------- --------- -------- ---------
1 Save 12 18
4 Save 18 21
7 Save 21 23
6 Save 23 25
10 Save 25 27
16 Save 29 31
0 Edit 31 37
我想要做的是合并具有相同(Source
和Target
)和相同Action
的行。例如,我最终需要的是:
Id Action Source Target
------- --------- -------- ---------
22 Save 12 27
16 Save 29 31
0 Edit 31 37
这意味着,具有相同Action
的所有项目(在我的情况下,此处保存)应合并为一行/项,但仅限,以防Target
上面项的值等于跟随项的Source
值。跟随者是较低的项目。
例如,upper是项Id = 1
,lower / follower是项Id = 4
。所以记录如下。
有没有办法用linq避免太多的foreach循环? 也许类似于SQL中的CTE层次结构。但我仍然没有找到正确的语法,所以我没有粘贴任何代码。
提前致谢!
答案 0 :(得分:1)
如果你想要&#34; LINQ&#34;解决方案,你可以像这样使用dat3 <- dat %>%
# Create Run Length ID
mutate(ID = rleid(Index1, Index2)) %>%
group_by(ID) %>%
# Filter groups with n > 1
filter(n() > 1) %>%
# Summarise the data by first and last value of each group
summarise(Time = paste(first(Time), last(Time), sep = ", "),
TempRange = paste(first(TempC), last(TempC), sep = ", "),
TempDiff = abs(first(TempC) - last(TempC))) %>%
ungroup() %>%
select(-ID)
dat3
# # A tibble: 3 x 3
# Time TempRange TempDiff
# <chr> <chr> <dbl>
# 1 2, 3 25.2, 25.6 0.400
# 2 4, 6 25, 23.6 1.40
# 3 7, 8 28.9, 30 1.10
:
Aggregate
它以空var result = tasks.Aggregate(new List<Item>(), (acc, current) =>
{
if (acc.Count > 0)
{
var prev = acc[acc.Count - 1];
if (prev.Action == current.Action && prev.Target == current.Source)
{
// update previous target
prev.Target = current.Target;
}
// otherwise just add
else acc.Add(current);
}
else acc.Add(current);
return acc;
});
作为累加器开始,逐个输入项目。然后我们只是将项目添加到累加器,如果它们不匹配标准,如果它们匹配 - 我们会更新以前的项目。
答案 1 :(得分:1)
看看MoreLinq
。有一个名为Segment
的函数,它根据某些条件将序列拆分为子序列:
var grouped = tasks
.GroupBy(t => t.Action, (k, g) => g
.Segment((s, f, a) => s.Source != f.Target)
.Select(c => new
{
c.First().Source,
c.Last().Target,
Action = k
})));
因此,当s.Source != f.Target
(f
是第一个元素且s
是一对中的第二个元素)时,序列被分割并在每个相邻对上创建新的子序列。
答案 2 :(得分:1)
类似于CTE查询。首先选择种子节点(没有指向SOURCE的记录)然后在do-wile循环中更改Targets以获取每个链的最终目标。不需要先前的订单。
public class Tasks
{
public int Id;
public string Action;
public int Source;
public int Target;
}
static void Main(string[] args)
{
List<Tasks> tasks = new List<Tasks>{
new Tasks{Id=1,Action="Save",Source= 12,Target=18},
new Tasks{Id=4,Action="Save",Source= 18,Target=21},
new Tasks{Id=7,Action="Save",Source= 21,Target=23},
new Tasks{Id=6,Action="Save",Source= 23,Target=25},
new Tasks{Id=10,Action="Save",Source= 25,Target=27},
new Tasks{Id=16,Action="Save",Source= 29,Target=31},
new Tasks{Id=0,Action="Edit",Source= 31,Target=37},
};
var collectTasks = (from t in tasks
where !tasks.Any(t1 => (t1.Target == t.Source)&&(t1.Action == t.Action)&&(t1.Id!=t.Id))
select t).ToList();
foreach (var ct in collectTasks)
{
do{
var t1 = from t in tasks where ((ct.Target == t.Source)&&(ct.Action == t.Action)&&(ct.Id!=t.Id)) select t;
if (t1.Count() == 0) { break; }
ct.Target = t1.First().Target;
} while (true);
}
foreach (var t in collectTasks)
{
Console.WriteLine("Action = {0}, Source = {1}, Target = {2}", t.Action, t.Source, t.Target);
}
}
答案 3 :(得分:0)
您只需要一个循环使用索引来访问列表项,从1
开始,如果前一项的Action
匹配和Target
等于{{Source
,则将当前项与前一项合并1}}当前:
for (int i = 1; i < items.Count; i++)
if (items[i].Action == items[i - 1].Action && items[i].Source == items[i - 1].Target)
{
items[i - 1].Target = items[i].Target;
items.RemoveAt(i);
i--; // to have same index after i++
}
这不会改变Id
,因此您写的时候会1
而不是22
。
答案 4 :(得分:0)
请尝试以下操作:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication33
{
class Program
{
static void Main(string[] args)
{
List<Task> tasks = new List<Task>() {
new Task() { Id = 1, Action = "Save", Source = 12, Target = 18},
new Task() { Id = 4, Action = "Save", Source = 18, Target = 21},
new Task() { Id = 7, Action = "Save", Source = 21, Target = 23},
new Task() { Id = 6, Action = "Save", Source = 23, Target = 25},
new Task() { Id = 10, Action = "Save", Source = 25, Target = 27},
new Task() { Id = 16, Action = "Save", Source = 29, Target = 31},
new Task() { Id = 0, Action = "Edit", Source = 31, Target = 37}
};
for(int i = tasks.Count - 1; i >= 0; i--)
{
int source = tasks[i].Source;
List<int> match = tasks.Select((x, index) => new { x = x, i = index }).Where(x => (x.x.Target == source) && (tasks[i].Action == tasks[x.i].Action)).Select(x => x.i).ToList();
if (match.Count > 0)
{
tasks[match[0]].Target = tasks[i].Target;
tasks.RemoveAt(i);
}
}
}
}
public class Task
{
public int Id { get; set; }
public string Action { get; set; }
public int Source { get; set; }
public int Target { get; set; }
}
}
答案 5 :(得分:0)
此程序将在一次通过中执行您所需的操作,同时保留&#34; Id&#34;无法合并的项目的值。
你会看到它在有序列表上工作,所以严格来说,那里有一些LINQ隐藏的活动;但一般来说,你会发现LINQ查询产生的开销会使得总是比数组上的for循环慢。
private List<Task> Merge(List<Task> list)
{
var result = new List<Task>();
var maxId = list.Max(x => x.Id) + 1;
// order list for this algo to work
var listO = list.OrderByDescending(x => x.Action).ThenBy(x => x.Source).ToArray();
// create seed and counter
Task seed = listO[0];
var ctr = 0;
for (var i = 0; i < listO.Length - 1; i++)
{
if (listO[i + 1].Source == listO[i].Target && listO[i + 1].Action == listO[i].Action && listO[i + 1].Action == seed.Action)
{
// if the next is the last, merge it now
if (i + 1 == listO.Length - 1)
result.Add(new Task() { Id = maxId++, Action = seed.Action, Source = seed.Source, Target = listO[i].Target });
// the next item qualifies for merge, move to next
ctr++;
continue;
}
// next item does not qualify for merge, merge what we have or just add the item if ctr == 0
result.Add(ctr == 0 ? seed : new Task() { Id = maxId++, Action = seed.Action, Source = seed.Source, Target = listO[i].Target });
// reset seed record + counter
seed = listO[i+1];
ctr = 0;
// if the next item is the last, it belongs in the list as is
if (i + 1 == listO.Length - 1) result.Add(seed);
}
return result;
}