How to check for the duplicates in an ObservableCollection? - c#

I have a collection of Addresses, which has different address items. Each address item has AddressType, City, Zipcode and other columns. I am writing a validation that if some one is adding a new addressType and the collection of Addressses has already an AddressType listed then give a warning that it has already been listed. How can I do that. I have attached some code. Right now its only checking it for the "Job Address". I have three types of Addresss.
if (Addresses.Any
(a => a.AddressType =="Job Address"))
{
DialogManager.ShowMessageBox(("The type has already been listed "),
MessageBoxButton.OKCancel);
}

If you are checking this after the fact, you can use the size of a HashSet<string>:
var types = new HashSet<string>(Addresses.Select(aa => aa.AddressType));
if (types.Count < Addresses.Count)
{
// You have a duplicate...
// ...not necessarily easy to know WHO is the duplicate
}
The above works by assigning each of the AddressType instances to a set. A set is a collection which only contains the unique items added. Therefore, if you have duplicates in your input sequence, the set will contain less items than your input sequence. You can illustrate this behavior like so:
// And an ISet<T> of existing items
var types = new HashSet<string>();
foreach (string typeToAdd in Addresses.Select(aa => aa.AddressType))
{
// you can test if typeToAdd is really a new item
// through the return value of ISet<T>.Add:
if (!types.Add(typeToAdd))
{
// ISet<T>.Add returned false, typeToAdd already exists
}
}
A better way would be beforehand, perhaps through CanExecute of a command if you have it implemented in a similar fashion:
this.AddCommand = new DelegateCommand<Address>(
aa => this.Addresses.Add(aa),
aa => !this.Addresses.Any(xx => xx.AddressType == aa.AddressType));

Create a new AddressComparer class implementing IEqualityComparer interface
Now you use Contains method
if(Addresses.Contains(Address,new AddressComparer()))
{
//your code
}

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.

Sort dynamic dropdown list alphabetically

I have a dropdown with three strings horizontally for each item. How can I alphabetically sort first by string 1 from each item, then by string 2 from each item?
Here's my snippet:
foreach (var item in list)
{
if(typeof(T).Equals(typeof(Machine)))
{
Machine machine = (item as Machine);
string title = machine.MachineName + " - " + machine.Serial + " - " + machine.MachineOwnership;
alert.AddAction(UIAlertAction.Create(title, UIAlertActionStyle.Default, action => {
button.SetTitle(title, UIControlState.Normal);
}));
}
else if(typeof(T).Equals(typeof(Person)))
{
alert.AddAction(UIAlertAction.Create((item as Person).Name, UIAlertActionStyle.Default, action => {
button.SetTitle((item as Person).Name, UIControlState.Normal);
}));
}
}
Where list contains objects of type Machine which has the following properties:
MachineName, Serial and MachineOwnshership (all strings).
So I want to do something like OrderBy(MachineName).ThenBy(Serial) but not sure how to do so correctly when I'm first checking to see what the list type is and then populating the dropdown list per item.
My dropdown list looks something like this if anyone needs clarification:
-------------------------------------------------
MachineNameStartsWithA - 01234 - OwnerStartsWithA
--------------------------------------------------
MachineNameStartsWithB - 012345 - OwnerStartsWithB
---------------------------------------------------
etc.... where it's a long list of items where the strings are separated by "-" like it's shown in the code.
Also, for what it's worth, this is currently inside a Xamarin app.
I think something like this should work. Sorting by a Tuple should be lexicographic:
list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial))
The OfType call should also remove the need for the typeof check and cast - the result should be an iterable of Machines.
Per the comment regarding multiple object types:
Grouping like types together
This would be my default preference from a UI/UX perspective unless it really makes sense to mix Machines and Persons together. Depending on the length of the list it may be preferable to split on the element type first as list.OfType will enumerate the entire list each time.
foreach(var item in list.OfType<Machine>().OrderBy(x => new Tuple<string,string>(x.MachineName,x.Serial)))
{
// append item
}
foreach(var item in list.OfType<Person>().OrderBy(x => x.Name))
{
// append item
}
Interleaving various types
private Tuple<string,string> Projection(BaseClass x)
{
Machine item = x as Machine;
if(item != null)
{
return new Tuple<string,string>(item.MachineName,item.Serial);
}
Person item = x as Person;
if(item != null)
{
return new Tuple<string,string>(item.Name,"");
}
}
foreach(var item in list.OrderBy(Projection))
{
// check type of item and append as appropriate
}

C# Linking element of List

I'm thinking of a way to create a link or reference to a List of Strings. My situation is that I'm creating ARP table and I need to save IP (as String) of my interface that captured response msg. Interface's IP address is saved in List<String>.
ARP_Table_entry(System.Net.IPAddress host_ip_addr, System.Net.NetworkInformation.PhysicalAddress host_mac_addr, int position)
{
this.host_ip_addr = host_ip_addr;
this.host_mac_addr = host_mac_addr;
this.time = System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
this.local_inter = ??;
}
What I don't want to do, is to assign local_inter smt like list.ElementAt(0), because when I change IP address on the interface (List gets updated w/ new one), value in entry won't change - and I would have to do foreach for every entry (not bad, but...)
Instead I'm looking for solution, that will "link" that specific List-element to local_inter parameter - so changing IP in List will result in automatic update in every entry that contained old one.
If you can control code for ARP_Table_entry just make local_inter property that returns value from that mysterious list:
class ARP_Table_entry
{
List<string> mysteriousList;
int pos;
public ARP_Table_entry(List<string> mysteriousList, int pos,...)
{
this.mysteriousList = mysteriousList;
this.pos = pos;
...
}
// TODO: add null check/position verification as needed
string local_inter => mysteriousList[pos];
// or {get { return mysteriousList[pos];} for C# 5 and below
...
You can also use Func<string> as type or local_inter if you want to use fields for some reason:
class ARP_Table_entry
{
public Func<string> local_inter;
...
public ARP_Table_entry(List<string> mysteriousList, int pos,...)
{
local_inter = () => mysteriousList[pos];
...
}
Note that either approach will not protect you from replacing list altogether with originalMysteriousList = new List<string>().
Another option is to have more complex type to store list of IPs that will notify about its changes (similar to ObesrvableCollection) and update fields on change in the collection.

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.)

Best way of building a collection of unique ID's as well as their counts

I've looked into various different ways of array's, arraylist's, dictionaries... but as I'm used to PHP I'm not entirely sure on the best way I could collect the following information.
My program loops through each user, and if their is a location ID, I want to add that to some sort of collection / array. It's expected that different users will have the same location ID.
If the location ID is the same, I need to increase an integer of how many occurrence for that location ID.
Example:
User1 - Location1
User2 - Location3
User3 - Location3
Location1 = 1
Location3 = 2
Also I need to somehow append each user ID to this collection. So Location3 / 2 occurrences / user2/user3
I've been trying to figure out the best way of doing this for about two hours now, and all the different methods of multidimensional arrays, arraylists, dictionaries is all a little confusing as it all seems abstract to my PHP knowledge. I think C# handles arrays in an entirely different way.
Essentially, the collection with unique location ID's / occurrences / and users collection needs to be stored in something that can be passed to somewhere else in my program as an argument.
I've made a PHP script which does exactly what I'm after
foreach($call["data"] as $v)
{
// Foreach USER ($v containing their unique ID and location ID.)
$user_id = $v["id"];
$location_id = $v["location"]["id"];
// This adds the location ID as the key within the array, followed by every user who has it. I don't need a count in this case, as I could just count the number of users.
$collection[$location_id][$user_id] = null;
}
This in return creates this array when printed using print_r
[106078429431815] => Array
(
[620790873] =>
[626276302] =>
[100000152470577] =>
)
(Small part of the output). - Added PHP Example.
Anyone know how I can get C# to collect the same information in the same way my PHP array does?
using System.Linq;
var grouppingByLocation = users.GroupBy(u => u.LocationID);
foreach (var g in grouppingByLocation)
{
Console.WriteLine("Location id: {0}", g.Key);
foreach (var u in g)
{
Console.WriteLine("User id: {0}", u.ID);
}
}
See Enumerable.GroupBy() for more details.
This is an Extension Method over IEnumerable<T> interface implemented by any built-in collection (such as Array T[], List<T>, Dictionary<K,V>, etc.) which accepts a lambda expression pointing to a property of class collection of which you're grouping by.
If you want to build the list looping through initial data, you can create object like this:
var list = new Dictionary<int, Tuple<int, List<int>>();
And fill it in the loop
if(list[locationID]==null) list[locationID] = Tuple.Create(0,new List<int>());
//..
list[locationId].Item1++; // counter
list[locationId].Item2.Add(userId); //list of users
Create an object to hold each item of data.
public Class Model{
public int LocationId {get;set;}
public int Occurences{get;set;}
public IList<User> Users{get;set;}
}
Initialize the container as a list of items.
var container = List<Model>();
Process you list of users.
foreach(var user in userList){
var model = container.SingleOrDefault(x=> x.LocationId == user.LocationId);
if(model != null){
model.Users.Add(user);
} else{
model = new Model{
model.Users = new List<User>.Add(user);
model.LocationId = user.LocationId;
container.Add(model)
}
model.Occruences ++;
}
}
var byLocation = users.Where(u => !string.IsNullOrEmpty(u.Location))
.GroupBy(u => u.Location);
var stats = byLocation.Select(l => string.Format("{0} / {1} occurrences / {2}",
l.Key, l.Count(), string.Join("/", l.Select(u => u.User)));
// And just to print the result
foreach (var location in stats)
Console.WriteLine(location);

Categories