LINQ: ...Where(x => x.Contains(string that start with "foo")) - c#

Given a collection of the following class:
public class Post
{
...
public IList<string> Tags { get; set; }
}
Is there an easy way to get all Posts that contain a tag starting with "foo" using LINQ?
var posts = new List<Post>
{
new Post { Tags = new[] { "fooTag", "tag" }},
new Post { Tags = new[] { "barTag", "anyTag" }},
new Post { Tags = new[] { "someTag", "fooBarTag" }}
};
var postsWithFooTag = posts.Where(x => [some fancy LINQ query here]);
postsWithFooTag should now contain items 1 and 3 of posts.

Use string's StartsWith
var postsWithFooTag = posts.Where(x => x.Tags.Any(y => y.StartsWith("foo")));
x.Any will check if any element matches some condition. StartsWith checks if the element starts with a certain string.
The above returned:
new Post { Tags = new[] { "fooTag", "tag" }},
new Post { Tags = new[] { "someTag", "fooBarTag" }}
To make it case insensitive use StringComparison.OrdinalIgnoreCase.
var postsWithFooTag = posts.Where(x => x.Tags.Any(y => y.StartsWith("FoO", StringComparison.OrdinalIgnoreCase)));
Returns:
new Post { Tags = new[] { "fooTag", "tag" }},
new Post { Tags = new[] { "someTag", "fooBarTag" }}
while StartsWith("FoO") returns no results.

Try this:
var postsWithFooTag = posts.Where(x => x.Tags.Any(y => y.StartsWith("foo")))

I believe this will work for what you're trying to do.
posts.Where(p => p.Tags.Any(t => t.StartsWith("foo")))

var tag = "foo";
var postsWithFooTag =
posts.Where( p=> p.Tags.Any( t => t.StartsWith(tag)));

Try x => x.Tags.Any(tag => tag.StartsWith("foo"))

Related

LINQ - Condition with .Contains() is not working as expected

I cannot seem to get the desirable filtered result from my query.
Data
public class fdp_1115
{
public string Id{ get; set; }
public string Number{ get; set; }
public string Type{ get; set; }
}
List<fdp_1115> fdpList = new List<fdp_1115>
{
new fdp_1115 { Id = "1", Number = "Lot123", Type = "D14MWT" },
new fdp_1115 { Id = "2", Number = "Lot123", Type = "E12WBC7W1" }
};
List<string> searchValues = new List<string> { "MLE12WBC7W1 A R" };
LINQ:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s)));
if (LocType != null)
{
Console.WriteLine("Matching record found:");
Console.WriteLine($"Id: {LocType.Id}, Number: {LocType.Number}, Type: {LocType.Type}");
}
else
{
Console.WriteLine("No matching records found.");
}
The result I wanted is:
Matching record found:
Id: 2, Number: Lot123, Type: E12WBC7W1
But I got "No matching records found." which indicates that LocType == null.
I already tried trimming and ignoring case sensitive:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s.Trim().Replace(" ", ""))));
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => d.Type.Contains(s, StringComparison.InvariantCultureIgnoreCase)));
But still no luck. Any idea how do I match "MLE12WBC7W1 A R" with "E12WBC7W1"?
You have your contains the other way around.
d.Type = "E12WBC7W1"
and
s = "MLE12WBC7W1 A R"
Then "E12WBC7W1" does not Contains "MLE12WBC7W1 A R"
It is the other way around.
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => s.Contains(d.Type)));
Your current logic checks whether there is any object with Type value that contains the value for each string in the searchValues array.
From your requirement:
You want to filter the object that fulfills there is any string in searchValues containing the value of Type.
Thus it should be:
var LocType = fdpList.FirstOrDefault(d => searchValues.Any(s => s.Contains(d.Type)));

How can I distinct with condition?

Here is class UserArrived:
public class UserArrived{
public string id{get;set;}
}
Here is class OldUser:
public class OldUser{
public string id{get;set;}
public DateTime lastArrived{get;set;}
}
And here is class User:
public class User{
public string id{get;set;}
public Boolean newUser{get;set;}
}
Finally, here is two List:
List<UserArrived> UserArrivedList=new List<UserArrived>();
List<OldUser> OldUserList=new List<OldUser>();
All the id in each class is unique.
Now I need to combine UserArrived and OldUser to a brand new List<User>.
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.
In my opinion, I will combine two List into one first and then use the distinct method to remove the duplicates.
However, it seems the distinct can not run with a condition.
Although I can use several foreach to solve this while I feel it is so troublesome. I want to use something easy just like lambda or linq. How can I achieve this?
=============================
Here is an example of the input:
List<UserArrived> UserArrivedList=new List<UserArrived>(){new UserArrived(){id="A"},new UserArrived(){id="B"},new UserArrived(){id="C"}};
List<OldUser> OldUserList=new List<OldUser>(){new OldUser(){id="B",lastArrived=DateTime.Now}};
the output is:
A,true
B,false
C,true
If I understand your requirement you're saying that if an id is in both lists then the user is an old user, otherwise it is a new user.
So here's the simplest way that I could come up with to do it:
IEnumerable<User> users =
Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 });
Let's test with some input:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
Here are my results:
B is the only user who appears in both lists so should be False.
So, there's a bit of confusion about the requirements here.
The OP has added a concrete example of the input data and the expected output.
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
new UserArrived() { id = "C" }
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B", lastArrived = DateTime.Now }
};
With this input the OP is expecting True, False, True for A, B, C respectively.
Here is the code of the four current answers:
var results = new []
{
new
{
answered = "Enigmativity",
users = Enumerable
.Concat(
UserArrivedList.Select(i => i.id),
OldUserList.Select(i => i.id))
.ToLookup(x => x)
.Select(x => new User() { id = x.Key, newUser = x.Count() == 1 })
},
new
{
answered = "JQSOFT",
users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
})
},
new
{
answered = "Anu Viswan",
users =
UserArrivedList
.Join(OldUserList, ual => ual.id, oul => oul.id, (ual, oul) => new User { id = oul.id, newUser = false })
.Concat(UserArrivedList.Select(x => x.id).Except(OldUserList.Select(x => x.id))
.Concat(OldUserList.Select(x => x.id).Except(UserArrivedList.Select(x => x.id)))
.Select(x=> new User{ id = x, newUser = true}))
},
new
{
answered = "Barns",
users =
UserArrivedList.Select(i => i.id)
.Union(OldUserList.Select(i => i.id))
.Select(j => new User
{
id = j,
newUser =
!(UserArrivedList.Select(i => i.id).Contains(j)
&& OldUserList.Select(i => i.id).Contains(j))})
}
};
That gives the output of:
So, currently all of the answers presented match the OP's example.
I'd be interested in the OP commenting on this as the input data:
var UserArrivedList = new List<UserArrived>()
{
new UserArrived() { id = "A" },
new UserArrived() { id = "B" },
};
var OldUserList = new List<OldUser>()
{
new OldUser() { id = "B" },
new OldUser() { id = "C" },
};
When I run this I get this output:
Here three users match and one does not.
This all boils down to what the description means:
As we know, the user arrives the shop may is a new user or an old user. If the user id in UserArrived also contains in OldUser, the property newUser in the new List is false for true.
The thing about LINQ--it isn't always easy. In fact it can get quit cluttered. In the question statement I read,
I want to use something easy just like lambda or linq.
Well, that is relative. But, I think that when using LINQ, one should try to keep it simple. Even break the statement down into multiple statements if necessary. For that reason I propose this solution (demonstrated in a console app):
static void Main(string[] args)
{
Console.WriteLine();
Console.WriteLine("--------------------Test This Code -----------------------");
var combined = TestUserCombined();
//The following is just to demonstrate the list is populated properly
combined.OrderBy(s => s.id.PadLeft(4, '0')).ToList().ForEach(k => Console.WriteLine($"X id: {k.id} | isNew:{k.newUser}"));
}
private static IEnumerable<User> TestUserCombined()
{
List<UserArrived> userArrivedList=new List<UserArrived>();
List<OldUser> oldUserList=new List<OldUser>();
//populate the lists...
for(int i = 0; i < 20; i+=2)
{
var userArrived = new UserArrived();
userArrived.id = i.ToString();
userArrivedList.Add(userArrived);
}
for(int i = 0; i < 20; i+=3)
{
var oldUser = new OldUser();
oldUser.id = i.ToString();
oldUserList.Add(oldUser);
}
//Now for the solution...
var selectedUserArrived = userArrivedList.Select(i => i.id);
var selectedOldUser = oldUserList.Select(i => i.id);
var users = selectedUserArrived
.Union(selectedOldUser)
.Select(j => new User{id=j,newUser=!(selectedUserArrived.Contains(j) && selectedOldUser.Contains(j))});
return users;
}
Certainly, this all could have been done in one statement, but I believe this makes it more readable and understandable.
EDIT:
There has been some discussion amongst the coders posting solutions as to exactly what conditions must be met in order for the value "newUser" to be set to "true". It was my understanding from the initial posted question that the "id" must be present in both lists "UserArrivedList" AND "OldUserList", but I tend to agree with #JQSOFT that it makes more sense that the only condition that must be met should be that the "id" need only be present in "OldUserList". If that is indeed the case than the Select() expression above should be .Select(j => new User{id=j,newUser=!selectedOldUser.Contains(j)});
I hope I understood your query. One way to achieve this using Linq would be
var users = UserArrivedList.Join(OldUserList,ual=>ual.id,oul=>oul.id,(ual,oul)=>new User{id=oul.id,newUser=false})
.Concat(UserArrivedList.Select(x=>x.id).Except(OldUserList.Select(x=>x.id))
.Concat(OldUserList.Select(x=>x.id).Except(UserArrivedList.Select(x=>x.id)))
.Select(x=> new User{id=x,newUser=true}));
Now you need to create a distinct list of User type from two lists of different types; UserArrived and OldUser objects. A user is identified by a unique id of string type.
Accordingly, I'd suggest this:
var users = UserArrivedList.Select(x => x.id)
.Concat(OldUserList.Select(y => y.id))
.Distinct()
.Select(x => new User
{
id = x,
newUser = OldUserList.Count(o => o.id == x) == 0,
}).ToList();
Which gets the unique ids from both UserArrivedList and OldUserList and creates new User object for each. The OldUserList.Count(o => o.id == x) == 0, assigns false to the newUser property if the user id exists in the OldUserList otherwise true.

Convert a List<T> to a nested Dictionary<string, Dictionary<string, T>

I'm stuck on a basic problem. I need to convert a flat List to a Nested dictionary of type Dictionary<string,Dictionary<string,Dictionary<string,Dictionary<string,string>>>>
I've almost done except the fact that I need to group multiple properties, and I used an anonymous type for that. Here is an example :
List<ApprovalAction> Gen()
{
return new List<ApprovalAction>
{
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Homeworking", ParameterName = "to", ParameterValue="Approver" },
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Homeworking", ParameterName="subject", ParameterValue = "Aproval Request" },
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Homeworking", ParameterName="body", ParameterValue = "I would like an approval request"},
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Absence", ParameterName="to" , ParameterValue="Approver" },
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Absence", ParameterName="subject", ParameterValue = "Aproval Request" },
new ApprovalAction { Name = "SendMailToApprover", Step="New", ApprovalRequestType = "Absence", ParameterName="body", ParameterValue = "I would like an approval request"}
};
}
var actions = Gen();
var dico = actions
.GroupBy(x => x.ApprovalRequestType)
.ToDictionary(
gdc => gdc.Key,
gdc => gdc.GroupBy(a => a.Step)
.ToDictionary(dd => dd.Key, dd => dd.GroupBy(x => new { x.Name, x.Step, x.ApprovalRequestType }, (key, group) => new
{
Key = key.Name,
Result = group.ToDictionary(k => k.ParameterName, v => v.ParameterValue)
})));
This is the output of Linqpad :
Do you know by which code I can replace to avoid the IEnumerable ?
Thank you !
IEnumerable<> comes from the final GroupBy. If you know that the innermost group would contain exactly one item, use Single(). Otherwise use First():
var dico = actions
.GroupBy(x => x.ApprovalRequestType)
.ToDictionary(
gdc => gdc.Key,
gdc => gdc.GroupBy(a => a.Step)
.ToDictionary(dd => dd.Key, dd => dd.GroupBy(x => new { x.Name, x.Step, x.ApprovalRequestType }, (key, group) => new {
Key = key.Name,
Result = group.ToDictionary(k => k.ParameterName, v => v.ParameterValue)
}).First()
)
);
Result of the query above looks like this:

get array values using Linq (LinqPad)

I have the following array and Linq statements. I can get the field name with the Linq, but how to I retrieve the field value? (in LinqPad)
var source = new[] {
new { FirstName = "Foo", SurName = "Bar", Password = "secret" },
};
var membersToInclude =
source
.First()
.GetType()
.GetProperties()
.Where(x => x.Name != "Password")
.Select(x => x.Name)
.ToArray();
foreach (var m in membersToInclude)
{
m.Dump();
}
The Dump() just displays the words -- FirstName, SurName --
How can I also get the values -- Foo and Bar?
While I'm at it (ok I'm asking 2 questions in one post -- but .... they are all linq --LinqPad related) how do you get string length using Linq (well, LinqPad)
from w in Albums.Take(5)
select new
{
w1 = w.AlbumArtUrl.Substring(1,5),
w2 = w.AlbumArtUrl.Length
}
The Substring function works fine, but when I try to get Length, LinqPad gives me this message
The specified argument value for the function is not valid. [ Argument # = 1,Name of function(if known) = LEN ]
I have tried using len, LEN, ... how to get the string length value?
You're selecting just property's name. You need to apply it to an object to get the value. This should give you an idea:
var source = new[] {
new { FirstName = "Foo", SurName = "Bar", Password = "secret" },
};
var membersToInclude =
source
.First()
.GetType()
.GetProperties()
.Where(x => x.Name != "Password")
.Select(x =>
{
var value = x.GetValue(source.First());
return new {x.Name, value};
})
.ToArray();
foreach (var m in membersToInclude)
{
m.Dump();
}

Elasticsearch.NET NEST Object Initializer syntax for a highlight request

I've got:
var result = _client.Search<ElasticFilm>(new SearchRequest("blaindex", "blatype")
{
From = 0,
Size = 100,
Query = titleQuery || pdfQuery,
Source = new SourceFilter
{
Include = new []
{
Property.Path<ElasticFilm>(p => p.Url),
Property.Path<ElasticFilm>(p => p.Title),
Property.Path<ElasticFilm>(p => p.Language),
Property.Path<ElasticFilm>(p => p.Details),
Property.Path<ElasticFilm>(p => p.Id)
}
},
Timeout = "20000"
});
And I'm trying to add a highlighter filter but I'm not that familiar with the Object Initializer (OIS) C# syntax. I've checked NEST official pages and SO but can't seem to return any results for specifically the (OIS).
I can see the Highlight property in the Nest.SearchRequest class but I'm not experienced enough (I guess) to simply construct what I need from there - some examples and explanations as to how to employ a highlighter with OIS would be hot!
This is the fluent syntax:
var response= client.Search<Document>(s => s
.Query(q => q.Match(m => m.OnField(f => f.Name).Query("test")))
.Highlight(h => h.OnFields(fields => fields.OnField(f => f.Name).PreTags("<tag>").PostTags("</tag>"))));
and this is by object initialization:
var searchRequest = new SearchRequest
{
Query = new QueryContainer(new MatchQuery{Field = Property.Path<Document>(p => p.Name), Query = "test"}),
Highlight = new HighlightRequest
{
Fields = new FluentDictionary<PropertyPathMarker, IHighlightField>
{
{
Property.Path<Document>(p => p.Name),
new HighlightField {PreTags = new List<string> {"<tag>"}, PostTags = new List<string> {"</tag>"}}
}
}
}
};
var searchResponse = client.Search<Document>(searchRequest);
UPDATE
NEST 7.x syntax:
var searchQuery = new SearchRequest
{
Highlight = new Highlight
{
Fields = new FluentDictionary<Field, IHighlightField>()
.Add(Nest.Infer.Field<Document>(d => d.Name),
new HighlightField {PreTags = new[] {"<tag>"}, PostTags = new[] {"<tag>"}})
}
};
My document class:
public class Document
{
public int Id { get; set; }
public string Name { get; set; }
}

Categories

Resources