customize OrderBy for a List? - c#

I have a list of items and I want to create two ways to sort them, Alphabetically and Last Modified.
Here's what I did:
// Alphabetically
tableItems = tableItems.OrderBy (MyTableItem => MyTableItem.ItemName).ToList();
reloadTable(tableItems);
// Last Modified
tableItems = tableItems.OrderBy (MyTableItem => MyTableItem.Timestamp).ToList();
reloadTable(tableItems);
and this works perfectly fine.
My problem is I want this happen to all items in the list except for one.
This one item will always be constant and I want to make sure it's ALWAYS on the top of the list.
What would I need to do for that?
if it matters, c# is the lang.
Thank you for your time.

tableItems = tableItems.OrderBy(i => i.ItemName != "yourexceptitem").ThenBy(i => i.Timestamp).ToList();
EDIT:
If you want to sort the itemname except one, do like this,
tableItems = tableItems.OrderBy(i => i.ItemName != "TestSubject3").ToList();

Other, generic solution:
public static IEnumerable<T> OrderByExcept<T>(
this IEnumerable<T> source,
Predicate<T> exceptPredicate,
Func<IEnumerable<T>, IOrderedEnumerable<T>> projection)
{
var rest = new List<T>();
using (var enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (exceptPredicate(enumerator.Current))
{
yield return enumerator.Current;
}
else
{
rest.Add(enumerator.Current);
}
}
}
foreach (var elem in projection(rest))
{
yield return elem;
}
}
Usage:
tableItems = tableItems.OrderByExcept(
item => item.ItemName == "TestSubject3",
items => items.OrderBy(MyTableItem => MyTableItem.ItemName)
.ThenBy(MyTableItem => MyTableItem.TimeStamp))
.ToList();
Items that meets predicate will always be on the top of list, to the rest of elements projection will be applied.

Related

Sublists of consecutive elements that fit a condition in a list c# linq

So suppose we have a parking(represented as a dictionary<int,bool> :
Every parking lot has its id and a boolean(free,filled).
This way:
Dictionary<int,bool> parking..
parking[0]= true // means that the first parking lot is free
My question is i want to get the all sublist of consecutive elements that matchs in a condition : parking-lot is free.
First i can get elements that fits in this condition easy:
parking.Where(X => X.Value).Select(x => x.Key).ToList();
But then using linq operations i dont know how to get the first generated list that matchs in.
Can i do this without thousand of foreach-while loops checking iterating one by one, is there a easier way with linq?
This method gets a list of consecutive free parking lots
data:
0-free,
1-free,
2-filled ,
3-free
The results will be two lists:
First One will contain => 0 ,1
Second One will contain=> 3
These are the list of consecutive of parking lots that are free.
public List<List<int>> ConsecutiveParkingLotFree(int numberOfConsecutive){}
You can always write your own helper function to do things like this. For example
public static IEnumerable<List<T>> GroupSequential<T, TKey>(
this IEnumerable<T> self,
Func<T, bool> condition)
{
var list = new List<T>();
using var enumerator = self.GetEnumerator();
if (enumerator.MoveNext())
{
var current = enumerator.Current;
var oldValue = condition(current);
if (oldValue)
{
list.Add(current);
}
while (enumerator.MoveNext())
{
current = enumerator.Current;
var newValue = condition(current);
if (newValue)
{
list.Add(current);
}
else if (oldValue)
{
yield return list;
list = new List<T>();
}
oldValue = newValue;
}
if (list.Count > 0)
{
yield return list;
}
}
}
This will put all the items with a true-value in a list. When a true->false transition is encountered the list is returned and recreated. I would expect that there are more compact ways to write functions like this, but it should do the job.
You can apply GroupWhile solution here.
parking.Where(X => X.Value)
.Select(x => x.Key)
.GroupWhile((x, y) => y - x == 1)
.ToList()

Query IEnumerable as IEnumerable<Type>

I have a problem I need to solve efficiently.
I require the index of an element in an IEnumerable source, one way I could do this is with the following
var items = source.Cast<ObjectType>().Where(obj => obj.Start == forDate);
This would give me an IEnumerable of all the items that match the predicate.
if(items != null && items.Any()){
// I now need the ordinal from the original list
return source.IndexOf(items[0]);
}
However, the list could be vast and the operation will be carried out many times. I believe this is inefficient and there must be a better way to do this.
I would be grateful if anyone can point me in the correct direction.
Sometimes, it's good to forget about Linq and go back to basics:
int index = 0;
foeach (ObjectType element in source)
{
if (element.Start == forDate)
{
return index;
}
index++;
}
// No element found
Using Linq, you can take the index of each object before filtering them:
source
.Cast<ObjectType>()
.Select((obj, i) => new { Obj = obj, I = i })
.Where(x => x.Obj.Start == forDate)
.Select(x => x.I)
.FirstOrDefault();
However, this is not really efficient, the following will do the same without allocations:
int i = 0;
foreach (ObjectType obj in source)
{
if (obj.Start == forDate)
{
return i;
}
i++;
}
Your second code sample was invalid: since items is an IEnumerable, you cannot call items[0]. You can use First(). Anyway:
var items = source.Cast<ObjectType>()
.Select((item, index) => new KeyValuePair<int, ObjectType>(index, item))
.Where(obj => obj.Value.Start == forDate);
and then:
if (items != null && items.Any()) {
return items.First().Key;
}
If you need to do this multiple times I would create a lookup for the indices.
ILookup<DateTime, int> lookup =
source
.Cast<ObjectType>()
.Select((e, i) => new { e, i })
.ToLookup(x => x.e.Start, x => x.i);
Now given a forDate you can do this:
IEnumerable<int> indices = lookup[forDate];
Since the lookup is basically like a dictionary that returns multiple values you get the results instantly. So repeating this for multiple values is super fast.
And since this returns IEnumerable<int> you know when there are duplicate values within the source list. If you only need the first one then just do a .First().

Linq Distinct only in next rows match

I have a datatable with the following information:
365.00
370.00
369.59
365.00
365.00 -> match with previous item
365.00 -> match with previous item
I only need to remove the next matched items, like this:
365.00
370.00
369.59
365.00
I tried:
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.DistinctBy(i => i.articlepricehistory_cost)
.ToList();
Result:
365.00
370.00
369.59
Any ideas?
Another approach:
public static IEnumerable<T> MyDistinct<T>(this IEnumerable<T> items)
{
T previous = default(T);
bool first = true;
foreach(T item in items)
{
if (first || !Equals(previous, item))
{
first = false;
previous = item;
yield return item;
}
}
}
Or, as requested, with a selector
public static IEnumerable<T> MyDistinct<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
U previous = default(U);
bool first = true;
foreach(T item in items)
{
U current = selector(item);
if (first || !Equals(previous, current))
{
first = false;
previous = current;
yield return item;
}
}
}
Here's a neat LINQ solution for u
var list = (dt as Enumerable);
var numbers = list.TakeWhile((currentItem, index) => currentItem != list.ElementAtOrDefault(index - 1));
Keep in mind if u have 0 as the first element it will be ommitted from the new list since ElementAtOrDefault will return 0 in the first iteration of the while loop (index of -1), thus evaluating the expression to false. A simple if statement can help you avoid this.
Here's an idea I have not actually tried
Do a Skip(1) on the query to produce a second query.
Now append to the second query any element not equal to the last element in the first query, to produce a third query.
Now zip join the first and third queries together to form a set of pairs; this is the fourth query.
Now construct a fifth query that filters out pairs that have identical elements from the fourth query.
Finally, construct a sixth query that selects the first element of each pair from the fifth query.
The sixth query is the data set you want.
The problem in your query is that you are using .DistinctBy() which will return distinct results only. So if 365.00 appeared anywhere, it won't show up in the returned list again.
var differentPreviousList = new List<double>();
var itemPriceList = dt.ToList();
differentPreviousList.Add(itemPriceList[0]);
for (var index = 1; index < itemPriceList.Count; index++)
{
if (itemPriceList[index - 1] == itemPriceList[index]) continue;
differentPriceList.Add(itemPriceList[index]);
}
It may not be an elegant solution but I would just parse a bare query..here is how.
Run a lambda query to get all the original results without trying to filter out DistinctBy.
Create an object of a single query result of the type you initially queried for.
Initialize a new list for foreach parse results.
Do a for each loop for each result.
The first if section should be if(object above loop is null).
IF is true add item to list
ELSE if check to see if value of item is same as the last iteration.
Store foreach iteration object to the object declared before the loop.
Rinse and repeat, and the result is no duplicate objects found in a row in the loop will be stored in the list resulting in what you wanted.
I think you have to use a temporary value to check if the next value matches the current value or not.
double temporary = -1; // temp value for checking
List<double> results = new List<double>(); // list to store results
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.Select(a => a.articlepricehistory_cost)
.ToList()
.ForEach(cost =>
{
if (temporary != cost) { results.Add(cost); }
temporary = cost;
});
foreach (var result in results)
{
Console.WriteLine(result);
}
After ForEach method is equivalent to the following.
foreach (var cost in costs)
{
if (temporary != cost)
{
results.Add(cost);
Console.WriteLine(cost);
}
temporary = cost;
}

Using C# lambda to find not matching elements in collection

I have two collections that I get from functions:
IEnumerable<InventoryItem> inventoryItems = get();
IEnumerable<InventoryItem> relatedItems = get();
I want to assign related items to each inventory item. But, related item can't match the inventory item itself. Meaning inventory item cant have itself for related item.
I am trying to skip the overlapping elements in the collection this way:
foreach (var item in inventoryItems)
{
InventoryItem item1 = item;
relatedItems.SkipWhile(x => x.RelatedItems.Contains(item1)).ForEach(i => item1.RelatedItems.Add(i));
Save(item);
}
This does not seem to work. Do any of you Linq user have any better suggestions.
The problem that I have is with SkipWhile(x => x.RelatedItems.Contains(item1)) part. The other part works when matching items regardless if they overlap
Where with negative condition should filter out the only item you don't need (note that comapison with != may need to be replaced with some other condition that check item identity)
item1.RelatedItems = relatedItems
.Where(x => !x.RelatedItems.Any(r => r!= item1)).ToList();
Try this:
public IEnumerable<T> GetNotMatchingElements<T>(IEnumerable<T> collection1, IEnumerable<T> collection2)
{
var combinedCollection = collection1.Union(collection2);
var filteredCollection = combinedCollection.Except(collection1.Intersect(collection2));
return filteredCollection;
}
Not sure I completely understand, but if I do, this should work:
foreach (var invItem in inventoryItems)
{
invItem.RelatedItems = relatedItems
.Where(relItem => !relItem.RelatedItems.Contains(invItem)));
Save(invItem);
}

LINQ 'in' clause for child object properties

With the following object hierarchy, I need to confirm whether or not all string Id values are present in Inventories of each SearchResult e.g.
Given a string[] list = { "123", "234", "345" } confirm all list values are present at least once in the array of Inventory elements. I'm curious if I can clean this up using one LINQ statement.
SearchResult
--
Inventory[] Inventories
Inventory
--
String Id
Right now, I'm splitting list e.g.
list.Split(').ToDictionary(i => i.ToString(), i => false)
And iterating the dictionary, testing each Inventory. Then, I create a new List<SearchResult> and add items if there are no false values left in the dictionary. This feels clunky.
Code
// instock: IEnumerable<SearchResult>
foreach (var result in instock)
{
Dictionary<string, bool> ids = list.Split(',').ToDictionary(i => i.ToString(), i => false);
foreach (var id in ids)
if (result.Inventory.Any(i => i.Id == id.Key))
ids[id.Key] = true;
if (!ids.Any(i => i.Value == false))
// instockFiltered: List<SearchResult>
instockFiltered.Add(result);
}
Here is a bit of code I wrote. The advantage here is that it uses a hash map, so it has theoretically linear complexity.
public static bool ContainsAll<T>(this IEnumerable<T> superset, IEnumerable<T> subset, IEqualityComparer<T> comparer)
{
var set = new HashSet<T>(superset, comparer);
return set.IsSupersetOf(subset);
}
This bit of LINQ will iterate over the entire stock and then interrogate the inventory (if it's not null) and find inventory that contain one of the values in your list.
var matches = instock.Where(stock => stock.Inventory != null && stock.Inventory.All(i => list.Contains(i.Id));

Categories

Resources