I have a master list that has the values for tables of a database I know to be correct:
masterList: List<string>(){ "business", "customer", "location", "employee", etc}
And I've queried a new database that is supposed to be identical. My test will tell me if I have any errors in the scripts my team has made to make this new DB. tablesList is supposed to the be the return of my query:
tablesList: List<string>(){ "business", "customer", "location", "employee", etc}
So in practice they are supposed to be the same, but to test errors, I want to compare the tablesList against the masterList to make sure all needed tables are there. As a copy of this process, I'm also reversing the search, in case there are any extra tables that are not there on the masterList.
Question: How do I compare a list against a master list, and return items that don't match up?
I am using Visual Studio 2017 with c# .net Core 2.0.
Here is what I've been trying so far:
var errorsList = new List<string>();
tablesList = QuerySchemaForTables();
masterList = GrabMasterTableList();
foreach(var item in masterList)
errorsList.Add(tablesList.Where(x => x.Contains(item)));
But with this, I'm getting the error:
cannot convert from IEnumerable to string
You can get the two directions of errors using LINQ. No need for the loop:
var missingInMasterList = tableList.Where(x => !masterList.Contains(x)).ToList();
var missingInTableList = masterList.Where(x => !tableList.Contains(x)).ToList();
Are you looking for something like that;
var errorList = tableList.Where(x => !masterList.Contains(x));
You can capture the differences using .Except(), which is one of the IEnumerable set operations:
var missingTables = masterList.Except(tablesList);
var extraTables = tablesList.Except(masterList);
Then, to create your error message, you can join the items in these IEnumerables with a comma into a single string using string.Join():
var errorMessage = new StringBuilder();
if (missingTables.Any())
{
errorMessage.AppendLine("Missing Tables: " + string.Join(", ", missingTables));
}
if (extraTables.Any())
{
errorMessage.AppendLine("Extra Tables: " + string.Join(", ", extraTables));
}
Then you can output your results by checking the length of errorMessage to determine if any errors were encountered:
if (errorMessage.Length > 0)
{
Console.WriteLine(errorMessage.ToString());
}
else
{
Console.WriteLine("No extra or missing tables detected");
}
I think better to use is Except() as follows
var MasterList = new List<string> { "business", "customer", "location", "employee"};
var ChildList = new List<String> { "customer", "location", "employee" };
var filter = MasterList.Except(ChildList);
This will values those are not in ChildList.You can also do vice versa.
To find all items that are in the tablesList but not in the masterList use .Contains:
var errorsList = tableList.Where(x => !masterList.Contains(x));
But I recommend you use a HashSet<String> for masterList so search for an item in it will be in O(1) instead of O(n):
var masterCollection = new HashSet<String>(GrabMasterTableList());
var errorsList = tableList.Where(x => !masterCollection.Contains(x));
As for the problem with your code as you posted:
foreach(var item in masterList)
errorsList.Add(tablesList.Where(x => x.Contains(item))); // <-- error
As the error points out, Linq's .Where returns an IEnumerable<T> whereas .Add expects a single item of the type of the collection, which in this case is a single string. You could use .AddRange instead but I think a better use all together is what I wrote above.
Your code is presently trying to add an IEnumerable to a List.
If you want to add all the matches you should AddRange instead.
https://msdn.microsoft.com/en-us/library/z883w3dc(v=vs.110).aspx
var errorsList = new List<string>();
tablesList = QuerySchemaForTables();
masterList = GrabMasterTableList();
foreach(var item in masterList)
errorsList.AddRange(tablesList.Where(x => x.Contains(item)));
Related
Please help me to fix this issue. My dropdown list looks something like this mentioned below.
Client
Contractor,Contractor,Contractor,Manager
Contractor,Manager
Manager
Operator
Viewer
I want to remove the duplicates and my output should be like :
Client
Contractor
Manager
Operator
Viewer
This is my code mentioned below:
Property:
public List<string> TeamRoleNames => TeamRoleUids.Select(MainRoles.GetRoleName).ToList();
Display Method:
{
result += " ; TeamRoleNames=" + this.TeamRoleNames;
}
GetRole Method:
{
string roleName;
if (RoleNameByUid.TryGetValue(roleUid, out roleName))
{
return roleName;
}
return null;
}
I have tried with Distinct Method mentioned below, But did not work like the output what I wanted!
public List<string> TeamRoleNames => TeamRoleUids.Select(MainRoles.GetRoleName).Distinct().ToList();
How can I fix this? Can anyone help?
Having elements comma separated require you to split them first to have an homogenous collection then do the distinct
// get the comma separated values out as 1 value each
// for that you can use split, remove empty and select many
// which will return as a single level list (flat)
var result = TeamRoleUids.SelectMany(o => o.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).Distinct().ToList();
Consider converting the list to a set (hashset) since sets as a data structure doesn't allow duplicates.
More about hashsets form official documentation.
So, the solution would be similar to the following:
var hashSet = new HashSet<YourType>(yourList);
example:
var hashSet = new HashSet<string>(TeamRoleUids);
then converting it back toList() will remove duplicates.
If you have already tried Distinct and it hasn't worked, then you could do the following;
Split your string list to a List<string>
List<string> result = TeamRoleNames.Split(',').ToList();
Then when you're adding them to the dropdwon, check to see if the role is already in the dropdown. If so, move on, else add to the dropdown.
So something like
foreach(var role in this.TeamRoleNames)
{
if(!result.contains(role))
result += " ; TeamRoleNames=" + role;
}
You can use SelectMany to flatten a enumeration containing a nested enumeration. Here, we create the nested enumeration by splitting the string at the commas:
string[] input = {
"Client",
"Contractor,Contractor,Contractor,Manager",
"Contractor,Manager",
"Manager",
"Operator",
"Viewer"
};
var roles = input
.SelectMany(r => r.Split(','))
.Distinct()
.OrderBy(r => r)
.ToList();
foreach (string role in roles) {
Console.WriteLine(role);
}
prints
Client
Contractor
Manager
Operator
Viewer
How to deserialize/serialize a property with JSON string array value and then filter (using where clause) in LINQ inside a lambda expression?
void Main()
{
var regionList = new List<Row>() {
new Row { RegionJsonList = "[\"QLD\",\"NSW\"]" },
new Row { RegionJsonList = "[\"TAZ\",\"SA\"]" },
new Row { RegionJsonList = "[\"QLD\",\"VIC\"]" }
};
var filterRegionList = new List<string>() {
"QLD", "NSW"
};
var queryable = regionList.AsQueryable();
// this is obviously wrong, i just want to find the Row that contains one on filterRegionList
var result = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Contains(filterRegionList));
result.Count().Dump(); // should be 2
}
class Row
{
public string RegionJsonList { get;set; }
}
Following would work:
var result =
filterRegionList.Aggregate(regionList,(current,filter) =>
current.Where( r => r.RegionJsonList.Contains(filter)).ToList())
Aggregating the filterRegionList and regionList and thus applying filters for the final result. I did not find a requirement to Deserialize the RegionJsonList, since this would work as is, but you may add that part in case you are keen.
Also we are applying And filter via aggregation, it checks for the rows which contains both the filters, and thus provide the result, you may modify filter to achieve more number of rows, like following will select two entries from original regionList
var filterRegionList = new List<string>() { "QLD" };
To filter for rows that contain at least one of the entries from filterRegionList, you can use Enumerable.Intersect and check for non-empty intersections:
var resultAny = queryable.Where(r => JsonConvert.DeserializeObject<string[]>(r.RegionJsonList).Intersect(filterRegionList).Any());
To filter for rows that contain all of the entries from filterRegionList, you can use Enumerable.Except to remove the row's entries from the filter list. If everything gets removed, it's a match:
var resultAll = queryable.Where(r => !filterRegionList.Except(JsonConvert.DeserializeObject<string[]>(r.RegionJsonList)).Any());
(It wasn't entirely clear from your question which you wanted.)
I have a list function on a console application on C#. This list function has different items where they look something like 'matt,5' 'matt,7' 'jack,4' 'jack,8' etc...
I want to be able to combine all of the names where I only see their name written once but the number after them are averaged out so it would be like 'jack,5+7/2' which would then display as 'jack,6'.
So far I have this...
currentFileReader = new StreamReader(file);
List<string> AverageList = new List<string>();
while (!currentFileReader.EndOfStream)
{
string text = currentFileReader.ReadLine();
AverageList.Add(text.ToString());
}
AverageList.GroupBy(n => n).Any(c => c.Count() > 1);
Not really sure where to go from here.
What you need is to Split your each string item on , and then group by first element of the returned array and average second element of the array (after parsing it to int) something like:
List<string> AverageList = new List<string> { "matt,5", "matt,7", "jack,4", "jack,8" };
var query = AverageList.Select(s => s.Split(','))
.GroupBy(sp => sp[0])
.Select(grp =>
new
{
Name = grp.Key,
Avg = grp.Average(t=> int.Parse(t[1])),
});
foreach (var item in query)
{
Console.WriteLine("Name: {0}, Avg: {1}", item.Name, item.Avg);
}
and it will give you:
Name: matt, Avg: 6
Name: jack, Avg: 6
But, a better option would be to use a class with Name and Score properties instead of comma separated string values.
(The code above doesn't check for invalid input values).
Firstly you will want to populate your unformatted data into a List, as you can see I called it rawScores. You could then Split each line by the comma delimiting them. You can then check to see if an existing person is in your Dictionary and add his score to it, or if not create a new person.
After that you would simply have to generate the Average of the List.
Hope this helps!
var scores = new Dictionary<string, List<int>>();
var rawScores = new List<string>();
rawScores.ForEach(raw =>
{
var split = raw.Split(',');
if (scores.Keys.All(a => a != split[0]))
{
scores.Add(split[0], new List<int> {Convert.ToInt32(split[1])});
}
else
{
var existing = scores.FirstOrDefault(f => f.Key == split[0]);
existing.Value.Add(Convert.ToInt32(split[1]));
}
});
I'm using HtmlAgilityPack to parse a page of HTML and retrieve a number of option elements from a select list.
The GvsaDivisions is a method that returns raw html from the result of a POST, irreverent in the context of the question
public IEnumerable<SelectListItem> Divisions(string season, string gender, string ageGroup)
{
var document = new HtmlDocument();
var html = GvsaDivisions(season);
document.LoadHtml(html);
var options = document.DocumentNode.SelectNodes("//select//option").Select(x => new SelectListItem() { Value = x.GetAttributeValue("value", ""), Text = x.NextSibling.InnerText });
var divisions = options.Where(x => x.Text.Contains(string.Format("{0} {1}", ageGroup, gender)));
if (ageGroup == "U15/U16")
{
ageGroup = "U15/16";
}
if (ageGroup == "U17/U19")
{
ageGroup = "U17/19";
}
return divisions;
}
What I'm observing is this... once the options.Where() is executed, divisions contains a single result. After the test of ageGroup == "U15/U16" and the assignment of ageGroup = "U15/16", divisions now contains 3 results (the original 1, with the addition of 2 new matching the criteria of the new value of ageGroup
Can anybody explain this anomaly? I expected to make a call to Union the result of a new Where query to the original results, but it seems it's happening automagically. While the results are what I desire, I have no way to explain how it's happening (or the certainty that it'll continue to act this way)
LINQ queries use deferred execution, which means they are run whenever you enumerate the result.
When you change a variable that is being used in your query, you actually are changing the result of the next run of the query, which is the next time you iterate the result.
Read more about this here and here:
This is actually by-design, and in many situations it is very useful, and sometimes necessary. But if you need immediate evaluation, you can call the ToList() method at the end of your query, which materializes you query and gives you a normal List<T> object.
The divisions variable contains an unprocessed enumerator that calls the code x.Text.Contains(string.Format("{0} {1}", ageGroup, gender)) on each element in the list of nodes. Since you change ageGroup before you process that enumerator, it uses that new value instead of the old value.
For example, the following code outputs a single line with the text "pear":
List<string> strings = new List<string> { "apple", "orange", "pear", "watermelon" };
string matchString = "orange";
var queryOne = strings.Where(x => x == matchString);
matchString = "pear";
foreach (var item in queryOne)
{
Console.WriteLine(" " + item);
}
I'm thinking along the same lines as Travis, the delayed execution of linq.
I'm not sure if this will avoid the issue, but I generally put my results into an immediate collection like this. With my experience it seems once you shove the results into a real defined collection I believe it may not be delayed execution.
List<SelectListItem> options = document.DocumentNode.SelectNodes("//select//option").Select(x => new SelectListItem() { Value = x.GetAttributeValue("value", ""), Text = x.NextSibling.InnerText }).Where(x => x.Text.Contains(string.Format("{0} {1}", ageGroup, gender))).ToList<SelectListItem>();
I have a function which searches some articles in the Sitecore content items and give me the value. So far I have build up my indexes and it is showing in my IndexViewer. But the return of the function is 0. I looked up this link: http://sitecoregadgets.blogspot.com/2009/11/working-with-lucene-search-index-in_25.html for more information.
protected IEnumerable<Item> ShowHomePageNews(int numOfArticles, string stringofCountries)
{
List<Item> items = new List<Item>();
Sitecore.Search.Index indx = SearchManager.GetIndex("newsArticles");
using (IndexSearchContext searchContext = indx.CreateSearchContext())
{
var db = Sitecore.Context.Database;
CombinedQuery query = new CombinedQuery();
QueryBase catQuery = new FieldQuery("countries", stringofCountries); //FieldName, FieldValue.
SearchHits results = searchContext.Search(catQuery); //Searching the content items by fields.
SearchResultCollection result = results.FetchResults(0, numOfArticles);
foreach (SearchResult i in result)
{
items = result
.Where(r => !r.Title.StartsWith("*"))
.Select(r => db.GetItem(new Sitecore.Data.ItemUri(r.Url).ToDataUri()))
.ToList();
//Lucene.Net.Documents.Field url = i.Document.GetField("_url");
//Sitecore.Data.ItemUri itemUri = new Sitecore.Data.ItemUri(url.StringValue());
//Sitecore.Data.Items.Item item = Sitecore.Context.Database.GetItem(itemUri.ToDataUri());
//items.Add(item);
}
}
return items;
}
Over here the result is 0. What I am doing wrond here?
This is the snapshot of what I am seeing in my IndexViewer:
EDIT:
I am passing a "NZ" in the 'catQuery' and I am getting the result back. Because in my index viewer I am seeing the Field Name = _name, which contains NZ in it. I got this part. However, I want my every field to be indexed. I am seeing only 3 fields in my IndexViewer: _url, _group & _name.
So your countries should be tokenized by the indexer. As a multilist, they will be tokenized by GUID. Searching for a single country by GUID with your code above should work. However, if you want to search for multiple countries, where any of the passed in countries can trigger a match, you need to structure your query differently.
CombinedQuery query = new CombinedQuery();
//apply other filters here to query if need be
//and country filter by creating a new clause (combinedquery) and "ORing" within it (QueryOccurance.Should)
CombinedQuery query3 = new CombinedQuery();
//here you would actually iterate over your country list
query3.Add(new FieldQuery("countries", country1GUID), QueryOccurance.Should);
query3.Add(new FieldQuery("countries", country2GUID), QueryOccurance.Should);
query.Add(query3, QueryOccurance.Must);