I am using this LINQ statement to sort a list by product name (ascending order) which contains product names (string) and Sizes available for each product (List<byte>);
LinkedList<FullItemDetails> itemDetails = new LinkedList<FullItemDetails>();
public class FullItemDetails
{
public string ProductName { get; set; }
public List<byte> Sizes { get; set; }
}
Now every time I input a new entry ex; Jacket,6,12,18,10, I think my program is sorting my list all over again;
itemDetails.AddLast(fullItemDetails);
//SortedProducts
itemDetails = Products.OrderBy(x => x.ProductName).ToList();
If the list is already sorted I only need to put the last entry in its correct place.
What is the best way to do it. Also to reduce the complexity of the algorithm. thanks
This seems like an ideal problem for a SortedList, as you have a key (name) and value (List<int> for the size).
Documentation is available here: http://msdn.microsoft.com/en-us/library/system.collections.sortedlist.aspx
The list declaration would look like this: SortedList<string, List<int> >. All inserts would be sorted on string, and the values can be enumerated based on each key.
Instead of List<T>, use SortedList<TKey, TValue> or SortedSet<T>. You can pass in an IComparer<T> to use a specific sorting algorithm through the respective constructor. Should you want to use a Lambda expression, you can use a small wrapper class to wrap a Comparison<T>.
Which will result in something like:
ICollection<FullItemDetails> _itemList = new SortedSet<FullItemDetails>(new ComparisonComparer<FullItemDetails>((x,y) -> x.ProductName.CompareTo(y.ProductName))
Your collection will now always be ordered.
When you're using .NET 4.5, you can use Comparer<T>.Create to create an IComparer implementation from a lambda expression.
You can use a SortedList<string,FullItemDetails>.
And you add your times like that list.Add(fullItemDetails.Name,fullItemDetails)
[Edit] : The order will be conserved after adding or removing an element.
[Edit2] Using LINQ
you use a list to store your items (adding/removing) : List<FullItemDetails> originalList and other property to read your sorted data :
IEnumerable<FullItemDetails> sortedList = originalList.OrderBy(e => e.Name).ThenBy(e => /* logic to order by another property*/);
and now you can iterate through your sortedList and as this sorted list is an IEnumerable<T> each time you iterate through it you will have exactly the same elements as in your originalList (after adding or removing items).
In other words : sortedList only contains the logic to read your originalList.
Hope this helps.
Regards.
Related
I have a list of Items, each containing a field of Type integer.
I want to filter my list to get only the items that match a given list of integers.
The code I have now works but I know it could be optimized.
Class Item
{
int ID;
//Other fields & methods that are irrelevant here
}
//Selection method
IEnumerable<Item> SelectItems(List<Item> allItems, List<int> toSelect)
{
return allItems.Where(x => toSelect.Contains(x.ID));
}
The problem I have is that I iterate through allItems and in each iteration I iterate through toSelect.
I have the feeling it is possible to be much more effective but I don't know how I can achieve this with Linq.
This might also be an already asked question as I don't know how this is called in English. This feels kind of stupid because I don't know how to formulate this properly in a seach engine.
You can use Join which is more efficient because it's using a set based approach:
var selectedItems = from item in allItems
join id in toSelect
on item.Id equals id
select item;
return selectedItems;
Another way which is more efficient is to use a HashSet<int> instead of a list:
IEnumerable<Item> SelectItems(List<Item> allItems, HashSet<int> toSelect)
{
return allItems.Where(x => toSelect.Contains(x.ID));
}
There are two ways to approach this.
Currently you have O(N×M) performance (where N is the size of allItems and M is the size of toSelect).
If you're just trying to reduce it easily, then you could reduce it to O(N)+O(M) by creating a hash-set of toSelect:
var matches = new HashSet<int>(toSelect);
return allItems.Where(x => matches.Contains(x.ID));
However, this is still going to be dominated by N - the size of allItems.
A better long term approach may be to pre-index the data (and keep it indexed) by Id. So instead of allItems being a List<T> - it could be a Dictionary<int, T>. Note that building the dictionary can be expensive, so you don't want to do this every time you want to search : the key is to do this once at the start (and keep it maintained). Then this becomes O(M) (the size of toSelect, which is usually small), since dictionary lookups are O(1).
IEnumerable<Item> SelectItems(Dictionary<int, Item> allItems, List<int> toSelect)
{
foreach(var id in toSelect)
{
if (allItems.TryGetValue(id, out var found))
yield return found;
}
}
(there is no need to pre-hash toSelect since we aren't checking it for Contains)
I'm trying to do something very simple but it seems that I don't understand SortedDictionary.
What I'm trying to do is the following:
Create a sorted dictionary that sorts my items by some floating number, so I create a dictionary that looks like this
SortedDictionary<float, Node<T>> allNodes = new SortedDictionary<float, Node<T>>();
And now after I add items, I want to remove them one by one (every removal should be at a complexity of O(log(n)) from the smallest to the largest.
How do I do it? I thought that simply allNodes[0] will give me the the smallest, but it doesn't.
More over, it seems like the dictionary can't handle duplicate keys. I feel like I'm using the wrong data structure...
Should I use something else if I have bunch of nodes that I want to be sorted by their distance (floating point)?
allNodes[0] will not give you the first item in the dictionary - it will give you the item with a float key value of 0.
If you want the first item try allNodes.Values.First() instead. Or to find the first key use allNodes.Keys.First()
To remove the items one by one, loop over a copy of the Keys collection and call allNodes.Remove(key);
foreach (var key in allNodes.Keys.ToList())
{
allNodes.Remove(key);
}
To answer your addendum to your question, yes SortedDictionary (any flavor of Dictionary for that matter) will not handle duplicate keys - if you try and add an item with an existing key it will overwrite the previous value.
You could use a SortedDictionary<float, List<Node<T>>> but then you have the complexity of extracting collections versus items, needing to initialize each list rather than just adding an item, etc. It's all possible and may still be the fastest structure for adds and gets, but it does add a bit of complexity.
Yes, you're right about complexity.
In SortedDictionary all the keys are sorted. If you want to iterate from the smallest to the largest, foreach will be enough:
foreach(KeyValuePair<float, Node<T>> kvp in allNodes)
{
// Do Something...
}
You wrote that you want to remove items. It's forbidden to remove from collections during iteratation with foreach, so firstly create a copy of it to do so.
EDIT:
Yes, if you have duplicated keys you can't use SortedDictionary. Create a structural Node with Node<T> and float, then write a comparer:
public class NodeComparer : IComparer<Node>
{
public int Compare(Node n1, Node n2)
{
return n2.dist.CompareTo(n1.dist);
}
}
And then put everything in simple List<Node> allNodes and sort:
allNodes.Sort(new NodeComparer());
As a Dictionary<TKey, TValue> must have unique keys, I'd use List<Node<T>> instead. For instance, if your Node<T> class has a Value property
class Node<T>
{
float Value { get; set; }
// other properties
}
and you want to sort by this property, use LINQ:
var list = new List<Node<T>>();
// populate list
var smallest = list.OrderBy(n => n.Value).FirstOrDefault();
To remove the nodes one by one, just iterate threw the list:
while (list.Count > 0)
{
list.RemoveAt(0);
}
Say I have this list of strings that I'm using to create a list of objects.
My list of strings has a count of 171 items in it, while my list of objects has 170. So one did not passed, but I need to figure out which one.
Luckily, all the strings can be found in each name of each object. That means that, for example:
string nameObjOne
Will equals to this:
public class myObj
{
public string myName {get;set;}
}
So how can I check if all the strings in my first list are located in the list of objects so that I may figure out which one is not there?
You can select items that are in List<string> but does not have corresponding item in List<myObj> using LINQ:
var results = strings.Except(myObjects.Select(o => o.myName)).ToArray();
After that, you can just check length of results array to determine, if there is such an item.
It is O(n+m) solution, because of Except implementation, which uses HastSet.
I have a data structure like
public DespatchGroup(DateTime despatchDate, List<Products> products);
And I am trying to do...
var list = new List<DespatchGroup>();
foreach (var group in dc.GetDespatchedProducts().GroupBy(i => i.DespatchDate))
{
// group.Values is not correct... how do I write this?
list.Add(new DespatchGroup(group.Key, group.Values);
}
I'm obviously not understanding IGrouping as I can't see how to actually get to the data records within the group!
The group implements IEnumerable<T> - In the general case, just call foreach over the group. In this case, since you need a List<T>:
list.Add(new DespatchGroup(group.Key, group.ToList());
There's no Values property or similar because the IGrouping<T> itself is the IEnumerable<T> sequence of values. All you need to do in this case is convert that sequence to a list:
list.Add(new DespatchGroup(group.Key, group.ToList());
For any selected group,you could call
var selectedGroupValues=selectedGroup.SelectMany(x=>x);
Just a related tip - since, as the other answers have said, the grouping is an IEnumerable, if you need to access a specific index you can use group.ElementAt(i).
This is probably obvious to a lot of people but hopefully it will help a few!
I have a class that has multiple List<> contained within it. Its basically a table stored with each column as a List<>. Each column does not contain the same type. Each list is also the same length (has the same number of elements).
For example:
I have 3 List<> objects; one List, two List, and three List.
//Not syntactically correct
List<DateTime> one = new List...{4/12/2010, 4/9/2006, 4/13/2008};
List<double> two = new List...{24.5, 56.2, 47.4};
List<string> three = new List...{"B", "K", "Z"};
I want to be able to sort list one from oldest to newest:
one = {4/9/2006, 4/13/2008, 4/12/2010};
So to do this I moved element 0 to the end.
I then want to sort list two and three the same way; moving the first to the last.
So when I sort one list, I want the data in the corresponding index in the other lists to also change in accordance with how the one list is sorted.
I'm guessing I have to overload IComparer somehow, but I feel like there's a shortcut I haven't realized.
I've handled this design in the past by keeping or creating a separate index list. You first sort the index list, and then use it to sort (or just access) the other lists. You can do this by creating a custom IComparer for the index list. What you do inside that IComparer is to compare based on indexes into the key list. In other words, you are sorting the index list indirectly. Something like:
// This is the compare function for the separate *index* list.
int Compare (object x, object y)
{
KeyList[(int) x].CompareTo(KeyList[(int) y])
}
So you are sorting the index list based on the values in the key list. Then you can use that sorted key list to re-order the other lists. If this is unclear, I'll try to add a more complete example when I get in a situation to post one.
Here's a way to do it using LINQ and projections. The first query generates an array with the original indexes reordered by the datetime values; in your example, the newOrdering array would have members:
{ 4/9/2006, 1 }, { 4/13/2008, 2 }, { 4/12/2010, 0 }
The second set of statements generate new lists by picking items using the reordered indexes (in other words, items 1, 2, and 0, in that order).
var newOrdering = one
.Select((dateTime, index) => new { dateTime, index })
.OrderBy(item => item.dateTime)
.ToArray();
// now, order each list
one = newOrdering.Select(item => one[item.index]).ToList();
two = newOrdering.Select(item => two[item.index]).ToList();
three = newOrdering.Select(item => three[item.index]).ToList();
I am sorry to say, but this feels like a bad design. Especially because List<T> does not guarantee element order before you have called one of the sorting operations (so you have a problem when inserting):
From MSDN:
The List is not guaranteed to be
sorted. You must sort the List
before performing operations (such as
BinarySearch) that require the List
to be sorted.
In many cases you won't run into trouble based on this, but you might, and if you do, it could be a very hard bug to track down. For example, I think the current framework implementation of List<T> maintains insert order until sort is called, but it could change in the future.
I would seriously consider refactoring to use another data structure. If you still want to implement sorting based on this data structure, I would create a temporary object (maybe using an anonymous type), sort this, and re-create the lists (see this excellent answer for an explanation of how).
First you should create a Data object to hold everything.
private class Data
{
public DateTime DateTime { get; set; }
public int Int32 { get; set; }
public string String { get; set; }
}
Then you can sort like this.
var l = new List<Data>();
l.Sort(
(a, b) =>
{
var r = a.DateTime.CompareTo(b);
if (r == 0)
{
r = a.Int32.CompareTo(b);
if (r == 0)
{
r = a.String.CompareTo(b);
}
}
return r;
}
);
I wrote a sort algorithm that does this for Nito.LINQ (not yet released). It uses a simple-minded QuickSort to sort the lists, and keeps any number of related lists in sync. Source code starts here, in the IList<T>.Sort extension method.
Alternatively, if copying the data isn't a huge concern, you could project it into a LINQ query using the Zip operator (requires .NET 4.0 or Rx), order it, and then pull each result out:
List<DateTime> one = ...;
List<double> two = ...;
List<string> three = ...;
var combined = one.Zip(two, (first, second) => new { first, second })
.Zip(three, (pair, third) => new { pair.first, pair.second, third });
var ordered = combined.OrderBy(x => x.first);
var orderedOne = ordered.Select(x => x.first);
var orderedTwo = ordered.Select(x => x.second);
var orderedThree = ordered.Select(x => x.third);
Naturally, the best solution is to not separate related data in the first place.
Using generic arrays, this can get a bit cumbersome.
One alternative is using the Array.Sort() method that takes an array of keys and an array of values to sort. It first sorts the key array into ascending order and makes sure the array of values is reorganized to match this sort order.
If you're willing to incur the cost of converting your List<T>s to arrays (and then back), you could take advantage of this method.
Alternatively, you could use LINQ to combine the values from multiple arrays into a single anonymous type using Zip(), sort the list of anonymous types using the key field, and then split that apart into separate arrays.
If you want to do this in-place, you would have to write a custom comparer and create a separate index array to maintain the new ordering of items.
I hope this could help :
one = one.Sort(delegate(DateTime d1, DateTime d2)
{
return Convert.ToDateTime(d2).CompareTo(Convert.ToDateTime(d1));
});