C# - 根据一些匹配值将列表项合并为一个项

时间:2018-04-04 08:54:07

标签: c# list linq merge

我有一个项目列表(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

我想要做的是合并具有相同(SourceTarget)和相同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层次结构。但我仍然没有找到正确的语法,所以我没有粘贴任何代码。

提前致谢!

6 个答案:

答案 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.Targetf是第一个元素且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;
    }