Lambda expression not behaving as expected - c#

I'm trying to construct a lambda expression that will match elements of one array with a second. Below is a simplified version of this query:
class Program
{
static void Main(string[] args)
{
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne.Where(p => listTwo.Select(e => e.Name).Equals(p)).ToArray();
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name {get; set;}
}
}
I would expect newVals to return an array of 1 value, but it's empty. I realise that uncommenting myVals2 would achieve the same result, but the lists of classes differ more fundamentally than shown.

You are using Equals but you should use Contains. You are checking wheter IEnumerable<> is equal to p, but you want to check if IEnumerable<> contains p, so replace:
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Equals(p)).
ToArray();
with
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Contains(p)).
ToArray();

Try this:
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne
.Where(p => listTwo.Select(e => e.Name).Contains(p))
.ToArray();
listTwo.Select(e => e.Name) is a IEnumerable<string>

You probably want to perform a Join on the 2 collections.
var q =
listOne
.Join(
listTwo,
l2 => l2,
l1 => l1.Name,
(l2, l1) => new { l2, l1, });
You can change the selector (the last parameter) to suit your needs, if it's just values from listOne for example then have (l2, l1) => l1.
The other solutions will work, but maybe not as you would expect.
Using Linq-Objects Contains within a where clause will cause the entire of listTwo to be iterated for each entry in listOne.

How about something like this:
string[] newVals = listOne.Where(p => listTwo.Any(e => e.Name.Contains(p))).ToArray();
or to be more strict use == instead of Contains.
But if you want to obtain the items that are common between the 2 why not just call .Intersect()??

You are trying to perform a join, technically you are better off simplifying your linq statement to use a join. An example is included below.
static void Main(string[] args)
{
string[] listOne = new [] { "test1", "test2", "test3" };
MyClass[] listTwo = new [] { new MyClass("test1") };
string[] newVals = (from str1 in listOne
join str2 in listTwo.Select(e => e.Name) on str1 equals str2
select str1).ToArray();
foreach (var newVal in newVals)
{
Console.WriteLine(newVal);
}
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name { get; set; }
}

Related

How to add multiple dynamic conditions or filters into LINQ query.?

I am fetching the products list whose name starts with numbers, but i would like to apply the two more filters like with specific BranchId and isActive.
Query which returns the list of products whose name starts with numbers.
List<string> searchP = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
.....Where(x=> searchP.Any(y=> x.ProductName.StartsWith(x.ProductName))).ToList();
I have a list of filters in custom FilterCollection object named filters which has three properties, filtername, operatorname and filtervalue.
filters.Add(FilterKey.BranchId, Filteroperator.Equals, 333); same for isActive, this is what I have passed before one layer back.
I would like to get the products whose name starts with number and having BranchId==value in filter and isActive==filter value i.e true / false.
If I understand your problem correct IsActiv and BranchId are properties of the Items you are searching.
In this case try something like this:
...Where(x => searchP.Any(y=> x.ProductName.StartsWith(x.ProductName))
&& x.IsActive == true/false && x.BranchId == specificVal).ToList();
I would also recommend you check out Regex Patterns instead of your List of startcharacters.
Given something like:
public enum Filteroperator {
Equals
}
public class Filter {
static public Dictionary<Filteroperator, ExpressionType> mapOperator = new() {
{ Filteroperator.Equals, ExpressionType.Equal }
};
public string propertyName;
public Filteroperator op;
public object value;
public Filter(string p, Filteroperator o, object v) {
propertyName = p;
op = o;
value = v;
}
public Func<T, bool> FilterExpression<T>(IEnumerable<T> _) {
// p =>
var parmP = Expression.Parameter(typeof(T), "p");
// p.{propertyName}
var propE = Expression.Property(parmP, propertyName);
var valE = Expression.Constant(value);
var filterBody = Expression.MakeBinary(Filter.mapOperator[op], propE, valE);
var filterExpr = Expression.Lambda<Func<T,bool>>(filterBody, parmP);
return filterExpr.Compile();
}
}
public class FilterCollection : IList<Filter> {
// ...
}
You can build your query with:
var q = data.Where(d => searchP.Any(aDigit => d.ProductName.StartsWith(aDigit)));
foreach (var filter in filters)
q = q.Where(filter.FilterExpression(q));

Check if a list contains all of another lists items when comparing on one property

I am learning Linq and I have two object lists. I want to compare one of these lists against the other to see if all of one of the properties of the objects within it can be matched to those in the other list.
So, I provide code for that but I want to change it to Linq expressions.
var list1 = new List<Product>
{
new Product{SupplierId = 1,ProductName = "Name1"},
new Product{SupplierId = 2,ProductName = "Name2"},
new Product{SupplierId = 3,ProductName = "Name3"},
new Product{SupplierId = 4,ProductName = "Name4"}
};
var list2 = new List<Product>
{
new Product {SupplierId = 1,ProductName = "Name5"},
new Product {SupplierId = 4,ProductName = "Name6"}
};
private static bool CheckLists(List<Product> list1, List<Product> list2)
{
foreach (var product2 in list2)
{
bool result = false;
foreach (var product in list1)
{
if (product.SupplierId == product2.SupplierId)
{
result = true;
break;
}
}
if (!result)
{
return false;
}
}
return true;
}
How can I do it using LINQ?
bool existsCheck = list1.All(x => list2.Any(y => x.SupplierId == y.SupplierId));
would tell you if all of list1's items are in list2.
You want to check whether there are any IDs in list1 that aren't in list2:
if (list1.Select(p => p.SupplierId).Except(list2.Select(p => p.SupplierId)).Any())
To see that list1 contains all of list2, check if Any of list1 matches All of list2:
private static bool CheckLists(List<Product> list1, List<Product> list2) => list2.All(l2p => list1.Any(l1p => l1p.SupplierId == l2p.SupplierId));
However, I would probably write an generic extension method:
public static class Ext {
static public bool ContainsAll<T, TKey>(this List<T> containingList, List<T> containee, Func<T, TKey> key) {
var HSContainingList = new HashSet<TKey>(containingList.Select(key));
return containee.All(l2p => HSContainingList.Contains(key(l2p)));
}
static public bool ContainsAll<T>(this List<T> containingList, List<T> containee) => containingList.ContainsAll(containee, item => item);
}
Then you can call it like:
var ans = list1.ContainsAll(list2, p => p.SupplierId);

Matching invariant strings with LINQ

I got collection of structures with string property. Given an array of strings I want to check if there is any structure which inner string matches some of them in the array. I did it in this way:
struct S
{
public string s { get; set; }
}
private List<S> List = new List<S>(); // populated somewhere else
public bool Check(params string[] arr)
{
return (from s1 in List
select s1.s into s1
where !string.IsNullOrEmpty(s1)
join s2 in arr on s1.ToLowerInvariant() equals s2.ToLowerInvariant() select s1).Any();
}
Simply, I just want to achieve StringComparison.InvariantCultureIgnoreCase. Is it a proper way to do so? Is it efficient?
You could also use a HashSet, which should have a similar performance to the Dictionary:
var set = new HashSet<string>(
List.Select(x => x.s),
StringComparer.InvariantCultureIgnoreCase);
return arr.Any(set.Contains);
The most efficient way to do so is to create a dictionary based on the collection of structures you have.
var dictionary = list.ToDictionary(item=>item.s.ToLowerInvariant(),item=>item);
Then you could loop through your array of strings (O(n)):
foreach(item in array)
{
S value;
// This is an O(1) operation.
if(dictionary.TryGetValue(item.ToLowerInvariant(), out value)
{
// The TryGetValue returns true if the an item
// in the dictionary found with the specified key, item
// in our case. Otherwise, it returns false.
}
}
You could do something like this
class Program
{
struct MyStruct
{
public string Data { get; set; }
}
static void Main(string[] args)
{
var list = new List<MyStruct>();
list.Add(new MyStruct { Data = "A" });
list.Add(new MyStruct { Data = "B" });
list.Add(new MyStruct { Data = "C" });
var arr = new string[] { "a", "b" };
var result = (from s in list
from a in arr
where s.Data.Equals(a, StringComparison.InvariantCultureIgnoreCase)
select s).ToArray();
}
}

Combine/Merge 3 lists of objects based on condition

public class Derp
{
public Derp
{
listOfStrings = new List<string>();
}
public string strName;
public List<string> listOfStrings;
public int unrequiredInt;
public bool unrequiredBool;
}
List<Derp> derp1 = ... //generate data assume strName is unique in list, but not across lists;
List<Derp> derp2 = ... //generate data;
List<Derp> derp3 = ... //generate data;
List<Derp> mergedDerp = new List<Derp>();
I need to merge derp1 and derp2 and derp3 with the condition derp1[x].strName == derp2[y].strName == derp3[z].strName. The merged list should have all Derps but merge derp1,2,3 into one derp based on the condition above (unrequiredInt and unrequiredBool's content doesn't matter). I know it can be done in LINQ but I'm quite at a loss. Something like ...
mergedDerp = derp1.Join(derp2, d1 => derp1, d2 => derp2, (d1,d2) => new { ... ;
//and the next derp would be (i assume)
mergedDerp = mergedDerp.Join(derp3, md => mergedDerp, ...;
But i'm not getting it.
The result should contain a list of unique Derps by their strName, and if any Derps were merged, the listOfStrings should all be appended into the new Derp.
Using GroupBy instead of Join seems more suitable in your case:
var mergedDerp = derp1.Union(derp2).Union(derp3).GroupBy(x => x.strName)
.Select(x => new Derp
{
strName = x.Key,
// I guess you want to merge the list of strings as well?
listOfStrings = x.SelectMany(d => d.listOfStrings).ToList()
// Leave unrequired fields as default or just use the first derp's value
// unrequiredInt = x.First().unrequiredInt,
// unrequiredBool = x.First().unrequiredBool,
})
.ToList();
It sounds like you want to determine equality based on the strName value. If so, simply implement the Equals and GetHashCode methods on the object:
public class Derp
{
public Derp()
{
listOfStrings = new List<string>();
}
public string strName;
public List<string> listOfStrings;
public int unrequiredInt;
public bool unrequiredBool;
public override bool Equals(object obj)
{
return ((Derp) obj).strName.Equals(strName);
}
public override int GetHashCode()
{
return strName.GetHashCode();
}
}
Then when you combine them, you can just use Union and Distinct:
var derp1 = new List<Derp>();
derp1.Add(new Derp() {strName = "X"});
derp1.Add(new Derp() { strName = "Y" });
derp1.Add(new Derp() { strName = "Z" });
var derp2 = new List<Derp>();
derp2.Add(new Derp() {strName = "A"});
derp2.Add(new Derp() { strName = "B" });
derp2.Add(new Derp() { strName = "X" });
var derp3 = new List<Derp>();
derp3.Add(new Derp() { strName = "J" });
derp3.Add(new Derp() { strName = "B" });
derp3.Add(new Derp() { strName = "X" });
var merged = derp1.Union(derp2.Union(derp3)).Distinct();
Console.WriteLine(merged.Count()); // Returns 6: X, Y, Z, A, B, J

TryGetValue() in linq expression with anonymous types

I can't use TryGetValue() from dictionary in linq expression with anonymous type.
Dictionary<string, string> dict = new Dictionary<string, string>()
{
{"1", "one"},
{"2", "two"},
{"3", "three"}
};
public string getValueByKey(string value)
{
string sColumnType = "";
dict.TryGetValue(value, out sColumnType);
return sColumnType;
}
[WebMethod]
public string getData(string name)
{
var result = (from z in myObject
where z.name == name
select new
{
a = z.A,
b = z.B,
c=getValueByKey(z.C) //fails there
}).ToList();
return new JavaScriptSerializer().Serialize(result);
}
Please, tell me how I can get value by key in dictionary?
The problem is most likely that it doesn't know how to translate the call to getValueByKey into an expression for your repository -- because it can't. Materialize the query first, using ToList(), so that it's now doing LINQ to Objects, then do the selection to the anonymous type.
[WebMethod]
public string getData(string name)
{
var result = myObject.Where( z => z.name == name )
.ToList()
.Select( k =>
new
{
a = k.A,
b = k.B,
c = getValueByKey(k.C)
})
.ToList();
return new JavaScriptSerializer().Serialize(result);
}

Categories

Resources