C # - combining list items into one item based on some matching values

I have a list of items ( List<Tasks> tasks ), like this one:

 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 

What I want to do is combine the lines that have the same ( Source and Target ) and the same Action . For example, what I need at the end should look like this:

 Id Action Source Target ------- --------- -------- --------- 22 Save 12 27 16 Save 29 31 0 Edit 31 37 

This means that all elements with the same Action (in my case here Save) should be combined into one line / element , but only if the Target value of the top item is equal to the Source value of the successor element. The follower is the bottom element.

For example, the top is the element Id = 1 , and the bottom / successor is the element Id = 4 . So the next entry.

Is there a way to do this with linq, avoiding too many foreach loops? Perhaps something like a Hierarchy with CTE in SQL. But I still can’t find the correct syntax, so why didn’t I paste the code.

Thanks in advance!

+5
source share
6 answers

If you want a “LINQ” solution, you can use Aggregate as follows:

 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; }); 

It starts with an empty List as a battery and feeds items one by one. Then we simply add items to the drive if they do not meet the criteria, and if they match, we update the previous item instead.

+1
source

To access list items, you need to use only one cycle, starting from 1 and combining the current item with the previous one, if their correspondence Action and Target previous item is equal to the Source current one:

 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++ } 

This would not change the Id , so it will be 1 , not 22 , as you wrote.

0
source

Try the following:

 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 => (xxTarget == source) && (tasks[i].Action == tasks[xi].Action)).Select(x => xi).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; } } } 
0
source

Take a look at MoreLinq . There is a function called Segment that breaks a sequence into subsequences based on some condition:

 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 }))); 

So, the sequence is divided and a new subsequence of each adjacent pair is created when s.Source != f.Target ( f is the first element and s is the second in the pair).

0
source

Something like a CTE request. First select the seed nodes (there are no records pointing to SOURCE), and then in the “Loop” setting in the loop to get the final goal of each chain. No previous order is required.

 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); } } 
0
source

This procedure will do what you need in a single pass mode, storing the "Id" values ​​of elements that cannot be combined.

You will see that he is working on an ordered list, so, strictly speaking, there is some kind of hidden LINQ activity; but, as a rule, you will find that LINQ queries incur overhead, which makes it always slower than for-loops over array.

  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; } 
0
source

Source: https://habr.com/ru/post/1276245/


All Articles