When performing a set of LINQ queries against a data-source (I'm using LINQ-to-SQL, but it happens here too using just a List<string> object), I end up getting a different result at the end of my checks.
Specifically, the code below is trying to find if a Fully Qualified Domain Name (FQDN) either exists in a list of host names (not all of which will be FQDNs or in the same domain, but the host identifier is what matters to me). The search is trying to find whether "host-6.domain.local" or any of its sub-components exist (i.e, "host-6.domain" or "host-6") in the list, which they do not. While inside the for-loop, we get the results we expect, but as soon as the for loop is finished, I get a result that has all of the contents of the list, which to me sounds like it is trying to find elements that match the empty string.
void MyMethod()
{
string fqdn = "host-6.domain.local";
string[] splitFqdn = fqdn.Split('.');
List<string> values = new List<string>();
values.add("host-1");
values.add("host-2.domain.local");
values.add("host-3.domain.local");
values.add("host-4");
values.add("host-5.other.local");
IEnumerable<string> queryResult = null;
for (int i = splitFqdn.Length; i > 0; i--)
{
result =
from value in values
where value.StartsWith(
string.Join(".", splitFqdn.Take(i)))
select value;
Console.WriteLine(
"Inside for loop, take " + i + ": " + result.Count());
}
Console.WriteLine();
Console.WriteLine(
"Outside for loop: " + result.Count());
}
Why is this happening and how can I get accurate results that I can still access after the for loop is finished?
You are getting bitten by LINQ's lazy execution and closure.
When you create an enumerable like you are doing here...
result =
from value in values
where value.StartsWith(
string.Join(".", splitFqdn.Take(i)))
select value;
It doesn't get evaluated until you do something that forces it to get evaluated... for instance when you do result.count()
Then later outside of your loop when you evaluate it again result.count() is evaluated with the last value of i that existed in your for loop which is not giving you what you want.
Try forcing evaluation by doing .ToList() on your enumerable like so... This code shows both values so you can compare.
void MyMethod()
{
string fqdn = "host-6.domain.local";
string[] splitFqdn = fqdn.Split('.');
List<string> values = new List<string>();
values.add("host-1");
values.add("host-2.domain.local");
values.add("host-3.domain.local");
values.add("host-4");
values.add("host-5.other.local");
IEnumerable<string> queryResult = null;
List<string> correctResult = null;
for (int i = splitFqdn.Length; i > 0; i--)
{
queryResult =
from value in values
where value.StartsWith(
string.Join(".", splitFqdn.Take(i)))
select value;
correctResult = queryResult.ToList();
Console.WriteLine(
"Inside for loop, take " + i + ": " + queryResult.Count());
}
Console.WriteLine();
Console.WriteLine(
"Outside for loop queryResult: " + queryResult.Count());
Console.WriteLine(
"Outside for loop correctResult: " + correctResult.Count());
}
EDIT: Thanks nlips for pointing out that I hadn't fully answered the question... and apologies for converting to method syntax but it would have taken longer to convert to query syntax.
void MyMethod()
{
string fqdn = "host-6.domain.local";
string[] splitFqdn = fqdn.Split('.');
List<string> values = new List<string>();
values.Add("host-1");
values.Add("host-2.domain.local");
values.Add("host-3.domain.local");
values.Add("host-4");
values.Add("host-5.other.local");
values.Add("host-5.other.local");
IEnumerable<string> queryResult = null;
List<string> correctResult = new List<string>();
for (int i = splitFqdn.Length; i > 0; i--)
{
correctResult = correctResult
.Union(values.Where(
value => value.StartsWith(string.Join(".", splitFqdn.Take(i)))))
.ToList();
}
}
I really like Kevin's answer to my question, but I wasn't a huge fan of calling .ToList() on the result since this would cause all of the objects that matched to be pulled from the database (eating up more memory) rather than executing a query that simply got the count of matching objects (which is a little faster and doesn't take the memory to store the objects), so using the information from his post, I have this additional solution that doesn't require pulling all objects from a database, and only runs a COUNT query (in the SQL sense).
To avoid the issue caused by capturing i which then becomes 0 at the end of the for-loop, I simply set up a temporary variable to hold the value I'm searching for.
void MyMethod()
{
string fqdn = "host-6.domain.local";
string[] splitFqdn = fqdn.Split('.');
List<string> values = new List<string>();
values.add("host-1");
values.add("host-2.domain.local");
values.add("host-3.domain.local");
values.add("host-4");
values.add("host-5.other.local");
IEnumerable<string> queryResult = null;
for (int i = splitFqdn.Length; i > 0; i--)
{
//taking the line referencing i out of the
//query expression prevents referencing i
//after it is set to 0 outside the for loop
string temp = string.Join(".", splitFqdn.Take(i));
//since temp isn't changed anywhere else, it won't
//get set to an invalid value after the loop exits
result =
from value in values
where value.StartsWith(temp)
select value;
Console.WriteLine(
"Inside for loop, take " + i + ": " + result.Count());
}
Console.WriteLine();
Console.WriteLine(
"Outside for loop: " + result.Count());
}
I think you need to call ToList when assigning to the result variable like this:
result =
(from value in values
where value.StartsWith(
string.Join(".", splitFqdn.Take(i)))
select value).ToList();
Related
This probably won't be my clearest question but here we go. I have a class that instantiates an object. This object runs through a series of methods and calls up some values from a web services dll. Here is my issue. The two strings that get pulled into the object array have to be looped through via a foreach in order to pass them through another method.
I am at a loss as to how to capture the results of the foreach loop and pass them outside the loop so I can process them.
Essentially, I am trying to loop through an array of objects and single out each object in the foreach loop and then I want to pass these two objects out so that I can pass them into methods.
I am able to type each object into a string, concatenate the string, and pass the string outside to the loop. I then can us the split method to split up the strings but they are now strings and no-longer objects so my methods no longer work. I am at my wits end here and any advice would be awesome...
Here is the code excerpt in question
JciFqr objRootFqr = objClient.CreateFqr("SIPE-NAE-001", "Programming.CommonPath", JciFqr.Classifications.Folder, JciFqr.Types.Folder);
JciFqr[] aobjRoomFqrs = await objClient.GetObjectsAsync(objRootFqr, CancellationToken.None);
foreach(JciFqr objFqr in aobjRoomFqrs)
{
System.Console.WriteLine(objFqr);
JciFqr objCodeFqr = JciFqr.Create(objFqr, "Code_Blue", JciFqr.Classifications.Object, JciFqr.Types.BinaryValue);
await objClient.WritePropertyAsync(objCodeFqr, "Present Value", a_bOn ? "on" : "off", CancellationToken.None);
System.Console.WriteLine(objCodeFqr);
//await objClient.ReadPropertyAsync(objCodeBlueFqr, "Present Value", a_bOn ? "on" : "off", CancellationToken.None);
var obj = objCodeFqr ;
fqrs = obj + "|" + fqrs;
count++;
}
Console.WriteLine(count);
Console.WriteLine(fqrs);
string[] fqrArray = fqrs.Split('|');
for (int i = 0; i < fqrArray.Length; i++)
{
Console.WriteLine(fqrArray[i]);
}
string strStatus = "";
string uri1 = fqrArray[0];
string uri2 = fqrArray[1];
bool triggerCodeBlue = false;
Console.WriteLine("Trigger the Code");
triggerCodeBlue = bool.Parse(Console.ReadLine());
Before your loop, make a List to store your objects:
List<JciFqr> fqrs = new List<JciFqr>();
Inside your loop, store the object in the list:
fqrs.Add(objCodeFqr);
After the loop, you can access fqrs[0] and fqrs[1], or loop through it with another foreach.
I didn't really understand the code, but from the description in the question it seems it could also be a case to use the keyword "yield"
the yield function allows you to return values from a loop, one by one and turn those results into an enumerable, for example:
static IEnumerable<string> Combinations(string input){
for(int i=0;i<input.length-1; i++){
for(int j=0;j<input.length; j++){
yield return input[i].ToString() + input[j].ToString();
}
}
}
static void Main(string[] args)
{
foreach (string output in Combinations("abc")){
Console.WriteLine(output); //outputs "ab", "ac", "bc"
}
}
yielding results is algorithmically speaking, the same thing as adding them to a list and returning the list, but can use significantly less memory when you yield a lot of results.
There is also yield return inside your foreach loop. I'm guessing on the types since they are ohh so clear.
IEnumerable<Tuple<JciFqr, JciFqr>> GetStuff() {
foreach(JciFqr objFqr in aobjRoomFqrs){
JciFqr objCodeFqr = JciFqr.Create(objFqr, "Code_Blue", JciFqr.Classifications.Object, JciFqr.Types.BinaryValue);
await objClient.WritePropertyAsync(objCodeFqr, "Present Value", a_bOn ? "on" : "off", CancellationToken.None);
yield return Tuple.Create(objFgr, objCodeFgr);
}
}
You can enumerate over the collection returned from the fucntion like this:
foreach(var pair in GetStuff()){
//do other stuff here
Console.WriteLine(string.format("Item1: {0}, Item2: {1}", pair.Item1, pair.Item2));
}
I have a list of strings and I need to select certain parts of the list to construct a separate string. What I have is:
name,gender,haircolour,car;
or
John,Male,Brown,toyota;
I also have a separate file indicating which parts, and in what order the new string should be constructed.
eg: Index = 0,3,1 would print John,toyota,Male or 1,2,0 would print Male,Brown,John.
I have tried several methods to try and select the index of the items I want, but all the functions that return values only return the contents of the List, and the only return that gives an integer is Count(), which I can't see as being helpful.
I have tried and tried but all I have succeeded in doing is confusing myself more and more. Can anyone help suggest a way to achieve this?
You should be able to do list[i], where i is the index of the element you need. There are some examples here: http://www.dotnetperls.com/list
List<string> list = new List<string> { "John", "Male", "Brown", "toyota" };
List<int> indexList = new List<int> { 0, 3, 1 };
List<string> resultList = new List<string>();
indexList.ForEach(i => resultList.Add(list[i]));
If I am understanding the question correctly, something like this should do the job:
const string data = "John,Male,Brown,toyota";
const string order = "0,3,1";
string[] indexes = order.Split(',');
string result = indexes.Aggregate("", (current, index) => current + data.Split(',')[int.Parse(index)] + ",");
result = result.TrimEnd(',');
If your string data ends with a semicolon ';', as possibly indicated in your question, then change the line to this:
string result = indexes.Aggregate("", (current, index) => current + data.Split(',')[int.Parse(index)].TrimEnd(';') + ",");
Note this solution doesn't check to make sure that the given index exists in the given data string. To add a check to make sure the index doesn't go over the array bounds, do something like this instead:
string result = indexes.Where(z => int.Parse(z) < data.Split(',').Length).Aggregate("", (current, index) => current + data.Split(',')[int.Parse(index)].TrimEnd(';') + ",");
Okay so I am stumped and have looked around for this and I know I am doing the implementation of something very simple more complex than it needs to be. Basically I have a POCO object that will have a member that contains a string of other members. This is labeled as 'st' and it may have strings that are comma seperated series in one string. Thus I may have two members of strings be 'images, reports' and another 'cms, crm'. I have a list of objects that I want to match for PART OF those strings but not necessarily all as a DISTINCT LIST. So a member of 'cms' would return the value of anything that contained 'cms' thus 'cms, crm' would be returned.
I want to hook this up so a generic List can be queried but I cannot get it to work and was looking at other threads but there methods do not work in my case. I keep thinking it is something simple but I am missing it completely. Please let me know if anyone has better ideas. I was looking here but could not get the logic to apply correctly:
Linq query list contains a list
I keep trying methods of 'Select', 'SelectMany', 'Contains', 'Any', 'All' at different levels of scope of the continuations to no avail. Here is a simple excerpt of where I am at with a simple Console app example:
public class Program
{
public class StringModel
{
public string name { get; set; }
public string str { get; set; }
}
static void Main(string[] args)
{
string s = "";
List<StringModel> sm = new List<StringModel>
{
new StringModel
{
name = "Set1",
str = "images, reports"
},
new StringModel
{
name = "Set2",
str = "cms, crm"
},
new StringModel
{
name = "Set3",
str = "holiday, pto, cms"
}
};
sm.ForEach(x => s += x.name + "\t" + x.str + "\n");
var selected = new List<object> {"cms", "crm"};
s += "\n\nITEMS TO SELECT: \n\n";
selected.ForEach(x => s += x + "\n");
s += "\n\nSELECTED ITEMS: \n\n";
// works on a single item just fine
var result = sm.Where(p => p.str.Contains("cms")).Select(x => new { x.name, x.str}).ToList();
// I am not using select to get POCO on other methods till I can get base logic to work.
// Does not return anything
var result2 = sm.Where(p => selected.Any(x => x == p.str)).ToList();
// Does not return anything
var result3 = sm.Where(p => selected.Any(x => selected.Contains(p.str))).ToList();
result.ForEach(y => s += y + "\n");
s += "\n\n2nd SET SELECTED: \n\n";
result2.ForEach(y => s += y + "\n");
s += "\n\n3rd SET SELECTED: \n\n";
result3.ForEach(y => s += y + "\n");
Console.WriteLine(s);
Console.ReadLine();
}
}
result2 is empty because you're comparing an object (x) with a string (StringModel.str). This will be a reference comparison. Even if you convert x to a string, you'll be comparing each value in selected ("cms", "crm") with your comma-separated string values ("images, reports", "cms, crm", "holiday, pto, cms").
result3 is empty because selected ("cms", "crm") does not contain any of the string values ("images, reports", "cms, crm", "holiday, pto, cms"), although in this case at least the comparisons are value comparisons.
I think you're looking for something like:
var result = sm.Where(p => selected.Any(x => p.str.Contains((string)x)));
I looked at this article to see if I could use that as a starting point, but I don't see it addressing my particular problem. C#: N For Loops
I have the following code:
int criteriaCount = rule.SearchCriteria.Criteria.Count();
string criteria = "";
for (int i = 0; i < criteriaCount; i++)
{
if (rule.SearchCriteria.Criteria[i].Criteria.Count() > 0)
{
criteria += string.Format("({0}" + System.Environment.NewLine, rule.SearchCriteria.Criteria[i].Display);
criteria += string.Format("{0})" + System.Environment.NewLine, rule.SearchCriteria.Criteria[i].Criteria[i].Display);
}
else
{
criteria += string.Format("[{0}]" + System.Environment.NewLine, rule.SearchCriteria.Criteria[i].Display);
}
}
To explain a little, you have in SearchCriteria and Array of Criteria[]. I can loop through this and grab the follow I need from each Criteria object as you can see I am doing.
I am also looking at the second level deep, so SearchCriteria.Criteria[n].Criteria[n] and I can put a for loop their and grab any nested values there as well.
The part I cannot figure out is how can I account for a variable number of nested Criteria objects? I could potentially have this:
SearchCriteria.Criteria[n].Criteria[n] ... (repeat a hundred times)... .Criteria[n]
So it can have potentially an infinite number of nested objects, and an infinite number of sibling objects (Criteria[0], Criteria[1] ... Criteria[100000] is what I mean).
Is there a way I can loop through all this? I have heard the a recursive loop may be the answer, and I vaguely understand that concept, but what I don't know is how can I get the number of children and siblings?
The basic concept for a recursive loop is defined below. You will be limited to the space available on the stack.
private String GetSearchCriteria(Citeria root) {
String result = root.Display;
foreach (Criteria child in root.Criteria) {
result += GetSearchCriteria(child);
}
return result;
}
Use recursion, and in your case you may use something like this...
private static string DisplayCriteria(Criteria criteriaObject)
{
string criteria = "";
foreach(Criteria c in criteriaObject)
{
if (c.Criteria.Count() > 0)
{
criteria += string.Format("({0}" + System.Environment.NewLine, c.Display);
criteria += string.Format("{0})" + System.Environment.NewLine, DisplayCriteria(c.Criteria));
}
else
{
criteria += string.Format("[{0}]" + System.Environment.NewLine, c.Display);
}
}
return criteria;
}
// your code ...
DisplayCriteria(rule.SearchCriteria.Criteria);
// your code ...
I am not sure about the new lines there, you can add/remove them later
You need to make your method into a "Recursive Method"...a method that calls itself to handle unlimited nesting, such as this:
public string ParseCriteria( Criteria criteria ) {
string result = "(";
result += criteria.Display;
foreach( var criteria in criteria.Criteria) {
result += ParseCriteria( criteria )
}
return result;
}
then, something like this in your existing code.
foreach( var criteria in rule.SearchCriteria.Criteria ) {
string result += ParseCriteria(criteria);
}
string concatentation is not ideal, and my exmaple doesn't format correctly...but hopefully you get the idea.
I have an ArrayList that import records from a database.
Is there any method to check whether the arrayList contains schname that i want to match to another list which is an api?
List<PrimaryClass> primaryList = new List<PrimaryClass>(e.Result);
PrimaryClass sc = new PrimaryClass();
foreach (string item in str)
{
for (int a = 0; a <= e.Result.Count - 1; a++)
{
string schname = e.Result.ElementAt(a).PrimarySchool;
string tophonour = e.Result.ElementAt(a).TopHonour;
string cca = e.Result.ElementAt(a).Cca;
string topstudent = e.Result.ElementAt(a).TopStudent;
string topaggregate = e.Result.ElementAt(a).TopAggregate;
string topimage = e.Result.ElementAt(a).TopImage;
if (item.Contains(schname))
{
}
}
}
This is what I have come up with so far, kindly correct any errors that I might have committed. Thanks.
How about ArrayList.Contains?
Try this
foreach( string row in arrayList){
if(row.contains(searchString)){
//put your code here.
}
}
Okay, now you've shown that it's actually a List<T>, it should be easy with LINQ:
if (primaryList.Any(x => item.Contains(x.PrimarySchool))
Note that you should really consider using foreach instead of a for loop to iterate over a list, unless you definitely need the index... and if you're dealing with a list, using the indexer is simpler than calling ElementAt.
// check all types
var containsAnyMatch = arrayList.Cast<object>().Any(arg => arg.ToString() == searchText);
// check strings only
var containsStringMatch = arrayList.OfType<string>().Any(arg => arg == searchText);