Comparing strings in 2 list using Linq - c#

I need to compare to list with strings, and find whitch elements that are similar. ie.
List<string> listA = {"ProfileHeight", "ProfileWidth", "WebThickness"}
List<string> listB ={"ProfileHeightVisibility", "ProfileWidthVisibility", "WebThicknessVisibility", "FlangeThicknessVisibility", "DepthVisibility"}
I was wondering if it is possible to use Linq. I want the result to be a list with the elements from ListB that have a name similar to the strings in listA. Where the statemant i.e. "ProfileHeightVisibility.Contains("ProfileHeight") = true

You mean you want items in listB where it contains any of the elements in listA?
listB.Where(b => listA.Any(a => b.Contains(a))

You can do it in LINQ:
listB.Where(e => listA.Any(a => e.Contains(a)));
Bear in mind that this algorithm has a runtime cost similar to O(n^2), it will therefore slow down quickly as the number of elements in your lists grows.

You can also implement things like that :
public class EqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return y.Contains(x);
}
public int GetHashCode(string obj)
{
return 1;
}
}
and then use intersect like that:
listB.Intersect(listA, new EqualityComparer()).ToList();

Yes you can do such thing with LINQ
public List<string> GetSimilarStrings(List<string> list1, List<string> list2)
{
return (from item in list1 from item2 in list2 where string.Equals(item,item2,StringComparison.InvariantCultureIgnoreCase) select item).ToList();
}

List<string> listA = new List<string> { "zinmin", "zin1", "zin2" };
List<string> listB = new List<string> { "zinmin", "min1", "min2" };
List<string> matching = listA.Intersect(listB).ToList();
matching result will come {"zinmin"} . Sorting order doesn't matter.

Related

Filter a list of address objects by a list of string postcodes [duplicate]

I have a list of parameters like this:
public class parameter
{
public string name {get; set;}
public string paramtype {get; set;}
public string source {get; set;}
}
IEnumerable<Parameter> parameters;
And a array of strings i want to check it against.
string[] myStrings = new string[] { "one", "two"};
I want to iterate over the parameter list and check if the source property is equal to any of the myStrings array. I can do this with nested foreach's but i would like to learn how to do it in a nicer way as i have been playing around with linq and like the extension methods on enumerable like where etc so nested foreachs just feel wrong. Is there a more elegant preferred linq/lambda/delegete way to do this.
Thanks
You could use a nested Any() for this check which is available on any Enumerable:
bool hasMatch = myStrings.Any(x => parameters.Any(y => y.source == x));
Faster performing on larger collections would be to project parameters to source and then use Intersect which internally uses a HashSet<T> so instead of O(n^2) for the first approach (the equivalent of two nested loops) you can do the check in O(n) :
bool hasMatch = parameters.Select(x => x.source)
.Intersect(myStrings)
.Any();
Also as a side comment you should capitalize your class names and property names to conform with the C# style guidelines.
Here is a sample to find if there are match elements in another list
List<int> nums1 = new List<int> { 2, 4, 6, 8, 10 };
List<int> nums2 = new List<int> { 1, 3, 6, 9, 12};
if (nums1.Any(x => nums2.Any(y => y == x)))
{
Console.WriteLine("There are equal elements");
}
else
{
Console.WriteLine("No Match Found!");
}
If both the list are too big and when we use lamda expression then it will take a long time to fetch . Better to use linq in this case to fetch parameters list:
var items = (from x in parameters
join y in myStrings on x.Source equals y
select x)
.ToList();
list1.Select(l1 => l1.Id).Intersect(list2.Select(l2 => l2.Id)).ToList();
var list1 = await _service1.GetAll();
var list2 = await _service2.GetAll();
// Create a list of Ids from list1
var list1_Ids = list1.Select(l => l.Id).ToList();
// filter list2 according to list1 Ids
var list2 = list2.Where(l => list1_Ids.Contains(l.Id)).ToList();

Intersect 2 list if first list contains part of another

I have 2 list of string, list A and list B. list A is a list of strings containing paths, and the other contains strings of folder. Examples:
List<string> listA = new List<string>{ "c:\myPath\FolderA\blabla\", "c:\myPath\FolderB\blabla2\", "c:\myPath\FolderA\blabla3\" "c:\myPath\FolderC\blabla\"};
List<string> listB = new List<string> { "FolderA, FolderC"};
I want to have a method that compares the 2 list. If listA contains any of listB it is valid, else I don't want it. So based on this logic I'd have:
List<string> listReturn = new List<string>{ "c:\myPath\FolderA\blabla\", "c:\myPath\FolderA\blabla3\" "c:\myPath\FolderC\blabla\"};
So far all I've done is a method that iterates through the first list and does a Contain call on the string with a Linq Any call, like this:
private static List<string> FilterList(List<string> listA, List<string> listB)
{
List<string> listReturn = new List<string>();
foreach (string val in listA)
{
if (listB.Any(item => val.Contains(item)))
{
listReturn.Add(val);
}
}
return listReturn;
}
It's not bad, but I want to use a Linq approach or a .NET approach if there's an Intersect method available for this. Thank you.
Use Where() against the listA to filter items in this list and Exists() on listB for the filter condition:
List<string> listA = new List<string> {#"c:\myPath\FolderA\blabla\", #"c:\myPath\FolderA\blabla2\", #"c:\myPath\Folder\blabla3\", #"c:\myPath\FolderC\blabla\"};
List<string> listB = new List<string> { "FolderA", "FolderC" };
var intersect = listA.Where(a => listB.Exists(b => a.Contains(b)));
Try this
var result = listA.Where(i => listB.Any(y => i.Contains(y)).ToList();

How to replace nested loops with LINQ - in a clean, manageable manner

Codewise, what it the cleanest way to do this using linq? Below, I have a crude example where I want to find a matching class instance based on name.
class item
{
string name {get;set;}
int identifier {get;set;}
}
void DoSomething()
{
List<item> List1 = GetSampleItems();
List<item> List2 = GetOtherSampleItems();
for(int a=0;a<List1.count;a++)
{
for(int b=0;b<List2.count;b++)
{
if(List1[a].identifier == List2[b].identifier)
{
List1[a].name = List2[b].name;
}
}
}
}
Linq is for querying, not updating, so you'll still need to loop through the results to make the changes, but you can join to match up the two lists like so:
var query = from l1 in List1
join l2 in List2
on l1.identifier equals l2.identifier
select new {l1, l2};
Now loop through the query to update the l1 items:
foreach(var item in query)
item.l1.name = item.l2.name;
As a side note, there's nothing wrong with the way you're doing it (other than you could break out of the inner loop if a match is found). If you understand how it works and the performance is acceptable, there's no compelling reason to change it.
This should work:
var query = from l1 in List1
join l2 in List2 on l1.identifier equals l2.identifier
select new
{
l1values = l1,
l2Name = l2.name
};
foreach(var item in query)
item.l1Values.name = item.l2Name;
A better way is using a Dictionary<TK,TV>:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
List1.ForEach(x => {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
});
Or as #Rawling says, use a foreach loop instead:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
foreach(item x in List1) {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
}
Ideone demo (with slight modifications to your item class).
This runs on average in linear time whereas your approach runs in quadratic time.
The assumption is however that the identifiers are unique: no two elements in the same list can have the same identifier.
A concluding note is that variables in general start with a lowercase character so list1 and list2 whereas classes and properties start with a capital one (this Item, Identifier and Name).

Overlay/Join two collections with Linq

I have the following scenario:
List 1 has 20 items of type TItem, List 2 has 5 items of the same type. List 1 already contains the items from List 2 but in a different state. I want to overwrite the 5 items in List 1 with the items from List 2.
I thought a join might work, but I want to overwrite the items in List 1, not join them together and have duplicates.
There is a unique key that can be used to find which items to overwrite in List 1 the key is of type int
You could use the built in Linq .Except() but it wants an IEqualityComparer so use a fluid version of .Except() instead.
Assuming an object with an integer key as you indicated:
public class Item
{
public int Key { get; set; }
public int Value { get; set; }
public override string ToString()
{
return String.Format("{{{0}:{1}}}", Key, Value);
}
}
The original list of objects can be merged with the changed one as follows:
IEnumerable<Item> original = new[] { 1, 2, 3, 4, 5 }.Select(x => new Item
{
Key = x,
Value = x
});
IEnumerable<Item> changed = new[] { 2, 3, 5 }.Select(x => new Item
{
Key = x,
Value = x * x
});
IEnumerable<Item> result = original.Except(changed, x => x.Key).Concat(changed);
result.ForEach(Console.WriteLine);
output:
{1:1}
{4:4}
{2:4}
{3:9}
{5:25}
LINQ isn't used to perform actual modifications to the underlying data sources; it's strictly a query language. You could, of course, do an outer join on List2 from List1 and select List2's entity if it's not null and List1's entity if it is, but that is going to give you an IEnumerable<> of the results; it won't actually modify the collection. You could do a ToList() on the result and assign it to List1, but that would change the reference; I don't know if that would affect the rest of your application.
Taking your question literally, in that you want to REPLACE the items in List1 with those from List2 if they exist, then you'll have to do that manually in a for loop over List1, checking for the existence of a corresponding entry in List2 and replacing the List1 entry by index with that from List2.
As Adam says, LINQ is about querying. However, you can create a new collection in the right way using Enumerable.Union. You'd need to create an appropriate IEqualityComparer though - it would be nice to have UnionBy. (Another one for MoreLINQ perhaps?)
Basically:
var list3 = list2.Union(list1, keyComparer);
Where keyComparer would be an implementation to compare the two keys. MiscUtil contains a ProjectionEqualityComparer which would make this slightly easier.
Alternatively, you could use DistinctBy from MoreLINQ after concatenation:
var list3 = list2.Concat(list1).DistinctBy(item => item.Key);
Here's a solution with GroupJoin.
List<string> source = new List<string>() { "1", "22", "333" };
List<string> modifications = new List<string>() { "4", "555"};
//alternate implementation
//List<string> result = source.GroupJoin(
// modifications,
// s => s.Length,
// m => m.Length,
// (s, g) => g.Any() ? g.First() : s
//).ToList();
List<string> result =
(
from s in source
join m in modifications
on s.Length equals m.Length into g
select g.Any() ? g.First() : s
).ToList();
foreach (string s in result)
Console.WriteLine(s);
Hmm, how about a re-usable extension method while I'm at it:
public static IEnumerable<T> UnionBy<T, U>
(
this IEnumerable<T> source,
IEnumerable<T> otherSource,
Func<T, U> selector
)
{
return source.GroupJoin(
otherSource,
selector,
selector,
(s, g) => g.Any() ? g.First() : s
);
}
Which is called by:
List<string> result = source
.UnionBy(modifications, s => s.Length)
.ToList();

Using lambda expressions to get a subset where array elements are equal

I have an interesting problem, and I can't seem to figure out the lambda expression to make this work.
I have the following code:
List<string[]> list = GetSomeData(); // Returns large number of string[]'s
List<string[]> list2 = GetSomeData2(); // similar data, but smaller subset
List<string[]> newList = list.FindAll(predicate(string[] line){
return (???);
});
I want to return only those records in list in which element 0 of each string[] is equal to one of the element 0's in list2.
list contains data like this:
"000", "Data", "more data", "etc..."
list2 contains data like this:
"000", "different data", "even more different data"
Fundamentally, i could write this code like this:
List<string[]> newList = new List<string[]>();
foreach(var e in list)
{
foreach(var e2 in list2)
{
if (e[0] == e2[0])
newList.Add(e);
}
}
return newList;
But, i'm trying to use generics and lambda's more, so i'm looking for a nice clean solution. This one is frustrating me though.. maybe a Find inside of a Find?
EDIT:
Marc's answer below lead me to experiment with a varation that looks like this:
var z = list.Where(x => list2.Select(y => y[0]).Contains(x[0])).ToList();
I'm not sure how efficent this is, but it works and is sufficiently succinct. Anyone else have any suggestions?
You could join? I'd use two steps myself, though:
var keys = new HashSet<string>(list2.Select(x => x[0]));
var data = list.Where(x => keys.Contains(x[0]));
If you only have .NET 2.0, then either install LINQBridge and use the above (or similar with a Dictionary<> if LINQBridge doesn't include HashSet<>), or perhaps use nested Find:
var data = list.FindAll(arr => list2.Find(arr2 => arr2[0] == arr[0]) != null);
note though that the Find approach is O(n*m), where-as the HashSet<> approach is O(n+m)...
You could use the Intersect extension method in System.Linq, but you would need to provide an IEqualityComparer to do the work.
static void Main(string[] args)
{
List<string[]> data1 = new List<string[]>();
List<string[]> data2 = new List<string[]>();
var result = data1.Intersect(data2, new Comparer());
}
class Comparer : IEqualityComparer<string[]>
{
#region IEqualityComparer<string[]> Members
bool IEqualityComparer<string[]>.Equals(string[] x, string[] y)
{
return x[0] == y[0];
}
int IEqualityComparer<string[]>.GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
#endregion
}
Intersect may work for you.
Intersect finds all the items that are in both lists.
Ok re-read the question. Intersect doesn't take the order into account.
I have written a slightly more complex linq expression that will return a list of items that are in the same position (index) with the same value.
List<String> list1 = new List<String>() {"000","33", "22", "11", "111"};
List<String> list2 = new List<String>() {"000", "22", "33", "11"};
List<String> subList = list1.Select ((value, index) => new { Value = value, Index = index})
.Where(w => list2.Skip(w.Index).FirstOrDefault() == w.Value )
.Select (s => s.Value).ToList();
Result: {"000", "11"}
Explanation of the query:
Select a set of values and position of that value.
Filter that set where the item in the same position in the second list has the same value.
Select just the value (not the index as well).
Note I used:
list2.Skip(w.Index).FirstOrDefault()
//instead of
list2[w.Index]
So that it will handle lists of different lengths.
If you know the lists will be the same length or list1 will always be shorter then list2[w.Index] would probably a bit faster.

Categories