Why is the delimiter excluded in this String.Join? C# - c#

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.

Related

How to use dynamic Linq with List<dynamic> object

I have a List of dynamic objects that I am trying to use dynamic Linq on. I am using dynamic objects because I do not know the properties that will be coming into the object. Linq works on my dynamic object, but, to avoid giant hard coding if statements, I would like to use dynamic Linq to search my list. The top half of the code snippet works but I need it to work dynamically so I can create a query string from my properties and filter that way.
public List<dynamic> GetFilteredLocationData(List<dynamic> locationData, string searchTerm){
//Does work
List<dynamic> totalResults = locationData.Where(x => x.Street.ToLower().Contains(searchTerm.ToLower()) ||
x.Street.ToLower().Contains(searchTerm.ToLower()) ||
x.Zip.ToLower().Contains(searchTerm.ToLower()));
//Does not work
var testQueryString = "(Street == \"king\")";
var testResult = locationData.Where(testQueryString);
return totalResults;
}
The runtime error I receive: No property or field 'Street' exists in type 'Object'
That error makes sense as object by default doesn't contain 'Street' but I'd expect the dynamic Linq to behave like the code above it. Is there something I am doing wrong here, or should I take a different approach? I can provide more detail if needed.
Thanks in advance!
Finally I got a working solution! It may not be the most efficient but it works for my needs and allows me to keep the dynamic nature I was hoping to retain. The solution was to drop Linq entirely and use a good old for-each loop. The Important part was the IDictionary which allowed me to search each row for the key value pair. This is the same functionality I was going for, just ditched linq.
public List<dynamic> GetFilteredLocationData(List<dynamic> locationData, string searchTerm){
List<dynamic> totalResults = new List<dynamic>();
List<string> locationProperties = new List<string> {"dynamic properties here, this was filled by call to DB for info pertaining to certain location combined with unique data"}
foreach (var locData in locationData)
{
var currentLoc = locData;
var currentLocDict = (IDictionary<string, object>)currentLoc;
bool containsSearchTerm = CheckIfLocationContainsSearch(currentLocDict, allLocationProperties, searchTerm);
if (containsSearchTerm)
{
totalResults.Add(locData);
}
}
}
public bool CheckIfLocationContainsSearch(IDictionary<string,object> location, List<string> locationProperties, string searchTerm){
foreach (var locProp in locationProperties)
{
if (location[locProp].ToString().ToLower().Contains(searchTerm))
{
return true;
}
}
return false;
}

C# list<string> contains specific string

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.

Why does this LINQ query new up only one instance of the internal List?

Upon request, I have simplified this question. When trying to take two generic List and blend them, I get unexpected results.
private List<ConditionGroup> GetConditionGroupParents()
{
return (from Conditions in dataContext.Conditions
orderby Conditions.Name
select new ConditionGroup
{
GroupID = Conditions.ID,
GroupName = Conditions.Name,
/* PROBLEM */ MemberConditions = new List<Condition>()
}).ToList();
}
private List<ConditionGroup> BuildConditionGroups()
{
var results = GetConditionGroupParents();
// contents of ConditionMaps is irrelevant to this matter
List<ConditionMap> ConditionMaps = GenerateGroupMappings();
// now pair entries from the map into their appropriate group,
// adding them to the proper List<MemberConditions> as appropriate
foreach (var map in ConditionMaps)
{
results.Find(groupId => groupId.GroupID == map.GroupID)
.MemberConditions.Add(new ConditionOrphan(map));
}
return results;
}
I would expect each map in ConditionMaps to be mapped to a single ConditionGroup's MemberConditions in the "results.Find...." statement.
Instead, each map is being added to the list of every group, and that happens simultaneously/concurrently.
[edit] I've since proven that there is only a single instance of
List<Memberconditions>, being referenced by each group.
I unrolled the creation of the groups like so:
.
.
.
/* PROBLEM */ MemberConditions = null }).ToList();
foreach (var result in results)
{
List<Condition> memberConditions = new List<Condition>();
results.MemberConditions = memberConditions;
}
return results;
In that case I was able to watch each instantiation stepping
through the loop, and then it worked as expected. My question
remains, though, why the original code only created a single
instance. Thanks!
.
Why doesn't the LINQ query in GetConditionGroupParents "new up" a unique MemberConditions list for each Group, as indicated in the /* PROBLEM */ comment above?
Any insight is appreciated. Thanks!
Jeff Woods of
Reading, PA
This is a bug. As a workaround you can create a factory function
static List<T> CreateList<T>(int dummy) { ... }
And pass it any dummy value depending on the current row such as Conditions.ID.
This trick works because L2S, unlike EF, is capable of calling non-translatable functions in the last Select of the query. You will not have fun migrating to EF since they have not implemented this (yet).

CSV file to class via Linq

With the code below, on the foreach, I get an exception.
I place breakpoint on the csv (second line), I expand the result, I see 2 entries thats ok.
When I do the same on the csv in the foreach, I get an excpetion : can't read from closed text reader.
Any idea ?
Thanks,
My CSV file :
A0;A1;A2;A3;A4
B0;B1;B2;B3;B4
The code
var lines = File.ReadLines("filecsv").Select(a => a.Split(';'));
IEnumerable<IEnumerable<MyClass>> csv =
from line in lines
select (from piece in line
select new MyClass
{
Field0 = piece[0].ToString(),
Field1 = piece[1].ToString()
}
).AsEnumerable<MyClass>();
foreach (MyClass myClass in csv)
Console.WriteLine(myClass.Field0);
Console.ReadLine();
MyClass :
public class MyClass
{
public string Field0 { get; set; }
public string Field1 { get; set; }
}
Perhaps something like this instead, will give you exactly what you want:
var jobs = File.ReadLines("filecsv")
.Select(line => line.Split(','))
.Select(tokens => new MyClass { Field0 = tokens[0], Field1 = tokens[1] })
.ToList();
The problem you have is that you're saving the Enumerable, which has delayed execution. You're then looking at it through the debugger, which loops through the file, does all the work and disposes of it. Then you try and do it again.
The above code achieves what you currently want, is somewhat cleaner, and forces conversion to a list so the lazy behaviour is gone.
Note also that I can't see how your from piece in line could work correctly as it currently stands.
Perhabs it is because LINQ does not directly read all the items, it just creates the connection it read if it is needed.
You could try to cast:
var lines = File.ReadLines("filecsv").Select(a => a.Split(';')).ToArray();
I suspect it is a combination of the yield keyword (used in Select()) and the internal text reader (in ReadLines) not "agreeing".
Changes the lines variable to var lines = File.ReadLines("filecsv").Select(a => a.Split(';')).ToArray();
That should sort it.

How to compare 2 lists values?

I have 2 lists and I would like to remove the items when the items from the first list is not present in the second list.
public class ResolutionsRow
{
public String Name { get; set; }
public int ID { get; set; }
}
public List<ResolutionsRow> Categories { get; set; }
In the following Category.LoadForProject(project.ID) returns an IList
DeleteItems(Category.LoadForProject(project.ID), Categories);
private void DeleteItems(dynamic currentItems, dynamic items)
{
if (currentItems != null)
{
foreach (var existingItem in currentItems)
{
if (items.Contains(existingItem.Name))
items.Remove(existingItem.Name);
}
}
}
I am having the error message
The best overloaded method match for 'System.Collections.Generic.List.Contains(MvcUI.Models.ResolutionsRow)' has some invalid arguments. What is wrong with my code and how can I correct it? Help please.
I have tried to change the code to, but I am having the error message
Error 6 Argument 1: cannot convert from 'int' to API.Category' MvcUI\Models\ProjectModel.cs 255 44 MvcUI
Error 5 The best overloaded method match for 'System.Collections.Generic.ICollection.Contains(API.Category)' has some invalid arguments MvcUI\Models\ProjectModel.cs 255 24 MvcUI
var categories = Category.LoadForProject(project.ID);
foreach (var item in Categories)
{
if(categories.Contains(item.ID))
{
}
}
Here's the easy LINQ answer:
var currentItems = new int[] { 1, 2, 5, 6 };
var items = new int[] { 2, 3, 4, 5 };
var resultItems = items.Except(currentItems); // resultItems == new int[] { 3, 4 }
What is items? I'm guess it is the list of ResolutionsRow - so you will need to search for an item with that name/id, not the name/id itself.
If they are the same object instances, then just Remove(existingItem) will work, but otherwise (if they are different object instances that happen to have the same .Name):
items.RemoveAll(item => item.Name == existingItem.Name);
by the way; do you really need dynamic here? Without it, the compiler would tell you the problem. It isn't helping you any, and could well cause a lot of problems (explicit interface implementations, lambdas, etc - there are constructs that aren't fans of dynamic)
Change
items.Contains(existingItem.Name);
and
items.Remove(existingItem.Name);
to
items.Contains(existingItem);
items.Remove(existingItem);
you want to use items1.Except(items2)
Your items.Contains method signature is expecting a type different than what you provided. Looks like you are providing a string instead of a ResolutionsRow.
How often are you doing this, and with how many items in each list? What you are doing is commonly considered a "Set operation"(union, intersection, minus, etc). If the answer to either of the previous questions is "a lot", then you want to consider using a SortedSet or HashSet.
Your current implementation is O(m*n) (where m and n are the sizes of the two lists). If you use a hash set it is O(n), since only the second set is actually iterated over. There is also a cost to build the sets (O(m+n)), but if you have enough objects or can use it for more than just one operation it can be worth it.

Categories

Resources