I have an array of a certain type. Now I want to find an entry where a certain condition is met.
What is the preferred way to do this with the restriction that I don't want to create a temporary object to find, but instead I only want to give a search condition.
MyClass[] myArray;
// fill and sort array..
MyClass item = Array.BinarySearch(myArray, x=>x.Name=="Joe"); // is this possible?
Maybe is it possible to use LINQ to solve it?
EDIT:
I know that it works on normal collections, but I need it to work for BinarySearch.
Just use FirstOrDefault (or SingleOrDefault, if unique).
var myItem = myArray.FirstOrDefault( x => x.Name == "Joe" );
Or if you want to force a BinarySearch and you know that the array is sorted
var myItem = Array.BinarySearch( myArray,
new MyClass { Name = "Joe" },
new MyClassNameComparer() );
where MyClassNameComparer is IComparer<MyClass> and compares based on the name property.
If you don't want any temporary object -- I assume that a constant string is ok, otherwise you're lost -- then you can use.
var myItem = Array.BinarySearch( myArray,
"Joe",
MyClassOrStringComparer() );
Where MyClassOrStringComparer is able to compare a string to a MyClass object (and vice versa).
public class MyClassOrStringComparer
{
public int Compare( object a, object b )
{
if (object.Equals(a,b))
{
return 0;
}
else if (a == null)
{
return -1;
}
else if (b == null)
{
return 1;
}
string aName = null;
string bName = null;
if (a is string)
{
aName = a;
}
else
{
aName = ((MyClass)a).Name;
}
if (b is string)
{
bName = b;
}
else
{
bName = ((MyClass)b).Name;
}
return aName.CompareTo( b.Name );
}
BinarySearch can only be used when the array is sorted, and only when searching for a particular value of the sort key. So this rules out use of an arbitrary predicate.
No, BinarySearch does not contains overload with Comparision<> parameter. You can use LINQ method instead:
MyClass item = myArray.FirstOrDefault(x => x.Name == "Joe");
Related
How do I convert a CSV string to an array of a type that is only known at run time. Say I have 2 array fields in my class of different types declared as int[] intArray and string[] strArray. I want a single function where I pass in 2 parameters the field name and CSV string. I can use the ...; FieldInfo f =... ; Type t = f.FieldType.GetElementType(); But what I can't do is declare List<t> because t is a variable and not a type. I saw one post suggesting csv.Split(',').Select(s => Convert.ChangeType(s, t)).ToArray() but this comes out as an object array not an int or string array; another post saying ...; var list = (IList) Activate.CreatInstance(...), which is fine that I can call list.Add(...) but then what do I do as I need an Array of t.
I'd recommend using a nuget package to do this parsing. CsvHelper has an example of how to deserialize to a given type.
I have bitten the bullet on this one and so will have to update my code if I need a new type like array of float numbers say is required but this suffices for what I need today, and I do agree with the other mentions about CSV what if my values had commas say addresses and came in as "\"12 George St, Sydney\",\"200 Sussex St, Sydney\"" then I would have to consider advanced CSV however again this is all I need for today.
class SetPropertyByName
{
public object this[string fieldName]
{
set
{
FieldInfo field = this.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
if (null != field)
{
if (field.FieldType.IsArray && value is String)
{
string newValue = (string)value;
if (string.IsNullOrEmpty(newValue))
value = null;
else
{
var conversionType = field.FieldType.GetElementType();
if (conversionType == typeof(string))
{
value = newValue.Split(',').ToArray();
}
else if (conversionType == typeof(int))
{
value = newValue.Split(',').Select(s => Convert.ToInt32(s)).ToArray();
}
}
field.SetValue(this, value);
}
else
field.SetValue(this, Convert.ChangeType(value, field.FieldType));
}
}
}
}
I have a use case where I need to check if a value is a C# 7 ValueTuple, and if so, loop through each of the items. I tried checking with obj is ValueTuple and obj is (object, object) but both of those return false. I found that I could use obj.GetType().Name and check if it starts with "ValueTuple" but that seems lame to me. Any alternatives would be welcomed.
I also have the issue of getting each item. I attempted to get Item1 with the solution found here: How do I check if a property exists on a dynamic anonymous type in c#? but ((dynamic)obj).GetType().GetProperty("Item1") returns null. My hope was that I could then do a while to get each item. But this does not work. How can I get each item?
Update - more code
if (item is ValueTuple) //this does not work, but I can do a GetType and check the name
{
object tupleValue;
int nth = 1;
while ((tupleValue = ((dynamic)item).GetType().GetProperty($"Item{nth}")) != null && //this does not work
nth <= 8)
{
nth++;
//Do stuff
}
}
Structures do not inherit in C#, so ValueTuple<T1>, ValueTuple<T1,T2>, ValueTuple<T1,T2,T3> and so on are distinct types that do not inherit from ValueTuple as their base. Hence, obj is ValueTuple check fails.
If you are looking for ValueTuple with arbitrary type arguments, you can check if the class is ValueTuple<,...,> as follows:
private static readonly Set<Type> ValTupleTypes = new HashSet<Type>(
new Type[] { typeof(ValueTuple<>), typeof(ValueTuple<,>),
typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>), typeof(ValueTuple<,,,,,,,>)
}
);
static bool IsValueTuple2(object obj) {
var type = obj.GetType();
return type.IsGenericType
&& ValTupleTypes.Contains(type.GetGenericTypeDefinition());
}
To get sub-items based on the type you could use an approach that is not particularly fast, but should do the trick:
static readonly IDictionary<Type,Func<object,object[]>> GetItems = new Dictionary<Type,Func<object,object[]>> {
[typeof(ValueTuple<>)] = o => new object[] {((dynamic)o).Item1}
, [typeof(ValueTuple<,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2}
, [typeof(ValueTuple<,,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2, ((dynamic)o).Item3}
, ...
};
This would let you do this:
object[] items = null;
var type = obj.GetType();
if (type.IsGeneric && GetItems.TryGetValue(type.GetGenericTypeDefinition(), out var itemGetter)) {
items = itemGetter(obj);
}
Regarding the part of the question "How can I get each item?"...
Both ValueTuple and Tuple both implement ITuple, which has a length property and an indexer property. So a the following console app code lists the values to the console:
// SUT (as a local function)
IEnumerable<object> GetValuesFromTuple(System.Runtime.CompilerServices.ITuple tuple)
{
for (var i = 0; i < tuple.Length; i++)
yield return tuple[i];
}
// arrange
var valueTuple = (StringProp: "abc", IntProp: 123, BoolProp: false, GuidProp: Guid.Empty);
// act
var values = GetValuesFromTuple(valueTuple);
// assert (to console)
Console.WriteLine($"Values = '{values.Count()}'");
foreach (var value in values)
{
Console.WriteLine($"Value = '{value}'");
}
Console output:
Values = '4'
Value = 'abc'
Value = '123'
Value = 'False'
Value = '00000000-0000-0000-0000-000000000000'
This is my solution to the problem. A PCL compatible extension class. Special thanks to #dasblinkenlight and #Evk for helping me out!
public static class TupleExtensions
{
private static readonly HashSet<Type> ValueTupleTypes = new HashSet<Type>(new Type[]
{
typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>)
});
public static bool IsValueTuple(this object obj) => IsValueTupleType(obj.GetType());
public static bool IsValueTupleType(this Type type)
{
return type.GetTypeInfo().IsGenericType && ValueTupleTypes.Contains(type.GetGenericTypeDefinition());
}
public static List<object> GetValueTupleItemObjects(this object tuple) => GetValueTupleItemFields(tuple.GetType()).Select(f => f.GetValue(tuple)).ToList();
public static List<Type> GetValueTupleItemTypes(this Type tupleType) => GetValueTupleItemFields(tupleType).Select(f => f.FieldType).ToList();
public static List<FieldInfo> GetValueTupleItemFields(this Type tupleType)
{
var items = new List<FieldInfo>();
FieldInfo field;
int nth = 1;
while ((field = tupleType.GetRuntimeField($"Item{nth}")) != null)
{
nth++;
items.Add(field);
}
return items;
}
}
hackish one liner
type.Name.StartsWith("ValueTuple`")
(can be extended to check the digit at the end)
I have two lists see below.....result is coming back as empty
List<Pay>olist = new List<Pay>();
List<Pay> nlist = new List<Pay>();
Pay oldpay = new Pay()
{
EventId = 1,
Number = 123,
Amount = 1
};
olist.Add(oldpay);
Pay newpay = new Pay ()
{
EventId = 1,
Number = 123,
Amount = 100
};
nlist.Add(newpay);
var Result = nlist.Intersect(olist);
any clue why?
You need to override the Equals and GetHashCode methods in your Pay class, otherwise Intersect doesn't know when 2 instances are considered equal. How could it guess that it is the EventId that determines equality? oldPay and newPay are different instances, so by default they're not considered equal.
You can override the methods in Pay like this:
public override int GetHashCode()
{
return this.EventId;
}
public override bool Equals(object other)
{
if (other is Pay)
return ((Pay)other).EventId == this.EventId;
return false;
}
Another option is to implement an IEqualityComparer<Pay> and pass it as a parameter to Intersect:
public class PayComparer : IEqualityComparer<Pay>
{
public bool Equals(Pay x, Pay y)
{
if (x == y) // same instance or both null
return true;
if (x == null || y == null) // either one is null but not both
return false;
return x.EventId == y.EventId;
}
public int GetHashCode(Pay pay)
{
return pay != null ? pay.EventId : 0;
}
}
...
var Result = nlist.Intersect(olist, new PayComparer());
Intersect is probably only adding objects when the same instance of Pay is in both List. As oldPay and newPay are instantiated apart they're considered not equal.
Intersect uses the Equals method to compare objects. If you don't override it it keeps the same behavior of the Object class: returning true only if both are the same instance of the object.
You should override the Equals method in Pay.
//in the Pay class
public override bool Equals(Object o) {
Pay pay = o as Pay;
if (pay == null) return false;
// you haven't said if Number should be included in the comparation
return EventId == pay.EventId; // && Number == pay.Number; (if applies)
}
Objects are reference types. When you create two objects, you have two unique references. The only way they would ever compare equal is if you did:
object a = new object();
object b = a;
In this case, (a == b) is true. Read up on reference vs value types, and objects
And to fix your issue, override Equals and GetHashCode, as Thomas Levesque pointed out.
As others have noted, you need to provide the appropriate overrides to get Intersect to work correctly. But there is another way if you don't want to bother with overrides and your use case is simple. This assumes you want to match items on EventId, but you can modify this to compare any property. Note that this approach is likely more expensive than calling Intersect, but for small data sets it may not matter.
List<Pay> intersectedPays = new List<Pay>();
foreach (Pay o in olist)
{
var intersectedPay = nlist.Where(n => n.EventId == o.EventId).SingleOrDefault();
if (intersectedPay != null)
intersectedPays.Add(intersectedPay);
}
List<Pay> result = intersectedPays;
I have a method that is supposed to check whether there is exactly one element in a collection that holds true for some predicate (given as a Func).
public bool ExistsUnique(Func<T, bool> p)
{
var tempCol = from i in MyCollection where p(i) select i;
return (tempCol.Count() == 1);
}
The problem with this is that when a second element that also holds true for the predicate
is found (for example two of the same string exists in a collection) the count is still 1. Which means it either overrides the first element or never adds the second because it already exists.
Any ideas as to how I can fix this method?
thx
/Peter
You can use the Single() method provided by LINQ like this:
public bool ExistsUnique(Func<T, bool> p)
{
try
{
var temp = myCollection.Single(x => p(x));
}
catch(Exception e)
{
// log exception
return false;
}
return true;
}
"Returns the only element of a sequence that satisfies a specified condition, and throws
an exception if more than one such element exists."
From http://msdn.microsoft.com/en-us/library/bb535118.aspx
EDIT
To avoid throwing an exception, you may also use the SingleOrDefault() method:
public bool ExistsUnique(Func<T, bool> p)
{
return myCollection.SingleOrDefault(x => p(x)) != null;
}
There must be some other problem. I'd suspect your predicate. For example, this returns a count of 2, as expected:
List<string> MyCollection = new List<string>()
{
"hello",
"hello"
};
var tempCol = from i in MyCollection where i == "hello" select i;
int count = tempCol.Count();
I doubt that it's the way you're calling it, either. The following works (returns false):
static List<string> MyCollection = new List<string>()
{
"hello",
"hello"
};
static bool ExistsUnique(Func<string, bool> p)
{
var tempCol = from i in MyCollection where p(i) select i;
return tempCol.Count() == 1;
}
static void DoIt()
{
bool isUnique = ExistsUnique((s) => s.Equals("hello"));
Console.WriteLine(isUnique);
}
Are you sure tempCol has looped completely through MyCollection?
is Count() a method that forces the complete loop or is it lazy?
Does for example tempCol.ToList().Count give the correct result?
This implementation would make it so you don't have to actually enumerate the entire collection, so will save you some execution time.
public bool ExistsUnique(Func<T, bool> p)
{
return MyCollection.Where(i => p(i)).Take(2).Count() == 1;
}
The Take(2) limits the Count to only enumerate the first two meeting the criteria.
Long story short: I have 2 collections of objects. One contains good values (Let's call it "Good"), the other default values (Mr. "Default"). I want the Intersect of the Union between Good and Default, and Default. In other words: Intersect(Union(Good, Default), Default). One might think it resolves as Default, but here is where it gets tricky : I use a custom IEqualityComparer.
I got the following classes :
class MyClass
{
public string MyString1;
public string MyString2;
public string MyString3;
}
class MyEqualityComparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass item1, MyClass item2)
{
if(item1 == null && item2 == null)
return true;
else if((item1 != null && item2 == null) ||
(item1 == null && item2 != null))
return false;
return item1.MyString1.Equals(item2.MyString1) &&
item1.MyString2.Equals(item2.MyString2);
}
public int GetHashCode(MyClass item)
{
return new { item.MyString1, item.MyString2 }.GetHashCode();
}
}
Here are the characteristic of my collections Good and Default collections :
Default : It's a large set, containing all the wanted { MyString1, MyString2 } pairs, but the MyString3 values are, as you can guess, default values.
Good : It's a smaller set, containing mostly items which are in the Default set, but with some good MyString3 values. It also has some { MyString1, MyString2 } that are outside of the wanted set.
What I want to do is this : Take only the items from Good that are in Default, but add the other items in Default to that.
Here is, what I think is, my best try :
HalfWantedResult = Good.Union(Default, new MyEqualityComparer());
WantedResult= HalfWantedResult.Intersect(Good, new MyEqualityComparer());
I taught it should have worked, but the result I get is basically only the good { MyString1, MyString2 } pairs set, but all coming from the Default set, so I have the default value all across. I also tried switching the Default and Good of the last Intersect, but I get the same result.
First of all this is wrong:
public bool Equals(MyClass item1, MyClass item2)
{
return GetHashCode(item1) == GetHashCode(item2);
}
If the hashcode's are different for sure the corresponding 2 items are different, but if they're equal is not guaranteed that the corresponding 2 items are equal.
So this is the correct Equals implementation:
public bool Equals(MyClass item1, MyClass item2)
{
if(object.ReferenceEquals(item1, item2))
return true;
if(item1 == null || item2 == null)
return false;
return item1.MyString1.Equals(item2.MyString1) &&
item1.MyString2.Equals(item2.MyString2);
}
As Slacks suggested (anticipating me) the code is the following:
var Default = new List<MyClass>
{
new MyClass{MyString1="A",MyString2="A",MyString3="-"},
new MyClass{MyString1="B",MyString2="B",MyString3="-"},
new MyClass{MyString1="X",MyString2="X",MyString3="-"},
new MyClass{MyString1="Y",MyString2="Y",MyString3="-"},
new MyClass{MyString1="Z",MyString2="Z",MyString3="-"},
};
var Good = new List<MyClass>
{
new MyClass{MyString1="A",MyString2="A",MyString3="+"},
new MyClass{MyString1="B",MyString2="B",MyString3="+"},
new MyClass{MyString1="C",MyString2="C",MyString3="+"},
new MyClass{MyString1="D",MyString2="D",MyString3="+"},
new MyClass{MyString1="E",MyString2="E",MyString3="+"},
};
var wantedResult = Good.Intersect(Default, new MyEqualityComparer())
.Union(Default, new MyEqualityComparer());
// wantedResult:
// A A +
// B B +
// X X -
// Y Y -
// Z Z -
You need to check for actual equality, not just hashcode equality.
GetHashCode() is not (and cannot be) collision free, which is why the Equals method is required in the first place.
Also, you can do this much more simply by writing
WantedResult = Good.Concat(Default).Distinct();
The Distinct method will return the first item of each pair of duplicates, so this will return the desired result.
EDIT: That should be
WantedResult = Good.Intersect(Default, new MyEqualityComparer())
.Union(Default, new MyEqualityComparer());