I have two string arrays
string[] a = ...
string[] b = ...
I want to remove any items from a that also exist in b or return a new array with only those items that exist only in a.
So, for example, if
a={"a", "b", "c"};
and,
b={"b"}
then the result should be
{"a", "c"}
Is there a neat lambda expression or Linq or something I can use to do this?
Thanks,
Sachin
I believe Except will do what you want. Remember, Except, like most LINQ Extension methods, will not modify the existing collection. It will return a new collection.
c = a.Except(b)
Related
I need a fast way to create immutable Lists in one line just like Java's List.of(), but in C#. What's the equivalent to this syntax?
List<String> strings = List.of("first", "second");
You could use ImmutableList.Create
ImmutableList<string> list = ImmutableList.Create("first", "second");
You could also use AsReadOnly which returns a wrapper for the list:
ReadOnlyCollection<string> readonlyList = new List<string> { "first", "second" }.AsReadOnly();
However, as this is just a wrappper you can always modify the underlying list, so it's not truly immutable.
Try this code:
var strings = new List<string> { "first", "second" };
I have two lists of string. I want to compare each elements in one list with another and if at least one of them match then do some processing else dont do anything.
I dont know how to do. I do have the following lists and the code I used was SequenceEqual but my lead said its wrong as it just compares if its equal or not and does nothing. I couldn't disagree and I want to achieve my intended functionality I mentioned above. Please help. As you seem, order doesn't matter, here 123 is in both list but in different order, so it matches and hence do some processing as per my requirement.
List<string> list1 = new List<string> () { "123", "234" };
List<string> list2 = new List<string> () { "333", "234" , "123"};
You can use the Any method for this :
var matchfound = list1.Any(x=> list2.Contains(x));
Now you can do conditional block on the matchFound if it returns true you can process what ever is required.
if you want to do case insentitive comparison then you will need to use String.Equals and can specify if case does not matter for comaparing those.
You can use Intersect to find common elements:
var intersecting = list1.Intersect(list2);
If you just want to know if there are common elements append .Any():
bool atLeastOneCommonElement = intersecting.Any();
If you want to process them:
foreach(var commonElement in intersecting)
{
// do something ...
}
You could check with Intersect and Any
var matchFound = list1.Intersect(list2).Any();
For example,
List<string> list1 = new List<string>{ "123", "234" };
List<string> list2 = new List<string>{ "333", "234" , "123"};
var result = list1.Intersect(list2).Any();
Output True
List<string> list3 = new List<string>{"5656","8989"};
result = list1.Intersect(list3).Any();
Output False
You need to take all those item that are matches from both list and then do code if match found like
foreach (var item in list1.Where(x => list2.Contains(x)))
{
//do some processing here
Console.WriteLine($"Match found: {item}");
}
In above code foreach iterate when item present in both list.
Output:
Use LINQ to find the matches; and then check the resulting array size as follows:
var intersect = list1.Where(el1=>list2.Any(el2=>el2==el1));
var isMatch = intersect.Count > 0;
Suppose I have two collections as follows:
Collection1:
"A1"
"A1"
"M1"
"M2"
Collection2:
"M2"
"M3"
"M1"
"A1"
"A1"
"A2"
all the values are string values. I want to know if all the elements in Collection1 are contained in Collection2, but I have no guarantee on the order and a set may have multiple entries with the same value. In this case, Collection2 does contain Collection1 because Collection2 has two A1's, M1 and M2. Theres the obvious way: sorting both collections and popping off values as i find matches, but I was wondering if there's a faster more efficient way to do this. Again with the initial collections I have no guarantee on the order or how many times a given value will appear
EDIT: Changed set to collection just to clear up that these aren't sets as they can contain duplicate values
The most concise way I know of:
//determine if Set2 contains all of the elements in Set1
bool containsAll = Set1.All(s => Set2.Contains(s));
Yes, there is a faster way, provided you're not space-constrained. (See space/time tradeoff.)
The algorithm:
Just insert all the elements in Set2 into a hashtable (in C# 3.5, that's a HashSet<string>), and then go through all the elements of Set1 and check if they're in the hashtable. This method is faster (Θ(m + n) time complexity), but uses O(n) space.
Alternatively, just say:
bool isSuperset = new HashSet<string>(set2).IsSupersetOf(set1);
Edit 1:
For those people concerned about the possibility of duplicates (and hence the misnomer "set"), the idea can easily be extended:
Just make a new Dictionary<string, int> representing the count of each word in the super-list (add one to the count each time you see an instance of an existing word, and add the word with a count of 1 if it's not in the dictionary), and then go through the sub-list and decrement the count each time. If every word exists in the dictionary and the count is never zero when you try to decrement it, then the subset is in fact a sub-list; otherwise, you had too many instances of a word (or it didn't exist at all), so it's not a real sub-list.
Edit 2:
If the strings are very big and you're concerned about space efficiency, and an algorithm that works with (very) high probability works for you, then try storing a hash of each string instead. It's technically not guaranteed to work, but the probability of it not working is pretty darn low.
The problem I see with the HashSet, Intersect, and other Set theory answers is that you do contain duplicates, and "A set is a collection that contains no duplicate elements". Here's a way to handle the duplicate cases.
var list1 = new List<string> { "A1", "A1", "M1", "M2" };
var list2 = new List<string> { "M2", "M3", "M1", "A1", "A1", "A2" };
// Remove returns true if it was able to remove it, and it won't be there to be matched again if there's a duplicate in list1
bool areAllPresent = list1.All(i => list2.Remove(i));
EDIT: I renamed from Set1 and Set2 to list1 and list2 to appease Mehrdad.
EDIT 2: The comment implies it, but I wanted to explicitly state that this does alter list2. Only do it this way if you're using it as a comparison or control but don't need the contents afterwards.
Check out linq...
string[] set1 = {"A1", "A1", "M1", "M2" };
string[] set2 = { "M2", "M3", "M1", "A1", "A1", "A2" };
var matching = set1.Intersect(set2);
foreach (string x in matching)
{
Console.WriteLine(x);
}
Similar one
string[] set1 = new string[] { "a1","a2","a3","a4","a5","aa","ab" };
string[] set2 = new string[] {"m1","m2","a4","a6","a1" };
var a = set1.Select(set => set2.Contains(set));
If I have:
List<string> myList1;
List<string> myList2;
myList1 = getMeAList();
// Checked myList1, it contains 4 strings
myList2 = getMeAnotherList();
// Checked myList2, it contains 6 strings
myList1.Concat(myList2);
// Checked mylist1, it contains 4 strings... why?
I ran code similar to this in Visual Studio 2008 and set break points after each execution. After myList1 = getMeAList();, myList1 contains four strings, and I pressed the plus button to make sure they weren't all nulls.
After myList2 = getMeAnotherList();, myList2 contains six strings, and I checked to make sure they weren't null... After myList1.Concat(myList2); myList1 contained only four strings. Why is that?
Concat returns a new sequence without modifying the original list. Try myList1.AddRange(myList2).
Try this:
myList1 = myList1.Concat(myList2).ToList();
Concat returns an IEnumerable<T> that is the two lists put together, it doesn't modify either existing list. Also, since it returns an IEnumerable, if you want to assign it to a variable that is List<T>, you'll have to call ToList() on the IEnumerable<T> that is returned.
targetList = list1.Concat(list2).ToList();
It's working fine I think so. As previously said, Concat returns a new sequence and while converting the result to List, it does the job perfectly.
It also worth noting that Concat works in constant time and in constant memory.
For example, the following code
long boundary = 60000000;
for (long i = 0; i < boundary; i++)
{
list1.Add(i);
list2.Add(i);
}
var listConcat = list1.Concat(list2);
var list = listConcat.ToList();
list1.AddRange(list2);
gives the following timing/memory metrics:
After lists filled mem used: 1048730 KB
concat two enumerables: 00:00:00.0023309 mem used: 1048730 KB
convert concat to list: 00:00:03.7430633 mem used: 2097307 KB
list1.AddRange(list2) : 00:00:00.8439870 mem used: 2621595 KB
I know this is old but I came upon this post quickly thinking Concat would be my answer. Union worked great for me. Note, it returns only unique values but knowing that I was getting unique values anyway this solution worked for me.
namespace TestProject
{
public partial class Form1 :Form
{
public Form1()
{
InitializeComponent();
List<string> FirstList = new List<string>();
FirstList.Add("1234");
FirstList.Add("4567");
// In my code, I know I would not have this here but I put it in as a demonstration that it will not be in the secondList twice
FirstList.Add("Three");
List<string> secondList = GetList(FirstList);
foreach (string item in secondList)
Console.WriteLine(item);
}
private List<String> GetList(List<string> SortBy)
{
List<string> list = new List<string>();
list.Add("One");
list.Add("Two");
list.Add("Three");
list = list.Union(SortBy).ToList();
return list;
}
}
}
The output is:
One
Two
Three
1234
4567
Take a look at my implementation. It's safe from null lists.
IList<string> all= new List<string>();
if (letterForm.SecretaryPhone!=null)// first list may be null
all=all.Concat(letterForm.SecretaryPhone).ToList();
if (letterForm.EmployeePhone != null)// second list may be null
all= all.Concat(letterForm.EmployeePhone).ToList();
if (letterForm.DepartmentManagerName != null) // this is not list (its just string variable) so wrap it inside list then concat it
all = all.Concat(new []{letterForm.DepartmentManagerPhone}).ToList();
I have looked this up on the net but I am asking this to make sure I haven't missed out on something. Is there a built-in function to convert HashSets to Lists in C#? I need to avoid duplicity of elements but I need to return a List.
Here's how I would do it:
using System.Linq;
HashSet<int> hset = new HashSet<int>();
hset.Add(10);
List<int> hList= hset.ToList();
HashSet is, by definition, containing no duplicates. So there is no need for Distinct.
Two equivalent options:
HashSet<string> stringSet = new HashSet<string> { "a", "b", "c" };
// LINQ's ToList extension method
List<string> stringList1 = stringSet.ToList();
// Or just a constructor
List<string> stringList2 = new List<string>(stringSet);
Personally I'd prefer calling ToList is it means you don't need to restate the type of the list.
Contrary to my previous thoughts, both ways allow covariance to be easily expressed in C# 4:
HashSet<Banana> bananas = new HashSet<Banana>();
List<Fruit> fruit1 = bananas.ToList<Fruit>();
List<Fruit> fruit2 = new List<Fruit>(bananas);
List<ListItemType> = new List<ListItemType>(hashSetCollection);
There is the Linq extension method ToList<T>() which will do that (It is defined on IEnumerable<T> which is implemented by HashSet<T>).
Just make sure you are using System.Linq;
As you are obviously aware the HashSet will ensure you have no duplicates, and this function will allow you to return it as an IList<T>.