Remove items from list using their IDs and linq - c#

I have a List of "widget"s (which have an int ID), and a List of selected IDs. My end goal is to have a List of hydrated widgets that agrees with the ID list inside my "set".
public List<Widget> widgets { get; set; }
public List<int> widgetIds
{
get
{
return widgets.Select(x => x.widgetId).ToList();
}
set
{
foreach (int addId in value.Except(widgets.Select(x => x.widgetId)))
{
widgets.Add(_dbWidgetHelper.getWidget(addId));
}
//I need to invert the add somehow
}
}
I've been trying everything I can think of using RemoveAll and Except, but I can't wrap my head around the solution yet. I know this can be done in one line, but the closest I've got is:
var removeIds = widgets.Select(x => x.widgetId).Except(value);
//this is me trying anything I can think of... obviously a syntax error.
widgets.RemoveAll(x=>x.widgetId in removeIds);

Try the following. Note that I changed removeIds to be a HashSet<int> to prevent the query from being re-evaluated every time and to make the look up faster
var removeIds = new HashSet<int>(widgets.Select(x => x.widgetId).Except(value));
widgets.RemoveAll(x => removeIds.Contains(x.widgetID));

Related

How to order list in custom Item class with linq query

I have two classes
class Variant
{
bool isOrdered;
}
class Item
{
List<Variant> Variants;
}
Then I get IQueryable<Item> from my data source. I wand to order List of variants.
e.g If execute query IQueryable<Item> we get:
{Item: Variants:{true,false,true}, Item: Variants:{false, false, true}, Item: Variants:{true,false,true,false}} and after ordering I need to get {Item: Variants:{true,true, false}, Item: Variants:{true,true,false}, Item: Variants:{true,true,false,false}}
I'm trying something like
var query =
from item in source
from variants in item.Variants
orderby variants.isOrdered
select item;
but instead of ordering variants this query order items and I have no idea how to order variants.
If what you are interested on is merely the variants, you could try:
var selections = source.Select(i => i.Variants.OrderByDescending(v => v.isOrdered));
selections will then be an IEnumerable<IOrderedEnumerable<Variant>> with three enumerations of variants (based on your sample data), ordered in this manner:
True,True,False
True,False,False
True,True,False,False
UPDATE:
OP has updated the question to require the item as well, so...
There are a few ways to go about this. The least OOP-based, least intrusive one would be to grab the item as well as the sorted variant list into an anonymous type:
var selections = source.Select(i => new
{
Item = i,
SortedVariants = i.Variants.OrderByDescending(v => v.isOrdered)
});
In this case, selections will be an IEnumerable<'a> where 'a is the anonymous type. The type will have two properties: the item that the variants belong to as well as a property called SortedVariants.
Then there is the simplest, non-reusable way. Every time you access the variants, sort them:
foreach (var item in source)
{
var variants = item.Variants.OrderByDescending(v => v.isOrdered);
//Do something with the variants
}
A more reusable way is to add another property (or method) to the Variant class that returns the variant list in the desired order:
public class Item
{
public List<Variant> Variants;
public IOrderedEnumerable<Variant> OrderedVariants
{
get { return Variants.OrderByDescending(v => v.isOrdered); }
}
//OR
public IOrderedEnumerable<Variant> GetOrderedVariants()
{
return Variants.OrderByDescending(v => v.isOrdered);
}
}
You can then use that property or method instead.
Lastly, if you have the flexibility to change the current design, you can always hide the list behind an interface and implement a method to add:
public class Item
{
private List<Variant> _variants = new List<Variant>();
public IEnumerable<Variant> Variants
{
get { return _variants.OrderByDescending(v => v.isOrdered); }
}
public void AddVariant(Variant variant)
{
_variants.Add(variant);
}
}
This would my personal favorite since it provides the variants, satisfies the requirement and hides the details of the implementation.
if you only want to sort Variant do this :
var SortedVariantItems= Items.Select(x =>new Item {Variants= x.Variants.OrderByDescending(c => c.isOrdered).ToList()})
and if you want In addition to Variant, Items also be sorted(by Variant) do this:
var SortedItems= Items.Select(x =>new Item {Variants= x.Variants.OrderByDescending(c => c.isOrdered).ToList()}).OrderByDescending(x=>x.Variants.Count(c=>c.isOrdered));
I would suggest something like the below.
var orderedItems = Items.OrderBy(item => item.Variants.Where(v => v.isOrdered).Count());
This orders the items by how many of it's variants are ordered. You could replace whatever you like in the where.

Not all empty lists are removed from ObservableCollection?

I have code to create a grouped list for a ListView in Xamarin Forms, which for some reason only sometimes removes a group from the list if it is empty.
char[] alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
var animals = new List<string>() { "Jaguar", "Elephant", "Nemo", "Cat", "Dog", "Iguana", "Puma", "Crow", "Hawk", "Owl", "Badger", "Meerkat", "Lion", "Tiger", "Rabbit", "Pig" };
var groups = new ObservableCollection<GroupedItemModel>();
for (int i = 0; i < alpha.Length; i++)
{
groups.Add(new GroupedItemModel(alpha[i].ToString()));
}
foreach (var _group in groups)
{
foreach (var _animal in animals)
{
if (_animal[0].ToString().ToUpper() == _group.GroupName.ToUpper())
{
_group.Add(_animal);
}
}
}
for (int i = 0; i < groups.Count; i++)
{
if (groups[i].Count == 0)
{
groups.RemoveAt(i);
}
}
ListSource = groups;
However, this happens:
Why do these groups not get removed? Any solutions?
When removing from a list you need to work backwards.
such as:
if you have 1,2,3,4,5,6,7,8,9,10 and you say remove even numbers as its 1-10,
on 1 thats ok, on 2, you remove it, so 3 moves into its place.. so now you arent checking 3...you skip to 4.. so in this example you'd get away withit, but what if your list was already missing say 3, it would have moved number 4 to the place where 2 had been, and move on, 4 would be missed
Change your for loop to be decremental
As described by BugFinder above, the problem you are getting is caused by modifying the list as you iterate through it. Problem solved. But it might also be worth taking a look at the overall approach. Currently we:
Create list of all possible groups (A-Z)
Iterate though the list of animals and add each to a group based upon the first letter
Iterate though the groups and throw away any which are empty.
This can all be done with a few lines of Linq code and a tweak to the GroupedItemModel
public class GroupedItemModel
{
public GroupedItemModel(string name, IEnumerable<string> values){
Name = name;
Values = new List<string>(values);
}
public string Name { get; }
public List<string> Values { get; }
}
We can now populate an ObservableCollection<GroupedItem> with
new ObservableCollection<GroupedItem>(animals.GroupBy(a => char.ToUpper(a[0]).ToString()).OrderBy(g => g.Key).Select(g => new GroupedItem(g.Key, g)));
Very minor afterword: Prefixing variable names with underscores is usually used for member variables of a class, not for local variables (_group, _animal). It doesn't change how the code works but when sharing code with others, using the general conventions helps speed up reading and understanding the code.

Group, Sort, and then Merge to a Observable Collection?

I have a List of Items that have a "DisplayOrder" property. This can either have NULL or int value. If it has int value, it has priority and should be in the 1st group of the Observable Collection. Items in the 1st group are also sorted by DisplayOrder.
If it is NULL, then it belongs to the 2nd group, sorted alphabetically.
The 1st group and 2nd group are then combined for a Main Items Collection Observable Collection which I bind to a ListView.
This is my current code though I am worried if there is a much optimal way of doing it.
var MainItemCollection = new ObservableCollection<MainItemViewModel>();
var ItemsWithProperty = new ObservableCollection<MainItemViewModel>();
var ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>();
foreach (var item in DataObject.MainItems)
{
if (item.DisplayOrder == null)
ItemsWithNullProperty.Add(new MainItemViewModel(item));
else
ItemsWithProperty.Add(new MainItemViewModel(item));
}
ItemsWithProperty = new ObservableCollection<MainItemViewModel>(ItemsWithProperty.OrderBy(c => c.DisplayOrder));
ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>(ItemsWithNullProperty.OrderBy(c => c.Title));
//Add those with priorities first sorted by DisplayOrder 1,2,3,4
foreach (var c in ItemsWithProperty)
{
MainItemCollection.Add(c);
}
//Add those without priority sorted Alphabetically
foreach (var c in ItemsWithNullProperty)
{
MainItemCollection.Add(c);
}
Thank you!
Get the items with DisplayOrder=null & order them by Title:
ItemsWithNullProperty=DataObject.MainItems.Where(x=>x.DisplayOrder==null).OrderBy(o=>o.Title).ToList();
Get the items with DisplayOrder(all items except the above query) & order them by DisplayOrder:
ItemsWithProperty= DataObject.MainItems.Except(ItemsWithNullProperty).OrderBy(o=>o.DisplayOrder).ToList();
Fill the data in MainCollection:
var allItems = MainItemCollection.Concat(ItemsWithProperty)
.Concat(ItemsWithNullProperty)
.ToList();
When doing things like this, you don't need all those intermediate ObservableCollections - you can use the appropriate data structures like array, list, dictionary, hash set etc. or Linq queries. In this particular case, the whole procedure can be reduced to something like this
var MainItemCollection = new ObservableCollection<MainItemViewModel>(DataObject.MainItems
.OrderBy(item => item.DisplayOrder ?? int.MaxValue)
.ThenBy(item => item.DisplayOrder == null ? item.Title : string.Empty)
);
I feel that this is a pretty common scenario.
There is a sorted ObservableCollection bound to some XAML UI, and once more data is available UI needs to be updated without full refresh.
Whenever new ObservableCollection is created like in suggestions above, all items will be rebound and therefore UI fully updated.
I'm surprised that there are no library methods to achieve this. Here is the solution I've came up with. Hope someone might find it useful.
public static class ObservableCollectionExtensions
{
public static void MergeSortedListIntoSortedObservableCollection<T>(this ObservableCollection<T> destination, IList<T> list, Func<T, T, int> compareFunc)
{
int dest_index = 0;
int list_index = 0;
while (list_index < list.Count)
{
if (dest_index >= destination.Count)
{
destination.Add(list[list_index]);
list_index++;
dest_index++;
}
else
{
if (compareFunc(destination[dest_index], list[list_index]) > 0)
{
destination.Insert(dest_index, list[list_index]);
list_index++;
dest_index++;
}
else
{
dest_index++;
}
}
}
}
}

Arrays/Array Lists

I am fairly new to C#
I am trying to retrieve some information from an external data source and store it in array, once it is in an array I wish to sort it by time.
I know how to do this for just one column in a row, however the information I require has multiple columns.
For example:
foreach (Appointment Appoint in fapts)
{
// Store Appoint.Subject, Appoint.Start, Appoint.Organiser.Name.ToString(), Appoint.Location in an array
}
// Sort my array by Appoint.Start
foreach ( item in myNewArray )
{
//print out Appoint.Subject - Appoint.Start, Appoint.Organiser.Name.ToString() and Appoint.location
}
Many thanks for your help.
EDIT:
I have multiple data sources which pull in this:
foreach (Appointment Appoint in fapts)
{
// Store Appoint.Subject, Appoint.Start, Appoint.Organiser.Name.ToString(), Appoint.Location in an array
}
Hence the need to sort the items in a new array, I know this isn't very efficent but there is no way of getting the information I need in any other way.
You can sort a list using the LINQ sorting operators OrderBy and ThenBy, as shown below.
using System.Linq;
and then...
var appointments = new List<Appointment>();
var sortedAppointments = list.OrderBy(l => l.Subject).ThenBy(l => l.Name).ToList();
This will create a new list of appointments, sorted by subject and then by name.
It's unclear what your final aim is but:
Use a generic List instead of an array:
See this SO question for more information as to why using a List is prefered.
List<Appointment> appointments = new List<Appointment>();
foreach (Appointment Appoint in fapts)
{
appointments.Add(Appoint);
}
foreach (var item in appointments)
{
Console.WriteLine(item.Subject);
Console.WriteLine(item.Foo);
// Here you could override ToString() on Appointment to print eveything in one Console.WriteLine
}
If the aim of your code is to order by time, try the following:
var sortedAppointments = fapts.OrderBy(a => a.Start); // assuming Start is a DateTime property of `Appointment`.
Consider a Dictionary Object instead of an array if the data is conceptually one row multiple columns.
foreach(KeyValuePair<string, string> entry in MyDic)
{
// do something with entry.Value or entry.Key
}
You already have a list of objects in fpts, sort that list itself:
fpts.OrderBy(x => x.Subject).ThenBy(x => x.Location).ToList();
LINQ is your friend here.
fapts appears to already be a collection so you could just operate on it.
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start).ToArray()
I've used the ToArray() call to force immediate evaluation and means that myNewArray is already sorted so that if you use it more than once you don't have to re-evaluate the sort.
Alternatively if you are only using this once you can just as easily miss the ToArray() portion out and then execution of the sort will be deferred until you try and enumerate through myNewArray.
This solution puts the source objects into the array, but if you are just wanting to store the specific fields you mention then you will need to use a select. You have two choices for the array item type, you can either use an anonymous class which provides difficulties if you are returning this array from a function or define a class.
For anonymous:
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start)
.Select(Appoint => new {
Start = Appoint.Start,
Organiser = Appoint.Organiser.Name.ToString(),
Location = Appoint.Location
}).ToArray();
For named class assuming class is MyClass:
var myNewArray = fapts.OrderBy(Appoint => Appoint.Start)
.Select(Appoint => new MyClass {
Start = Appoint.Start,
Organiser = Appoint.Organiser.Name.ToString(),
Location = Appoint.Location
}).ToArray();
You have a wide range of options. The 2 most common are:
1) Create a class, then define an array or list of that class, and populate that
2) Create a structure that matches the data format and create an array or list of that
Of course, you could put the data into an XML format or dataset, but that's probably more work than you need.
public List<foo> appointments = new List<foo>();
public struct foo
{
public string subject ;
public DateTime start ;
public string name ;
public string location ;
}
public void foo1()
{
// parse the file
while (!File.eof())
{
// Read the next line...
var myRecord = new foo() ;
myRecord.subject = data.subject ;
myRecord.start = data.Start ;
myRecord.name = data.Name ;
//...
appointments.Add(myRecord);
}
}
Enjoy
(Since I can't comment and reply to the comment - it wasn't clear if he had a class, etc. or was just showing us what he wanted to do. I assumed it was just for demonstration purposes since there wasn't any info as to how the data was being read. If he could already put it into a class, than the first answer applied anyway. I just tossed the last 2 in there because they were options for getting the data first.)

LINQ - convert group to array

I have a problem displaying most popular tags from database in view. I'm not sure about this question title so if someone has a better one please do rename it.
Scenario
I need to show recent posts, recent galleries and most popular tags on my index page. I decided to use tuples for this and it worked fine until I tried to show most popular tags.
Error
The model item passed into the dictionary is of type
'System.Tuple3[photoBlog.Models.Gallery[],photoBlog.Models.Post[],System.Linq.IGrouping2[System.Int32,photoBlog.Models.PostTag][]]',
but this dictionary requires a model item of type
'System.Tuple`3[photoBlog.Models.Gallery[],photoBlog.Models.Post[],photoBlog.Models.PostTag[]]'.
As you can see it gives my view wrong object type. I want expect it to be an array, but it gives me some kind of System.Linq.Grouping item.
Code
As you can see I convert it into array in my controller.
public ActionResult Index()
{
photoBlogModelDataContext _db = new photoBlogModelDataContext();
var posts = _db.Posts.OrderByDescending(x => x.DateTime).Take(4).ToArray();
var galleries = _db.Galleries.OrderByDescending(x => x.ID).Take(4).ToArray();
var posttags = _db.PostTags.GroupBy(x => x.TagID).OrderBy(x => x.Count()).Take(4).ToArray();
return View(Tuple.Create(galleries, posts, posttags));
}
My view is straight forward, note that this did work until I tried to add most popular tags.
#model Tuple<photoBlog.Models.Gallery[], photoBlog.Models.Post[], photoBlog.Models.PostTag[]>
#foreach (var tag in Model.Item3)
{
#tag.Tag.Name
}
You probably want
var posttags = _db.PostTags
.GroupBy(x => x.TagID)
.OrderBy(x => x.Count())
// Take each group and pass the first tag of the group
.Select(g => g.First())
.Take(4)
.ToArray();
At the moment you're passing e.g. all 8 instances of the most popular tag, all 5 instances of the second most popular, etc., each in their own group. I imagine you just want to pass an "example" of each tag.
I don't mean to completely dodge the casting issue, but how about simplifying a little:
public class PopularStatsViewModel{
public Gallery[] Galleries { get; set; }
public Post[] Posts { get; set; }
public PostTags[] Tags { get; set; }
}
New up a PopularStats instance and set the properties to the results of your db calls. Then in your view:
#model photoblog.Models.PopularStatsViewModel
This gets you past the casting problem, sure, but more importantly it makes it a little easier to test, gives you a little more freedom in refactoring/extending that stats object, and it's pretty clear what you're working with on the view when you're accessing the VM properties.
Try changing photoBlog.Models.PostTag[] to IGrouping<TagIDType, photoBlog.Models.PostTag>[]

Categories