I have a list of strings (thing1-3, else1-3, other1-3), and I want to create a simplified list with just (thing, else, other). Seems straight forward (or at least this was with the Classic VB Dictionary .Exists function), but I'm stuck. So I'm checking if the string startswith one of my simplified strings, then if the simplified list does not contain that string, add it. But checking if the simplified list contains the string already is throwing me off.
List<string> myList = new List<string>(new string[] { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" });
List<string> myListSimplified = new List<string>();
foreach (string s in myList)
{
if (s.StartsWith("thing"))
{
if (!myListSimplifed.Contains("thing")) { myListSimplifed.Add("thing"); }
}
if (s.StartsWith("else"))
{
if (!myListSimplifed.Contains("else")) { myListSimplifed.Add("else"); }
}
if (s.StartsWith("other"))
{
if (!myListSimplifed.Contains("other")) { myListSimplifed.Add("other"); }
}
}
I would expect this mySimplifiedList to contain "thing", "else", "other", but it contains thing1-3, else1-2, other1-3.
if (myListSimplified.Exists("thing")) { }
IntelliSense returns "cannot convert from 'string' to 'System.Predicate'
ok.. so this:
if (!myListSimplified.Any(str => str.Contains("thing"))) { myListSimplified.Add("thing"); }
Or
if (!myListSimplified.Exists(str => str.Contains("thing"))) { myListSimplified.Add("thing"); }
None of these work.
Obviously I can create a method to iterate through the list and compare it to a string, but this functionality seems to be too fundamental to lists that MS left it out... Also seems silly to be passing lists around...
private bool Exists(List<string> lList, string sCompare)
{
bool bVal = false;
foreach (string s in lList)
{
if (s == sCompare) { bVal = true; }
break;
}
return bVal;
}
I'm not sure what your problem is:
First of all, it seems your first code snippet contains a typo: you have List<string> myListSimplified but then inside the foreach you reference myListSimplifed (missing 'i' after the 'f').
If I correct that typo and run your code, then I get a list containing {"thing", "else", "other" }, which seems to be what you also expect.
Besides the typo in myListSimplifed vs myListSimplified your code sample produces what you want it to do.
Not what you ask for, but you can have the same effect with far fewer lines of code:
var myList = new List<string> {"thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3"};
var myListSimplified = myList.Select(s => new string(s.Where(char.IsLetter).ToArray())).Distinct();
Lists are generic data types. They don't know what strings are, and so they're not provided out of the box with facilities to search for items that StartWith or Contain other strings. This sort of operation doesn't make sense, generically speaking. This is why you have operations like Any that take a lambda function to provide your own logic:
if (myList.Any(str => str.StartsWith("thing")))
mySimplifiedList.Add("thing");
I've tested this and it works fine.
Of course, if you have multiple strings you want to extract, you should probably make this more generic. The simplest way is to extract the lines above to a method that receives the substring ("thing", in this case) as a parameter. A more generic approach (assuming it matches your data and logic) would be to go over all strings, strip all numerals from it, and store that. Assuming StripNumerals is a method that receives a string, it could look like this, with Distinct ensuring you have only one instance of each string.
var simplifiedList = myList.Select(StripNumerals).Distinct().ToList();
I have noticed your typo when putting the code in Visual Studio. The result is correct, but your algorithm is far from being generic. What you can try:
var is useful to simplify declaration
list initialization can be simplified
obtain a list by stripping all digits and perform a distinct on it
var myList = new List<string>() { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" };
var listWithoutNumbers = myList.Select(s =>
{
Regex rgx = new Regex("[0-9]");
return rgx.Replace(s, "");
});
var simplifiedList = listWithoutNumbers.Distinct();
Your original solution works, apart from the typo.
However, if you want more generic solution, you could use something like this
List<string> myList = new List<string>(new string[] { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" });
List<string> myListSimplified = myList.Select(s => new String(s.Where(Char.IsLetter).ToArray())).Distinct().ToList();
Don't forget to add
using System.Linq;
if you will try this solution.
Related
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;
I have a list of strings. Neither the number of nor the order of these strings is guaranteed. The only thing that is certain is that this list WILL at least contain my 3 strings of interest and inside those strings we'll say "string1", "string2", and "string3" will be contained within them respectively (i.e. these strings can contain more information but those keywords will definitely be in there). I then want to use these results in a function.
My current implementation to solve this is as such:
foreach(var item in myList)
{
if (item.Contains("string1"))
{
myFunction1(item);
}
else if (item.Contains("string2"))
{
myFunction2(item);
}
else if (item.Contains("string3"))
{
myFunction3(item);
}
}
Is there a better way to check string lists and apply functions to those items that match some criteria?
One approach is to use Regex for the fixed list of strings, and check which group is present, like this:
// Note the matching groups around each string
var regex = new Regex("(string1)|(string2)|(string3)");
foreach(var item in myList) {
var match = regex.Match(item);
if (!match.Success) {
continue;
}
if (match.Groups[1].Success) {
myFunction1(item);
}
else if (match.Groups[2].Success)
{
myFunction2(item);
}
else if (match.Groups[3].Success)
{
myFunction3(item);
}
}
This way all three matches would be done with a single pass through the target string.
You could reduce some of the duplicated code in the if statements by creating a Dictionary that maps the strings to their respective functions. (This snippet assumes that myList contains string values, but can easily be adapted to a list of any type.)
Dictionary<string, Action<string>> actions = new Dictionary<string, Action<string>>
{
["string1"] = myFunction1,
["string2"] = myFunction2,
["string3"] = myFunction3
};
foreach (var item in myList)
{
foreach (var action in actions)
{
if (item.Contains(action.Key))
{
action.Value(item);
break;
}
}
}
For a list of only three items, this might not be much of an improvement, but if you have a large list of strings/functions to search for it can make your code much shorter. It also means that adding a new string/function pair is a one-line change. The biggest downside is that the foreach loop is a bit more difficult to read.
I have an ObservableCollection<T> that I use for binding that I want to put into a String.Join statement, but I don't understand why it is giving the results I am getting and how to fix it?
This is the code I am using to get the result,
First I am getting the data I need via this LINQ query,
public static IEnumerable<string> GetNursingHomeNames(string home)
{
return DataContext.NursingHomeNameServerTables.Where(p => p.Nursing_Home_Section == home)
.Select(p => p.Nursing_Home_Name).Distinct();
}
I then put it into the ObservableCollection<T> (You may be wondering why I am not using an ObservableCollection<T> with the LINQ query, but I have my reasons.)
public static void NursingHomeNamesCollection(ObservableCollection<string> nursingHomeNames, string nursingHomeSection)
{
var homeNames = GetNursingHomeNames(nursingHomeSection);
if (homeNames == null)
{
return;
}
foreach (var item in homeNames)
{
nursingHomeNames.Add(item);
}
}
This is the property in the main window,
public ObservableCollection<string> NursingHomeNames { get; set; } =
new ObservableCollection<string>();
Then
Than I use Join to get the results for a specific purpose I need,
var result = String.Join(#",", NursingHomeNames.ToList());
And this gives the following result where there is no delimiter only a space,
foo bar bat baz
However, if just do this,
ObservableCollection<string> observableCol = new ObservableCollection<string>() { "foo", "bar", "bat", "baz" };
var result = String.Join(#",", observableCol.ToList());
The result is displayed with the delimiters in place.
foo,bar,bat,baz
Why is it doing this and is there a way to ensure the delimiters are correctly placed?
I know I have to work on my naming conventions.
EDIT: In the debuger, this is what I see,
When assigning the collection to a variable named data and viewing the results in the Watch Window
var data = NursingHomeNames.ToList();
Count = 4
[0] "foo"
[1] "bar"
[2] "bat"
[3] "baz"
However, I cannot reproduce this using any other code that does not use the LINQ query that pulls the data from the database. I tried making a new list and passing that list through the same code, but the error does not occur. I am sorry, but I can't post an example that can be reproduced.
As it turns out, after weeks of effort to figure this out, the answer to be had was with the comment that #Panagiotis Kanavos and #CodeCaster made.
I was using a Unicode character that looked like a comma and it was therefore creating a different behavior than what I was expecting.
In method
public static void NursingHomeNamesCollection(string nursingHomeSection)
you get string parameter to input. After in this method scope you add this string into static collection, but item in this scope is a char.
foreach (var item in homeNames)
You're trying to add a character at a time and join one big string. You need get collection to input of this method.
I have a string of comma separated values, to get a string array from this I use
string[] values = value.Split(',');
I want to trim all these values by creating a new list of string and calling a foreach on the array like this
List<string> trimmedValues = new List<string>();
foreach (string str in values)
trimmedValues.Add(str.Trim());
Is there a more efficent way to do this with less code say by calling a method on the array itself?
Use linq
List<string> trimmedValues = values.Select(v => v.trim()).toList()
Try this :
var myTrimResult = "a ,b , c ".Split(',').Select(x => x.Trim());
The "myTrimResult" variable will contain trimmed elements.
To effectively reduce code bloat, I suggest to use an extension.
Declare a method like the following, in a common context in your project (or even better, in an external helper DLL to carry among different projects):
public static List<string> TrimList(this string[] array)
{
var list = new List<string>();
foreach (var s in array)
{
list.Add(s.Trim());
}
return list;
}
Now, in your code, you can simply use:
var trimmedValues = values.TrimList();
I find it more readable than using LINQ expression in code
How is it possible to initialize (with a C# initializer) a list of strings? I have tried with the example below but it's not working.
List<string> optionList = new List<string>
{
"AdditionalCardPersonAddressType","AutomaticRaiseCreditLimit","CardDeliveryTimeWeekDay"
}();
Just remove () at the end.
List<string> optionList = new List<string>
{ "AdditionalCardPersonAdressType", /* rest of elements */ };
List<string> mylist = new List<string>(new string[] { "element1", "element2", "element3" });
You haven't really asked a question, but the code should be
List<string> optionList = new List<string> { "string1", "string2", ..., "stringN"};
i.e. no trailing () after the list.
var animals = new List<string> { "bird", "dog" };
List<string> animals= new List<string> { "bird", "dog" };
Above two are the shortest ways, please see https://www.dotnetperls.com/list
Your function is just fine but isn't working because you put the () after the last }. If you move the () to the top just next to new List<string>() the error stops.
Sample below:
List<string> optionList = new List<string>()
{
"AdditionalCardPersonAdressType","AutomaticRaiseCreditLimit","CardDeliveryTimeWeekDay"
};
If you are using C# 9.0 and up you can use the new feature target-typed new expressions Link
Example:
List<string> stringList = new(){"item1","item2", "item3"} ;
The right way to initialize along with declaration is :
List<string> optionList = new List<string>()
{
"AdditionalCardPersonAdressType","AutomaticRaiseCreditLimit","CardDeliveryTimeWeekDay"
};
This is how you initialize and also you can use List.Add() in case you want to make it more dynamic.
List<string> optionList = new List<string> {"AdditionalCardPersonAdressType"};
optionList.Add("AutomaticRaiseCreditLimit");
optionList.Add("CardDeliveryTimeWeekDay");
In this way, if you are taking values in from IO, you can add it to a dynamically allocated list.
Move round brackets like this:
var optionList = new List<string>(){"AdditionalCardPersonAdressType","AutomaticRaiseCreditLimit","CardDeliveryTimeWeekDay"};
One really cool feature is that list initializer works just fine with custom classes too: you have just to implement the IEnumerable interface and have a method called Add.
So for example if you have a custom class like this:
class MyCustomCollection : System.Collections.IEnumerable
{
List<string> _items = new List<string>();
public void Add(string item)
{
_items.Add(item);
}
public IEnumerator GetEnumerator()
{
return _items.GetEnumerator();
}
}
this will work:
var myTestCollection = new MyCustomCollection()
{
"item1",
"item2"
}
There is something else that you might be missing that hasn't been mentioned. I think it might be the problem you are having as I suspect you already tried removing the trailing () and still got an error.
First, like others have mentioned here, in your example you do need to remove the trailing ();
But, also, note that List<> is in the System.Collections.Generic namespace.
So, you need to do one of the following two options:
[#1 below is probably the more preferred option]
(1)
Include the use of the namespace at the top of your code with:
using System.Collections.Generic;
or
(2)
Put the fully qualified path to List in your declaration.
System.Collections.Generic.List optList=new System.Collections.Generic.List
{ "AdditionalCardPersonAddressType","AutomaticRaiseCreditLimit","CardDeliveryTimeWeekDay"
};
Hope that helps.
The error message you receive when you implement List correctly but don't include the System.Collections.Generic namespace is misleading and not helpful:
"Compiler Error CS0308: The non-generic type List cannot be used with type arguments."
PS - It gives this unhelpful error because if you don't specify that you intend to use System.Collections.Generic.List the compiler assumes you are trying to use System.Windows.Documents.List.
I have seen the content tag C#, but if someone could use Java (the same search terms lead here):
List<String> mylist = Arrays.asList(new String[] {"element1", "element2", "element3" }.clone());
This is how you would do it.
List <string> list1 = new List <string>();
Do Not Forget to add
using System.Collections.Generic;