Optimising a Get Next Value List function - c#

We retrieve a list of objects from a database and cannot rely on the Id order to guarantee they will be in the right sequence, as objects may have been edited, deleted etc.
They look like this:
Id NextId
1 3
2 0
3 17
17 2
So the correct order is 1, 3, 17, 2.
I came up with this code to solve the problem:
long lastStep = steps.Single(x => x.NextId == 0).Id;
//Probably should be a guard clause for nulls
List<MyObject> orderedSteps = new List<MyObject>();
int retries = 0;
do
{
foreach (var entry in steps)
{
if (lastStep == entry.NextId) orderedSteps.Add(entry);
retries++;
}
} while (orderedSteps.Count() < steps.Count() && retries < 10000);
//Flip the order so it runs first to last
orderedSteps.Reverse();
return orderedSteps;
I think this works...but it feels kind of hacky, and that there's a more safe and efficient way of doing it.
Any Suggestions? Thanks!

You could do this directly in the database using a recursive CTE:
WITH SequenceQuery (Id, NextId, Ordering)
AS
(
SELECT Id,
NextId,
0 AS Ordering
FROM Steps
WHERE Id = 1
UNION ALL
SELECT Steps.Id,
Steps.NextId,
SequenceQuery.Ordering + 1 AS Ordering
FROM SequenceQuery INNER JOIN Steps
ON SequenceQuery.NextId = Steps.Id
)
SELECT *
FROM SequenceQuery
ORDER BY Ordering
In the event of a cycle, this will return an error once it hits the maximum recursion depth. The maximum depth is by default 100; if your data set could legitimately have more than 100 elements, you can increase the limit with the following clause (which goes right at the end of the query, after the SELECT statement):
OPTION (MAXRECURSION 1000) -- (for example)
This will be by far the fastest way to get the data back, provided that the Id column is properly indexed.
If you prefer to do it in code, then you'll need to load the entire table into a dictionary beforehand and then walk through it. The advantage to this is that you can explicitly detect cycles instead of depending on a numeric limit to the number of levels.
var steps = ...;
var stepById = steps.ToDictionary(step => step.Id);
var stepsInOrder = new List<int>();
var visited = new HashSet<int>();
// Make sure that when we hit 0, we'll definitely stop.
Debug.Assert(!stepsInOrder.ContainsKey(0));
int currentStepId = 1;
while (stepById.TryGetValue(currentStepId, out Step step))
{
stepsInOrder.Add(currentStepId);
int nextStepId = step.NextId;
if (!visited.Add(nextStepId))
throw new Exception($"Cycle found at step {nextStepId}");
currentStepId = nextStepId;
}
(SQL tested, C# code untested)

Here's my solution. Requires several assumptions to be true: Single chain, terminated with a 0 Id.
public class Item
{
public int Id;
public int NextId;
public override string ToString()
{
return string.Format("Item {0} (links to {1})", Id, NextId);
}
};
class Program
{
static void Main(string[] args)
{
Item[] items = new Item[] {
new Item() { Id = 1, NextId = 3 },
new Item() { Id = 2, NextId = 0 },
new Item() { Id = 3, NextId = 17 },
new Item() { Id = 17, NextId = 2 }
};
Dictionary<int, int> idToIndex = new Dictionary<int, int>();
int headId = 0;
for (int index = 0; index < items.Length; ++index)
{
idToIndex.Add(items[index].Id, index);
headId = headId ^ items[index].NextId ^ items[index].Id;
}
int currentId = headId;
while (currentId != 0)
{
var item = items[idToIndex[currentId]];
Console.WriteLine(item);
currentId = item.NextId;
}
}
}

My suggestion is as follows:
class MyObject
{
public long Id;
public long NextId;
public override string ToString() => Id.ToString();
};
public void q48710242()
{
var items = new[]
{
new MyObject{ Id = 1, NextId = 3 },
new MyObject{ Id = 2, NextId = 0 },
new MyObject{ Id = 3, NextId = 17 },
new MyObject{ Id = 17, NextId = 2 }
};
var nextIdIndex = items.ToDictionary(item => item.NextId);
var orderedSteps = new List<MyObject>();
var currentStep = new MyObject() { Id = 0 };
while (nextIdIndex.TryGetValue(currentStep.Id, out currentStep))
{
orderedSteps.Add(currentStep);
}
orderedSteps.Reverse();
var output = string.Join(", ", orderedSteps);
}
Returns:
output = "1, 3, 17, 2"
This uses a dictionary to build an index of the items as in Jonathan's answer, but by using NextId as the key. The algorithm then proceeds backwards from the 0 as in the original question to build the list in reverse. This approach has no problems with loops in the data as any such loop will never be entered assuming that Id is unique.
If the data contains multiple elements with the same NextId, then it forms a tree structure:
var items = new[]
{
new { Id = 1, NextId = 3 },
new { Id = 2, NextId = 0 },
new { Id = 3, NextId = 17 },
new { Id = 17, NextId = 2 },
new { Id = 100, NextId = 2 }
};
This will cause the .ToDictionary() call to fail with System.ArgumentException: An item with the same key has already been added.
If the data contains no entries with a NextId equal to 0, it will return an empty list.
Update Changed to return a list of objects rather than the indices.
Hope this helps

Related

How to group the Similar value array in c#

class Program
{
static void Main(string[] args)
{
int[] arr1 = { 1, 2, 3, 4 };
int[] arr2 = { 1, 2, 3 };
int[] arr3 = { 1, 2, 3 };
int[] arr4 = { 1, 2, 3, 4 };
int[] arr5 = { 1, 2, 3, 4 };
int[] arr6 = { 1, 2, 3, 4, 5 };
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
// I want to Output like this based on same values in details object.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
}
}
public class Order
{
public int OrderId { get; set; }
public int[] Deatils { get; set; }
}
I want to output like this because OrderId-1,4,5 have the same values in Details.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
This extension method is a generic generalization of the solution provided by Michael Liu here which orders elements prior to hash code generation and will work for any T which itself correctly implements GetHashCode() and Equals():
static class Extensions
{
public static int GetCollectionHashCode<T>(this IEnumerable<T> e)
{
return ((IStructuralEquatable)e.OrderByDescending(x => x).ToArray())
.GetHashCode(EqualityComparer<T>.Default);
}
}
You can utilize it like so:
var orders = new[] { Order1, Order2, Order3, Order4, Order5, Order6 };
var groups = orders
.Where(x => x.Deatils != null)
.GroupBy(x => x.Deatils.GetCollectionHashCode())
.Select(x => x.Select(y => y.OrderId));
Note: Deatils typo above is intentional as per OP.
You could use LINQ as the other answer provided or create a helper method to group them for you.
public static IEnumerable<IEnumerable<T>> Aggregate<T>(IEnumerable<T> ungrouped, Func<T, T, bool> Expression)
{
// create a place to put the result
List<List<T>> groupedItems = new();
// create a mutable storage to keep track which ones we shouldn't compare
List<T> mutableBag = ungrouped.ToArray().ToList();
// n^2 isn't great but it was easy to implement, consider creating a hash from all elements and use those instead of manual comparisons
foreach (var left in ungrouped)
{
// create a place to store any similar items
List<T> similarItems = new();
// compare element to every remaining element, if they are similar add the other to the similar list
foreach (var right in mutableBag)
{
if (Expression(left, right))
{
similarItems.Add(right);
}
}
// if we didn't find any similar items continue and let GC collect similar items
if (similarItems.Count == 0)
{
continue;
}
// since we found items remove the from the mutable bag so we don't have duplicate entries
foreach (var item in similarItems)
{
mutableBag.Remove(item);
}
// add the similar items to the result
groupedItems.Add(similarItems);
}
return groupedItems;
}
public static bool Compare<T>(T[] Left, T[] Right)
{
// this compare method could be anything I would advise against doing member comparison like this, this is simplified to provide how to use the Aggregate method and what the CompareMethod might look like for comlpex types
if (Left.Length != Right.Length)
{
return false;
}
foreach (var item in Left)
{
if (Right.Contains(item) is false)
{
return false;
}
}
return true;
}
For the given example
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
Order[] arrs = { Order1, Order2, Order3, Order4, Order5, Order6 };
We could use this new helper method like this:
var groupedItems = Aggregate(arrs, (left, right) => Compare(left.Deatils, right.Deatils));

C# sort object list with start position and loop

I have a strange question :)
I have a object list looking like this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
I would like to sort the list providing a "start id"
For example, if I provide "start id" 3, the result should look like this:
Id
Name
3
Patric
4
Theodor
1
Marcus
2
Mattias
I have no idea where to start, so I really need some help from you coding gods
The list is from a sql table, but it does not matter for me where the sort take place (in sql query or in c# code)
Try this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var start_id = 3;
var max_id = list.Max(y => y.Id);
var result =
from x in list
orderby (x.Id + max_id - start_id) % max_id
select x;
I get:
With LINQ to objects you can do something like that:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var startId = 3;
var result = list
.GroupBy(i => i.Id >= startId ? 1 : 0) // split in two groups
.OrderByDescending(g => g.Key) // sort to have the group with startId first
.Select(g => g.OrderBy(i => i.Id)) // sort each group
.SelectMany(i => i) // combine result
.ToList();
Console.WriteLine(string.Join(", ", result.Select(i => i.Id))); // prints "3, 4, 1, 2"
You require 2 criteria to apply:
Order ascending by Id.
Return the Ids greater than threshold before the Ids less than threshold.
You can try:
var offset = 3;
var sorted1 = list
.OrderBy(item => item.Id < offset)
.ThenBy(item => item.Id);
The OrderBy condition yields true if Id is less than offset and false otherwise.
true is greater than false and therefore is returned later
A dirty way could also be:
var offset = 3;
var sorted2 = list
.OrderBy(item => unchecked((uint)(item.Id - offset)));
Here the offset is subtracted from Id and the result converted to unsigned int to make the negative values become very large positive ones. A little hacky. Might not work with queries against SQL providers.
Here's a toy Non-Linq Version
object[] ShiftList(int id)
{
var list = new dynamic[]
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
Span<dynamic> listSpan = list;
int indexFound = -1;
for (int i = 0; i < list.Length; i++)
{
if (listSpan[i].Id == id)
{
indexFound = i;
}
}
if (indexFound is -1)
{
return list;
}
var left = listSpan.Slice(0, indexFound);
var right = listSpan[indexFound..];
object[] objs = new object[list.Length];
Span<object> objSpan = objs;
right.CopyTo(objSpan);
left.CopyTo(objSpan[right.Length..]);
return objs;
}
Try using foreach and iterate over each object in your list:
foreach (var item in list)
{
}
from here you should be able to use some of the collection methods for a list to reorder your list.

c#: Move element whose ID is in array to top of list

In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}

LINQ OrderBy - Custom

I have some data like
ID Sequence customIndex
1 1 0
2 2 0
3 3 2
4 4 1
5 5 0
I need to use sequence in order by when customIndex is zero other wise use customIndex.
So result should be ID in order of 1,2,4,3,5.
I need LINQ implementation using Lambda. I tried some solution but could not implement.
Posting duplicate and deleting previous one, because of wrong formatting the meaning of question got changed and I received bunch of negative votes.
Added code at dotnet fiddle:
https://stable.dotnetfiddle.net/fChl40
The answer is based on assumption, that CustomIndex is greater or equals to zero:
var result =
data.OrderBy(x => x.CustomIndex==0 ? x.Sequence :
data.Where(y => y.CustomIndex==0 && y.Sequence < x.Sequence)
.Max(y => (int?)y.Sequence))
.ThenBy(x => x.CustomIndex);
This is working for provided test data:
l.OrderBy(a => a.customIndex != 0 ?
list.Where(b => b.Sequence < a.Sequence && b.customIndex == 0)
.OrderByDescending(c => c.Sequence)
.FirstOrDefault()
.Sequence : a.Sequence)
.ThenBy(c=>c.customIndex )
.ToList();
The idea is to order non zero values by first preceding zero valued rows, and then by non zero values itself.
This is something I wanted:
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data{ Id=1, Sequence=1, CustomIndex=0});
data.Add(new Data{ Id=5, Sequence=5, CustomIndex=0});
data.Add(new Data{ Id=6, Sequence=6, CustomIndex=2});
data.Add(new Data{ Id=2, Sequence=2, CustomIndex=0});
data.Add(new Data{ Id=3, Sequence=3, CustomIndex=2});
data.Add(new Data{ Id=4, Sequence=4, CustomIndex=1});
data.Add(new Data{ Id=7, Sequence=7, CustomIndex=1});
int o = 0;
var result = data
.OrderBy(x=>x.Sequence).ToList()
.OrderBy((x)=> myCustomSort(x, ref o) )
;
result.ToList().ForEach(x=> Console.WriteLine(x.Id));
}
public static float myCustomSort(Data x, ref int o){
if(x.CustomIndex==0){
o = x.Sequence;
return x.Sequence ;
}
else
return float.Parse(o + "."+ x.CustomIndex);
}
Sample code: https://stable.dotnetfiddle.net/fChl40
I will refine it further
Based on your question and reply to my comment, I understand you need to clusterize the items' collection, then consider Sequence and CustomIndex on all items of each cluster.
Once clustered (split into blocks depending on a specific criterion) you can merge them back into a unique collection, but while doing that you can manipulate each cluster independently the way you need.
public static class extCluster
{
public static IEnumerable<KeyValuePair<bool, T[]>> Clusterize<T>(this IEnumerable<T> self, Func<T, bool> clusterizer)
{
// Prepare temporary data
var bLastCluster = false;
var cluster = new List<T>();
// loop all items
foreach (var item in self)
{
// Compute cluster kind
var bItemCluster = clusterizer(item);
// If last cluster kind is different from current
if (bItemCluster != bLastCluster)
{
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
// Store new cluster kind and reset items
bLastCluster = bItemCluster;
cluster.Clear();
}
// Add current item to cluster
cluster.Add(item);
}
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
}
}
// sample
static class Program
{
public class Item
{
public Item(int id, int sequence, int _customIndex)
{
ID = id; Sequence = sequence; customIndex = _customIndex;
}
public int ID, Sequence, customIndex;
}
[STAThread]
static void Main(string[] args)
{
var aItems = new[]
{
new Item(1, 1, 0),
new Item(2, 2, 0),
new Item(3, 3, 2),
new Item(4, 4, 1),
new Item(5, 5, 0)
};
// Split items into clusters
var aClusters = aItems.Clusterize(item => item.customIndex != 0);
// Explode clusters and sort their items
var result = aClusters
.SelectMany(cluster => cluster.Key
? cluster.Value.OrderBy(item => item.customIndex)
: cluster.Value.OrderBy(item => item.Sequence));
}
}
It ain't pretty, but it exemplifies what you were asking for, I think:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data { Id = 1, Sequence = 1, CustomIndex = 0 });
data.Add(new Data { Id = 2, Sequence = 2, CustomIndex = 0 });
data.Add(new Data { Id = 3, Sequence = 3, CustomIndex = 2 });
data.Add(new Data { Id = 4, Sequence = 4, CustomIndex = 1 });
data.Add(new Data { Id = 5, Sequence = 5, CustomIndex = 0 });
//List of items where the sequence is what counts
var itemsToPlaceBySequence = data.Where(x => x.CustomIndex == 0).OrderBy(x => x.Sequence).ToList();
//List of items where the custom index counts
var itemsToPlaceByCustomIndex = data.Where(x => x.CustomIndex != 0).OrderBy(x => x.CustomIndex).ToList();
//Array to hold items
var dataSlots = new Data[data.Count];
//Place items by sequence
foreach(var dataBySequence in itemsToPlaceBySequence) {
dataSlots[dataBySequence.Sequence - 1] = dataBySequence ;
}
//Find empty data slots and place remaining items in CustomIndex order
foreach (var dataByCustom in itemsToPlaceByCustomIndex) {
var index = dataSlots.ToList().IndexOf(null);
dataSlots[index] = dataByCustom ;
}
var result = dataSlots.ToList();
result.ForEach(x => Console.WriteLine(x.Id));
var discard = Console.ReadKey();
}
public class Data
{
public int Id { get; set; }
public int Sequence { get; set; }
public int CustomIndex { get; set; }
}
}
The ordering you want to do (order partly on CustomIndex and partly on Sequence) doesn't work like that. But this should be close to what you want. Order first by CustomIndex, and then by Sequence.
var result = data.OrderBy(x => x.CustomIndex).ThenBy(x => x.Sequence);

How can i do this with Extensions methods or Linq?

It is a little hard to explain it with my poor english but i will try.
In below list sequence, if a item first field has same value with another item first field value but not same second fields. As result i want to collect items which has same first field but not second fields.
It looks quite easy but i think it is not any.Consider that you will work on same sequence so it is important doing it effectively.
class MyClass
{
public int first;
public int second;
}
List<MyClass> sequence = new List<MyClass>();
Try this:
List<MyClass> sequence = new List<MyClass>()
{
new MyClass{ First = 1, Second = 10 },
new MyClass{ First = 1, Second = 10 },
new MyClass{ First = 2, Second = 11 },
new MyClass{ First = 2, Second = 12 }
};
var doesntMatch = sequence
.GroupBy(i => i.First)
.Select(g => new
{
Key = g.Key,
Values = g.Select(i => i.Second).Distinct()
})
.Where(i => i.Values.Count() > 1);
foreach (var i in doesntMatch)
{
Console.WriteLine(
"First = {0} contains {1} distinct values: {2}", i.Key, i.Values.Count(),
String.Join(", ", i.Values.Select(n => n.ToString()).ToArray()));
}
// output: "First = 2 contains 2 distinct values: 11, 12"
I'm thinking you might want to use GroupBy.
var sequence = new List<MyClass>()
{
new MyClass() { First = 1, Second = 2 },
new MyClass() { First = 1, Second = 3 },
new MyClass() { First = 1, Second = 4 },
new MyClass() { First = 3, Second = 2 },
new MyClass() { First = 5, Second = 4 },
};
var group1 = sequence.GroupBy(x => x.First);
you could do something like this with linq assuming you MyClass objects are in some kind of collection
Let's say a list<MyClass> myList for the example
(from o in myList where
(from o1 in myList where o1.first == o.first select o1).Count == 2
&& (from o2 in myList where o2.second == o.second select o2).count == 1
select o)
This says get all of the objects in my list where there are at least 2 objects that have the first parameter (o and some other object) and only one objects that have the second parameter.
I'm sure this could be improved upon.
I think that you could do this by joining the sequence to itself on the condition that the first field is equal. Below is some example code that does this. The output is also shown below. Note that this code results in duplicate matches found, so you may have to address that.
class Program
{
class MyClass
{
public int ID;
public int first;
public int second;
}
static void Main(string[] args)
{
// create a sequence containing example data
List<MyClass> sequence = new List<MyClass>();
sequence.AddRange(new MyClass[] {
new MyClass { ID = 1, first = 0, second = 10 },
new MyClass { ID = 2, first = 1, second = 11 },
new MyClass { ID = 3, first = 2, second = 12 },
new MyClass { ID = 4, first = 0, second = 10 },
new MyClass { ID = 5, first = 1, second = 20 },
new MyClass { ID = 6, first = 2, second = 30 },
new MyClass { ID = 7, first = 0, second = 0 },
new MyClass { ID = 8, first = 1, second = 11 },
new MyClass { ID = 9, first = 2, second = 12 },
});
var matches = from x in sequence
join y in sequence // join sequence to itself
on x.first equals y.first // based on the first field
where
!object.ReferenceEquals(x, y) // avoid matching an item to itself
&& x.second != y.second // find cases where the second field is not equal
select new { X = x, Y = y }; // return a "tuple" containing the identified items
foreach (var match in matches)
{
Console.WriteLine("Found first:{0}, x.second:{1}, y.second:{2}, x.ID:{3}, y.ID:{4}", match.X.first, match.X.second, match.Y.second, match.X.ID, match.Y.ID);
}
}
}
The output of this program is the following:
Found first:0, x.second:10, y.second:0, x.ID:1, y.ID:7
Found first:1, x.second:11, y.second:20, x.ID:2, y.ID:5
Found first:2, x.second:12, y.second:30, x.ID:3, y.ID:6
Found first:0, x.second:10, y.second:0, x.ID:4, y.ID:7
Found first:1, x.second:20, y.second:11, x.ID:5, y.ID:2
Found first:1, x.second:20, y.second:11, x.ID:5, y.ID:8
Found first:2, x.second:30, y.second:12, x.ID:6, y.ID:3
Found first:2, x.second:30, y.second:12, x.ID:6, y.ID:9
Found first:0, x.second:0, y.second:10, x.ID:7, y.ID:1
Found first:0, x.second:0, y.second:10, x.ID:7, y.ID:4
Found first:1, x.second:11, y.second:20, x.ID:8, y.ID:5
Found first:2, x.second:12, y.second:30, x.ID:9, y.ID:6
Here's what I came up with:
class MyClass
{
public int First;
public int Second;
}
void Main()
{
List<MyClass> sequence = new List<MyClass>()
{
new MyClass{ First = 1, Second = 10 },
new MyClass{ First = 1, Second = 10 },
new MyClass{ First = 1, Second = 11 },
new MyClass{ First = 2, Second = 11 },
new MyClass{ First = 2, Second = 12 },
new MyClass{ First = 3, Second = 10 }
};
var lonelyItems = sequence
// remove all those which don't match First
.GroupBy(x => x.First).Where(g => g.Count() > 1)
// keep only one for each Second
.SelectMany(g => g.GroupBy(x => x.Second)).Select(g => g.First());
foreach (var x in lonelyItems)
Console.WriteLine(x);
// output:
// 1,10
// 1,11
// 2,11
// 2,12
}

Categories

Resources