I have a list
List<MyObject> myList
and I am adding items to a list and I want to check if that object is already in the list.
so before I do this:
myList.Add(nextObject);
I want to see if nextObject is already in the list.
The object "MyObject" has a number of properties but comparison is based on matching on two properties.
What is the best way to do a check before I add a new "MyObject" to this list of "MyObject"s.
The only solution I thought up was to change from a list to a dictionary and then make the key a concatenated string of the properties (this seems a little unelegant).
Any other cleaner solutions using list or LINQ or something else?
It depends on the needs of the specific situation. For example, the dictionary approach would be quite good assuming:
The list is relatively stable (not a lot of inserts/deletions, which dictionaries are not optimized for)
The list is quite large (otherwise the overhead of the dictionary is pointless).
If the above are not true for your situation, just use the method Any():
Item wonderIfItsPresent = ...
bool containsItem = myList.Any(item => item.UniqueProperty == wonderIfItsPresent.UniqueProperty);
This will enumerate through the list until it finds a match, or until it reaches the end.
Simply use Contains method:
bool alreadyExist = list.Contains(item);
Note that it works based on the equality function Equals. Check the example of the link above if you need to implement Equals function.
If it's maintainable to use those 2 properties, you could:
bool alreadyExists = myList.Any(x=> x.Foo=="ooo" && x.Bar == "bat");
Are you sure you need a list in this case? If you are populating the list with many items, performance will suffer with myList.Contains or myList.Any; the run-time will be quadratic. You might want to consider using a better data structure. For example,
public class MyClass
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class MyClassComparer : EqualityComparer<MyClass>
{
public override bool Equals(MyClass x, MyClass y)
{
if(x == null || y == null)
return x == y;
return x.Property1 == y.Property1 && x.Property2 == y.Property2;
}
public override int GetHashCode(MyClass obj)
{
return obj == null ? 0 : (obj.Property1.GetHashCode() ^ obj.Property2.GetHashCode());
}
}
You could use a HashSet in the following manner:
var set = new HashSet<MyClass>(new MyClassComparer());
foreach(var myClass in ...)
set.Add(myClass);
Of course, if this definition of equality for MyClass is 'universal', you needn't write an IEqualityComparer implementation; you could just override GetHashCode and Equals in the class itself.
Another point to mention is that you should ensure that your equality function is as you expect. You should override the equals method to set up what properties of your object have to match for two instances to be considered equal.
Then you can just do
mylist.contains(item)
Here is a quick console app to depict the concept of how to solve your issue.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication3
{
public class myobj
{
private string a = string.Empty;
private string b = string.Empty;
public myobj(string a, string b)
{
this.a = a;
this.b = b;
}
public string A
{
get
{
return a;
}
}
public string B
{
get
{
return b;
}
}
}
class Program
{
static void Main(string[] args)
{
List<myobj> list = new List<myobj>();
myobj[] objects = { new myobj("a", "b"), new myobj("c", "d"), new myobj("a", "b") };
for (int i = 0; i < objects.Length; i++)
{
if (!list.Exists((delegate(myobj x) { return (string.Equals(x.A, objects[i].A) && string.Equals(x.B, objects[i].B)) ? true : false; })))
{
list.Add(objects[i]);
}
}
}
}
}
Enjoy!
Edit: I had first said:
What's inelegant about the dictionary solution? It seems perfectly elegant to me, especially since you only need to set the comparator in creation of the dictionary.
Of course though, it is inelegant to use something as a key when it's also the value.
Therefore I would use a HashSet. If later operations required indexing, I'd create a list from it when the Adding was done, otherwise, just use the hashset.
Simple but it works
MyList.Remove(nextObject)
MyList.Add(nextObject)
or
if (!MyList.Contains(nextObject))
MyList.Add(nextObject);
A collection can be used as a dictionary, where the difference is that you don't need to a reference to Microsoft Scripting Runtime or use late binding. Please note that in this case the key must be a string. In my case the key(number) is integer, but declared as string.
You can create a custom Boolean function to check if the key exists in the list.
There is a good article be Paul Kelly on ExcelMacroMastery.com
' Function to check if item in the collection already exists
Function Exists(coll As Collection, key As String) As Boolean
On Error GoTo EH
IsObject (coll.Item(key))
Exists = True
EH:
End Function
end you can use it like this
For i = 3 To lastRow
' Ignore the Normal areas
If rg.Cells(i, 1).value <> "Normal" Then
number = rg.Cells(i, 1).value
' Check if the area exist in the collection using a custom function Exists
If Exists(coll, number) = False Then
Set oRiskArea = New clsHighRiskArea
oRiskArea.number = number
coll.add key:=oRiskArea.number, Item:=oRiskArea
Else
Set oRiskArea = coll(number)
End If
With oRiskArea
.name = rg.Cells(i, 2).value
End With
End If
Next i
Related
I have two record structures and two lists as follows:
public struct gtAliasRecType : ICloneable
{
public int lRecordNum;
public double dLocationCd;
}
public struct gtCVARecType : ICloneable
{
public double dLocationCd;
}
static public List<gtCVARecType> LCVARec = null;
static public List<gtAliasRecType> LAliasRec = null;
Now i want to iterate "LAliasRec" list and find whether similar "dLocationCd" exists in "LCVARec" list or not.
I tried using "Contains" and "Find" function of list1 but ended up in errors.
public static void XYZ()
{
gtAliasRecType uAliasRec = gtAliasRecType.CreateInstance();
gtCVARecType uCVARec = gtCVARecType.CreateInstance();
for (int i = 0; i < LAliasRec.Count; i++)
{
uAliasRec = LAliasRec[i];
//trying Find method
gtCVARecType c1 = LCVARec.Find(uAliasRec.dLocationCd);
//trying Contains method
bool nReturn = LCVARec.Contains( uAliasRec.dLocationCd );
}
}
However, i ran into "Cannot convert from 'double' to 'gtCVARecType' error.
Contains & Find
Thanks in advance :)
You can't use Contains to find an item of a different type. You can use Find, but I'd personally use the LINQ Any method:
foreach (var uAliasRec in LAliasRec)
{
bool nReturn = LCVARec.Any(rec => rec.dLocationCd == uAliasRec.dLocationCd);
// Presumably do something with nReturn
}
If the lists are large, you might want to create a HashSet<double> for all the locations first, which is an up-front cost that will make everything else cheaper:
HashSet<double> locations = new HashSet<double>(LCVARec.Select(rec => rec.dLocationCd));
foreach (var uAliasRec in LAliasRec)
{
bool nReturn = locations.Contains(uAliasRec.dLocationCd);
// Presumably do something with nReturn
}
As an aside, I'd strongly advise you to start following regular .NET naming conventions. In its current form, your code is going to be very hard for anyone used to regular C# code to work with.
What about using Intersect
var results = LAliasRec
.Select(x => x.dLocationCd)
.Intersect(LCVARec.Select(x => x.dLocationCd));
bool exists = results.Count() > 0;
Select only the double values, and get intersected ones. If Count greater than 0, you got mutual property values.
You can use LINQ and Inner join to find the intersection of two lists.
var query = from lcva in LCVARec
join lAlias in LAliasRec on lcva.dLocationCd equals lAlias.dLocationCd
select lcva;
Console.WriteLine(query.Count()); //prints number of matching items.
Update
If you can change the List<T> to SortedList<TKey, TValue> of SortedDictionary<TKey, TValue> it will help in quicker lookup.
If you prefer to use Contains() you must implement IEquatable<T> and if you want performance you have to Sort() which needs the class to have IComparable<T> and then do BinarySearch
Reference : https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netcore-3.1#remarks
How is it possible to find a specific object from a list?
Lets say i have a function that takes an object and a list that contains objects of this type and returns the number at which position the specific object is found.
The only way i could think of a solution is to run the list through with a foreach loop, but isn't there a better way?
Thanks
You can use the IndexOf(T item) method:
myList.IndexOf(myItem);
It returns the index of the first occurrence of the item.
The only way i could think of a solution is to run the list through with a foreach loop
Generally, you need a loop (a for or foreach) to find an object in a list. You could use it directly, or through a function that iterates over list elements, but there is going to be a loop. There is no way around it: unless you know something special about the way the elements of the array are arranged, you have to look at them all.
One case of knowing something special about arrangement of elements is knowing that an array is sorted. If this is the case, and when you know the value of the attribute on which the element is sorted, you can find the element much faster by using binary search.
You could use linq expressions
List.where(condition)
Or
List.Select(condition).FirstOrDefault
Based on search condition it will return the item you want.
You can use method IndexOf or if you use a special condition then you can use method
public int FindIndex(Predicate<T> match);
where match is delegate
public delegate bool Predicate<T>(T obj);
In fact it is similar to standard C++ algorithm std::find_if
To see whether object is there You might just need List<T>.Contains method
It states,
Determines whether an element is in the List.
And you need to use it like List<T>.Contains(T type item) , where T is the same type of List and item you need to compare. In your case it's a the type of Object
And to return the index you can use List<T>.IndexOf Method
Searches for the specified object and returns the zero-based index of the first occurrence within the entire List.
Simple Console program
class Program
{
static void Main(string[] args)
{
MyType a = new MyType() { id = 10 };
MyType b = new MyType() { id = 20 };
MyType c = new MyType() { id = 30 };
List<MyType> testList = new List<MyType>();
testList.Add(a);
testList.Add(b);
Console.WriteLine(testList.Contains(a)); // <= Will return true
Console.WriteLine(testList.Contains(c)); // <= Will return false
Console.WriteLine(testList.IndexOf(a)); // <= will return 0 : the index of object a
Console.ReadKey();
}
}
// A simple class
class MyType
{
private int ID;
public int id
{
get { return ID; }
set { ID = value; }
}
}
I've a list which contains a string and another list in it - sample code as follows:
List<Config> ConfigLists;
Here Config is a class containing 2 members:
String Name
List BinaryLists
where Binary is another class.
I want to sort 'ConfigLists' list based on 'Name' contained in each list item.
Can anyone please guide?
Also I would like to use a substring of 'Name' for sorting as the 'Name' would contain values like "Week 07", "Week 09" etc where the substring I will take for comaparision is the numeric part of string (e.g. '07', '09' etc)
Linq should help here;
// using System.Linq;
ConfigLists = ConfigLists.OrderBy(x => x.Name).ToList();
...which of course can be used to sort by a substring...
// Skips "Week ";
// using System.Linq;
ConfigLists = ConfigLists.OrderBy(x => x.Name.Substring(5)).ToList();
If you have a more complex sort order or want to avoid temporary strings in your sorting, you may want to instead use an IComparer to do the sorting for you;
class WeekComparer : IComparer<Config>
{
public int Compare(Config x, Config y)
{
return string.Compare(x.Name, 5, y.Name, 5, int.MaxValue);
}
}
// using System.Linq;
var weekComparer = new WeekComparer();
ConfigLists = ConfigLists.OrderBy(x => x, weekComparer).ToList();
The IComparer also works with "plain old" Sort if you don't like Linq's syntax.
var weekComparer = new WeekComparer();
ConfigLists.Sort(weekComparer);
Just pass a delegate to the Sort() member-function like this:
List<Config> ConfigLists;
//...
ConfigLists.Sort((c1,c2) =>
int.Parse(c1.Name.Substring(5)) - int.Parse(c2.Name.Substring(5)));
If you implement the IComparable interface, you can decide exactly how your custom objects get sorted.
returning a positive int (e.g. 1) will stack ahead and returning a negative int (e.g. -1) will stack behind.
EDIT:
The code could look like:
public class Config : IComparable
{
public string Name { get; set; }
// other properties
public int CompareTo(object obj) {
if (obj == null) return 1;
Config otherConfig = obj as Config;
if (otherConfig != null)
// this is where you would place the compare logic
else
throw new ArgumentException("Object is not a Config");
}
}
With this sort of solution, you will have to call list.Sort() at some point. If you are after efficiency you might want to keep the collection in order as you go (spreading the time to sort). This post explains options to do that.
Good day all,
I have a class and a property, and I have three instances of that class.
public class myclass {
public int myproperty;
}
...
myclass inst1, inst2, inst3;
...
Now at a certain point I need to compare those three property values, and verify that they be equal or not, to end up with the least amount of values.
So if I have
inst1.myproperty = 3;
inst2.myproperty = 5;
inst3.myproperty = 3;
after the magic_function_call, I should get 3 and 5.
And if I have
inst1.myproperty = 2;
inst2.myproperty = 2;
inst3.myproperty = 2;
after the magic_function_call, I should get 2.
Albeit this is trivial per se, and can be solved with as many IF checks as needed, I was wondering which is the fastest, or more efficient way to do it, especially in light of the fact that I might need to add another variable to the check in the future.
I am in fact wondering if there is a bitwise operation that can be performed that can solve this elegantly and quickly.
Alternatively, is there an array operation that can be used to achieve the same goal? I've tried looking for 'reduction' or 'compression' but those keywords don't seem to lead in the right direction.
Thanks in advance.
You can use the morelinq DistinctBy query operator if all of the instances belong to a collection:
List<MyClass> myList = new List<MyClass>();
.. populate list
List<MyClass> distinct = myList.DistinctBy(mc => mc.myproperty).ToList();
Looking at the question, you may want a list of just the property values (a list of ints), which you can achieve with the standard query operators:
List<int> distinct = myList.Select(mc => mc.myproperty).Distinct().ToList();
Note that you haven't defined a property, you've defined a public field. To define an auto property change:
public int myproperty;
to
public int myproperty { get; set; }
Note also that PascalCasing is recommended for property and class names.
Here's a quick function which doesn't require any extra libraries and avoids the setup costs and overheads associated with LINQ:
static int[] Reduce(IEnumerable<myclass> items)
{
HashSet<int> uniqueValues = new HashSet<int>();
foreach (var item in items)
{
uniqueValues.Add(item.myproperty);
}
return uniqueValues.ToArray();
}
Pass it a collection of your myclass instances and it will return an array of unique myproperty values.
Just anohter way to implement it .
var result = myList.GroupBy(p => p.myproperty).Select(p => p.First());
A simplified scenario:
I have a List<Foo>.
Foo has two properties Description (string), IsFoo (bool)
E.g:
var foos = new List<Foo>();
User can "add new Foo's" via textboxes, then on form submit i do this:
foos.Add(new Foo { Description = txtOne.Text, IsFoo = true });
foos.SaveToDb();
However, there are multiple textboxes, and if for example they type "FooBar" in textbox one, then "FooBar" in textbox two, i do not want to show an error, but i simply do not want to add them to the collection. (don't worry about the reason behind this, this is a simplified scenario).
I don't need to show anything to the UI, just when they submit the form, before persisting to the database i need to remove any duplicates (or prevent them from being added to the list in the first place).
What's the easiest/best way to do this? Dictionary perhaps?
I'm using C#4, LINQ, .NET 4.
You can use a HashSet<Foo>.
HashSets are unique, unordered collections.
Adding an element that already exists will silently do nothing. (and return false)
Note that you must override Equals and GetHashCode on the Foo class to compare by value.
Also note that hashsets are intrinsically unordered; if you care about insertion order, you can't use it.
Alternatively, you can use LINQ to check whether your list has a duplicate:
if (!foos.Any(f => f.Description == txtOne.Text))
foos.Add(new Foo { Description = txtOne.Text, IsFoo = true });
To expand on SLaks' answer, you could do something like this:
public class FooComparer : IEqualityComparer<Foo> {
public static readonly FooComparer Instance = new FooComparer();
private FooComparer() { }
public bool Equals(Foo a, Foo b) {
if (a == null)
return b == null;
if (b == null)
return false;
// For case-sensitivity:
return a.Description == b.Description;
// For case-insensitivity:
return String.Equals(a.Description, b.Description, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Foo obj) {
// For case-sensitivity:
return obj.Description.GetHashCode();
// For case-insensitivity:
return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Description);
}
}
Then store your items in a HashSet<Foo> like so:
var hashSet = new HashSet<Foo>(FooComparer.Instance);
hashSet.Add(new Foo() { ... });
With this code, if a second Foo object is added to the hashset and has an identical description as one already present in the hashset, the new object will simply not be added.
Can you use Distinct in linq?
This is VB (and not accurate as I've not got VS on this machine), but something along the lines of:
Dim ie as IEnumerable(of Foo) = From obj As Foo In Foo's Select obj Distinct
Then implent IEqualityComparer? - lookd like #cdhowie just answered....