How to find items in sequence and group them? - c#

Let's say I have the following:
public class Person
{
public string Name{get;set;}
public string Other{get;set;}
public string Other2{get;set;}
public int? Sequence{get;set;}
}
new Person("bob","other1","other2",1)
new Person("bob","other1","other2",2)
new Person("bob","other1","other2",3)
new Person("bob","other1","other2",4)
new Person("Alice","other1","other2")
new Person("Alice","other1","other2",1)
new Person("Alan","other1","other2",1)
new Person("Alan","other1","other2",2)
new Person("Alan","other1","other2",3)
new Person("Alex","other1","other2")
new Person("Alex","other1","other2",1)
new Person("Alex","other1","other2",2)
As shown some of the objects have sequence 1-n and some don't.
Could I use LINQ to pull objects by sequence from the given list?
Desired output would be:
Bob and all his related data where a sequence is there like 1,2,3,4 records
Alex 2 records as he only has sequences 1 and 2.
So the output would be another object by name and data by sequence.
new {Name="Bob", Data=new[]{
Other = "other"
Sequence = 1
Other2 = "Other2" //etc
}}
The sequence will always increment by 1 and be in order, but how many there might be is unknown.
If I have not made something clear just ask.
What I tried
I tried without using LINQ and looping through the list and processing each object and passing out a newly created object for each row using lots of if's.
I am just wondering if there is an easier way with LINQ although my way works it's ugly.

If I understand correctly:
var result = yourCollection
.Where(x => x.Sequence.HasValue)
.GroupBy(x => x.Name)
.Select(grp => new
{
Name = grp.Key,
Data = grp.Select(x => new
{
x.Other,
x.Sequence,
x.Other2
})
});
This assumes the list is already ordered by Sequence; if not, just add an .OrderBy(x => x.Sequence.Value) before the GroupBy.

Related

Update a property field in a List

I have a List<Map> and I wanted to update the Map.Target property based from a matching value from another List<Map>.
Basically, the logic is:
If mapsList1.Name is equal to mapsList2.Name
Then mapsList1.Target = mapsList2.Name
The structure of the Map class looks like this:
public class Map {
public Guid Id { get; set; }
public string Name { get; set; }
public string Target { get; set; }
}
I tried the following but obviously it's not working:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
// populate the 2 lists here
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
The count of items in list 1 will be always greater than or equal to the count of items in list 2. No duplicates in both lists.
Assuming there are a small number of items in the lists and only one item in list 1 that matches:
list2.ForEach(l2m => list1.First(l1m => l1m.Name == l2m.Name).Target = l2m.Target);
If there are more than one item in List1 that must be updated, enumerate the entire list1 doing a First on list2.
list1.ForEach(l1m => l1m.Target = list2.FirstOrDefault(l2m => l1.Name == l2m.Name)?.Target ?? l1m.Target);
If there are a large number of items in list2, turn it into a dictionary
var d = list2.ToDictionary(m => m.Name);
list1.ForEach(m => m.Target = d.ContainsKey(m.Name) ? d[m.Name].Target : m.Target);
(Presumably list2 doesn't contain any repeated names)
If list1's names are unique and everything in list2 is in list1, you could even turn list1 into a dictionary and enumerate list2:
var d=list1.ToDictionary(m => m.Name);
list2.ForEach(m => d[m.Name].Target = m.Target);
If List 2 has entries that are not in list1 or list1 has duplicate names, you could use a Lookup instead, you'd just have to do something to avoid a "collection was modified; enumeration may not execute" you'd get if you were trying to modify the list it returns in response to a name
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
LINQ Where doesn't really work like that / that's not a statement in itself. The m1 is the entry from list1, and the inner Where would produce an enumerable of list 2 items, but it doesn't result in the Boolean the outer Where is expecting, nor can you do anything to either of the sequences because LINQ operations are not supposed to have side effects. The only thing you can do with a Where is capture or use the sequence it returns in some other operation (like enumerating it), so Where isn't really something you'd use for this operation unless you use it to find all the objects you need to alter. It's probably worth pointing out that ForEach is a list thing, not a LINQ thing, and is basically just another way of writing foreach(var item in someList)
If collections are big enough better approach would be to create a dictionary to lookup the targets:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
var dict = mapsList2
.GroupBy(map => map.Name)
.ToDictionary(maps => maps.Key, maps => maps.First().Target);
foreach (var map in mapsList1)
{
if (dict.TryGetValue(map.Name, out var target))
{
map.Target = target;
}
}
Note, that this will discard any possible name duplicates from mapsList2.

C# List.OrderBy with multiple lists

I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();

Splitting LINQ results into array based on column

I am trying to select a few columns from a single row using LINQ to Entities and then split each column into its own point of an array.
I have the LINQ query getting the results I want, but I can't figure out how to split it into the array. I thought using .ToArray() would work but it doesn't split them.
var LinkData = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
}).ToArray();
How can I split the results from the query so I have a single array of two elements: one for Type and one for Route?
Your Select-statement already creates a list of two-value-items which are stored in instances of anonymous type. So there is no need to create a new two-dimensional array for this. Your linkdata already contains the data you want, however if you´re after one specific combination of (Type, Route) simply call linkedData[myIndex].Route or linkedData[myIndex].Type respectively.
EDIT: If you really want arrays then the following should work:
var arr = linkedData.Select(x => new[] { x.Rote, x.Type }).ToArray();
Which will give you an array of arrays where every element itself contains an array of two elements.
var section = db.Sections
.Where(s => s.ID == SectionID)
.Select(s => new
{
s.Type,
s.Route
})
.SingleOrDefault();
var LinkData = new [] {section.Type, section.Route};
Use a List<> instead
Thats what i would do.
Create a public class of Link Data so:
public class LinkData
{
public string type {get; set;}
public string Route {get;set;}
}
Then in your code
create a List of the Link Data:
List<LinkData> LinkDataList = new List<LinkData>();
then create an object
LinkData obj = new LinkData
add stuff to the object
obj.type = db.Section.Where(s => s.ID==SectionID).Select s=> s.Type new s.Type).SingleOrDefault();
obj.Route = db.Section.Where(s => s.ID==SectionID).Select s=> s.Route new s.Type).SingleOrDefault();;
LinkDataList.Add(obj)
This should give you a clear indication of whats what :)

Sorting a list of objects based on another

public class Product
{
public string Code { get; private set; }
public Product(string code)
{
Code = code;
}
}
List<Product> sourceProductsOrder =
new List<Product>() { new Product("BBB"), new Product("QQQ"),
new Product("FFF"), new Product("HHH"),
new Product("PPP"), new Product("ZZZ")};
List<Product> products =
new List<Product>() { new Product("ZZZ"), new Product("BBB"),
new Product("HHH")};
I have two product lists and I want to reorder the second one with the same order as the first.
How can I reorder the products list so that the result would be : "BBB", "HHH", "ZZZ"?
EDIT: Changed Code property to public as #juharr mentioned
You would use IndexOf:
var sourceCodes = sourceProductsOrder.Select(s => s.Code).ToList();
products = products.OrderBy(p => sourceCodes.IndexOf(p.Code));
The only catch to this is if the second list has something not in the first list those will go to the beginning of the second list.
MSDN post on IndexOf can be found here.
You could try something like this
products.OrderBy(p => sourceProductsOrder.IndexOf(p))
if it is the same Product object. Otherwise, you could try something like:
products.OrderBy(p => GetIndex(sourceProductsOrder, p))
and write a small GetIndex helper method. Or create a Index() extension method for List<>, which would yield
products.OrderBy(p => sourceProductsOrder.Index(p))
The GetIndex method is rather simple so I omit it here.
(I have no PC to run the code so please excuse small errors)
Here is an efficient way to do this:
var lookup = sourceProductsOrder.Select((p, i) => new { p.Code, i })
.ToDictionary(x => x.Code, x => x.i);
products = products.OrderBy(p => lookup[p.Code]).ToList();
This should have a running time complexity of O(N log N), whereas an approach using IndexOf() would be O(N2).
This assumes the following:
there are no duplicate product codes in sourceProductsOrder
sourceProductsOrder contains all of the product codes in products
you make the Code field/property non-private
If needed, you can create a safeguard against the first bullet by replacing the first statement with this:
var lookup = sourceProductsOrder.GroupBy(p => p.Code)
.Select((g, i) => new { g.Key, i })
.ToDictionary(x => x.Key, x => x.i);
You can account for the second bullet by replacing the second statement with this:
products = products.OrderBy(p =>
lookup.ContainsKey(p.Code) ? lookup[p.Code] : Int32.MaxValue).ToList();
And you can use both if you need to. These will slow down the algorithm a bit, but it should continue to have an O(N log N) running time even with these alterations.
I would implement a compare function that does a lookup of the order from sourceProductsOrder using a hash table. The lookup table would look like
(key) : (value)
"BBB" : 1
"QQQ" : 2
"FFF" : 3
"HHH" : 4
"PPP" : 5
"ZZZ" : 6
Your compare could then lookup the order of the two elements and do a simple < (pseudo code):
int compareFunction(Product a, Product b){
return lookupTable[a] < lookupTable[b]
}
Building the hash table would be linear and doing the sort would generally be nlogn
Easy come easy go:
IEnumerable<Product> result =
products.OrderBy(p => sourceProductsOrder.IndexOf(sourceProductsOrder.FirstOrDefault(p2 => p2.Code == p.Code)));
This will provide the desired result. Objects with ProductCodes not available in the source list will be placed at the beginning of the resultset. This will perform just fine for a couple of hundred of items I suppose.
If you have to deal with thousands of objects than an answer like #Jon's will likely perform better. There you first create a kind of lookup value / score for each item and then use that for sorting / ordering.
The approach I described is O(n2).

C# Sorting list by another list

I have now 2 lists:
list<string> names;
list<int> numbers;
and I need to sort my names based on the values in numbers.
I've been searching, and most use something like x.ID, but i don't really know what that value is. So that didn't work.
Does anyone know, what to do, or can help me out in the ID part?
So i assume that the elements in both lists are related through the index.
names.Select((n, index) => new { Name = n, Index = index })
.OrderBy(x => numbers.ElementAtOrDefault(x.Index))
.Select(x => x.Name)
.ToList();
But i would use another collection type like Dictionary<int,string> instead if both lists are related insomuch.
Maybe this is a task for the Zip method. Something like
names.Zip(numbers, (name, number) => new { name, number, })
will "zip" the two sequences into one. From there you can either order the sequence immediately, like
.OrderBy(a => a.number)
or you can instead create a Dictionary<,>, like
.ToDictionary(a => a.number, a => a.name)
But it sounds like what you really want is a SortedDictionary<,>, not a Dictionary<,> which is organized by hash codes. There's no LINQ method for creating a sorted dictionary, but just say
var sorted = new SortedDictionary<int, string>();
foreach (var a in zipResultSequence)
sorted.Add(a.number, a.name);
Or alternatively, with a SortedDictionary<,>, skip Linq entirely, an go like:
var sorted = new SortedDictionary<int, string>();
for (int idx = 0; idx < numbers.Count; ++idx) // supposing the two list have same Count
sorted.Add(numbers[idx], names[idx]);
To complement Tims answer, you can also use a custom data structure to associate one name with a number.
public class Person
{
public int Number { get; set; } // in this case you could also name it ID
public string Name { get; set; }
}
Then you would have a List<Person> persons; and you can sort this List by whatever Attribute you like:
List<Person> persons = new List<Person>();
persons.Add(new Person(){Number = 10, Name = "John Doe"});
persons.Add(new Person(){Number = 3, Name = "Max Muster"});
// sort by number
persons = persons.OrderBy(p=>p.Number).ToList();
// alternative sorting method
persons.Sort((a,b) => a.Number-b.Number);
I fixed it by doing it with an dictionary, this was the result:
dictionary.OrderBy(kv => kv.Value).Reverse().Select(kv => kv.Key).ToList();

Categories

Resources