LINQ iterate through results - linq

I am trying to read the following text file:
Author
{
Name xyz
blog www.test.com
rating 123
}
Author
{
Name xyz
blog www.test.com
rating 123
}
Author
{
Name xyz
blog www.test.com
rating 123
}
Author
{
Name xyz
blog www.test.com
rating 123
}
I am using the following snippet to fetch my author record:
public static IEnumerable<string> GetAuthors(string path, string startfrom, string endto)
{
return File.ReadLines(path)
.SkipWhile(line => line != startfrom)
.TakeWhile(line => line != endto);
}
public static void DoSomethingWithAuthors(string fileName)
{
var result = GetAuthors(fileName, "AUTHOR", "}").ToList();
}
The above only returns me one Author Details. Could someone kindly show me how to fetch all authors in one go so I could popluate to an object. Thank you so much!!

I rarely suggest that, but if the file structure is that predicatable you might even use regex to get your author details. As the objects you want to initialize are not complex, you can match the Author bit and take the values from regex match groups.
the regex to match the authors would be something like this:
Author\s*{\s*Name\s+(.*?)\s+blog\s+(.*?)\s+rating\s+(.*?)\s*}
Your values would be in the group 1,2 and 3.
EDIT:
If it doesn't make a difference for you, you can use the ReadToEnd() method and then you can parse the whole file content as a string:
http://msdn.microsoft.com/en-us/library/system.io.streamreader.readtoend(v=vs.100).aspx
As for the regex solution - check this out:
http://msdn.microsoft.com/en-us/library/twcw2f1c.aspx
An adapted version - it might need tweaking but in general it should work:
string text = [yourInputFileAsString]
string pat = #"Author\s*{\s*Name\s+(.*?)\s+blog\s+(.*?)\s+rating\s+(.*?)\s*}";
Regex r = new Regex(pat, RegexOptions.IgnoreCase | RegexOptions.Singleline);
Match m = r.Match(text);
var authors = new List<Author>();
while (m.Success)
{
var name = m.Groups[1].Value;
var blog = m.Groups[2].Value;
var rating = m.Groups[3].Value;
var author = new Author(name, blog, rating);
authors.Add(author);
m = m.NextMatch();
}

It is going to stop at the first } it runs into.
Remove the .TakeWhile(line => line != endto) bit and it should work for you.

Related

multiple word search in flutter

Suppose I have a recipe called Garlic parmesan butter. I need to return an object when the appropriate name has been found.
Now in a simple ad-hoc solution I can search in the following way:
class SearchRecipe {
late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
for (int i = 0; i < Store.instance.getAllRecipes().length; i++) {
if (suggestion == Store.instance.getAllRecipes()[i].recipeName) {
return Store.instance.getAllRecipes()[i];
}
}
return recipe;
}
}
But I need a simple way where if the user types in Garlic butter I need to return the object associated with the Garlic Paremesan butter.
How can I do it?
Edit: I should've clarified that I'm working with a List of objects. So the Store.instance.getAllRecipes() basically returns a List<RecipeModel>.
Update 1: This is what I've written:
class SearchRecipe {
//late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
List<RecipeModel> results = [];
suggestion!.split(' ').forEach((s) {
results.addAll(Store.instance
.getAllRecipes()
.where((element) => element.recipeName!.contains(s)));
});
results = results.toSet().toList();
for (int i = 0; i < results.length; i++) {
return results[i];
}
return results[0];
}
}
String search = 'Garlic butter';
List<String> list = [
'Garlic Paremesan butter',
'Butter',
'Garlic',
'Butter Garlic',
'Paremesan',
'Stackoverflow'
];
List<String> results = [];
search.split(' ').forEach((s) {
results.addAll(list.where((element) => element.contains(s)));
});
// Avoid repeated values
results = results.toSet().toList();
Split the user input at spaces. Then you have a list. You can check the list and depending on your preference implement a variety of behaviors.
You can match if all words in the list are found. You can return if at least one of the words is matched.
You could give preference to more contained words or you could check the order of words.
In either case you would also not check for equality but use a function like includes / contains to check whether the searched word is part of the name.
(Checking the order could be done by remembering which words you already identified and only searching after the words that were found. In your example you would find ‘garlic’ and after that you would just look through ‘paremesan Butter’ and try to find ‘butter’)
first split the input text
var string = "Hello world!";
string.split(" ");
// ['Hello', 'world!'];
then iterate over each word in above array and check whether the above
Store.instance.getAllRecipes()[i].recipeName contains above word.
if(str.equalsIgnoreCase(str))
{
//remaining code
}
try contains method.
.contains()

How to search exact record by asp.net web api with help of lamda expresion

I am facing a problem in searching a exact record by LINQ query method in ASP.NET Web API my controller. This is my code:
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x=>x.PrdKeyword.Contains(SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
In this I am searching the record with value have keyword column and getting the result but problem is that it is not giving exact result for example if in database two record have keyword column value like shirt and another have Tshirt
Then if I pass shirt in SearchText or pass tshirt in SearchText it is giving both record while I want one record which exact match with SearchText. Please help me
My updated action method code is:
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x => CheckWord(x.PrdKeyword, SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
private bool CheckWord(string source, string searchWord)
{
var punctuation = source.Where(Char.IsPunctuation).Distinct().ToArray();
var words = source.Split().Select(x => x.Trim(punctuation));
return words.Contains(searchWord, StringComparer.OrdinalIgnoreCase);
}
But is throwing the same error - http 500
EDITED 2
Added ToList() - db.tblProducts.ToList().... In this case we retrieve all data from Data Base and filter them in memory. If we don't retrieve all data before filtering .Net tries to create request to SQL with filtration and can't because there are .Net methods as CheckWord().
I think we can get required data without retrieving all table into memory, but don't know how. As variant we should write specific Stored Procedure and use it. Get all into memory is a simplest way (but not faster)
Please, look at this post Get only Whole Words from a .Contains() statement
Actually, for your case solution can be:
IEnumerable<tblProduct> tblProduct = db.tblProducts.ToList()
.Where(x => Regex.Match(x.PrdKeyword, $#"\b{SearchText}\b", RegexOptions.IgnoreCase).Success)
.AsEnumerable();
Option 2. Without regexp:
public static bool CheckWord(string source, string searchWord)
{
if (source == null)
return false;
var punctuation = source.Where(Char.IsPunctuation).Distinct().ToArray();
var words = source.Split().Select(x => x.Trim(punctuation));
return words.Contains(searchWord, StringComparer.OrdinalIgnoreCase);
}
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.ToList()
.Where(x => CheckWord(x.PrdKeyword, SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
Sorry, I'm from phone now, there can be mistakes here. Will try it in 3-4 hour
You are making a simple mistake. You just need to use .Equals instead of .Contains.
When you use Contains .Net will check if the input string is part of the main string. Whereas Equals will check for exact match.
var mainStr = “long string with Hello World”;
var inputStr = “Hello”;
var status = mainStr.Contains(inputStr);
// Value of status is `true`
status = mainStr.Equals(inputStr);
// Value of status is `false`
So your code should look like this:
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x=>x.PrdKeyword.Equals(SearchText)).AsEnumerable();
.Equals can also help you find exact match with or without having case-sensitive check in force. The single-parameterised method does a Case-Sensitive check whereas the other overridden methods of .Equals gives you an opportunity to ignore it.
Hope this helps!

How to create a search page and function in basic web app

I have a fairly simple MVC webapp connected to a database. The user needs a search page that will pull certain records from a single table. The user can give 1 or more keywords. The search function must search for records containing those keywords in 3 different columns: title, description or poc.
I've got the following setup but its incorrect. It is providing records containing any of the keywords. The results must instead be records containing all keywords. Also, I'm not sure if this is the best way of writing a search function...
// searchString contains all keywords delimited by spaces
string[] keywordArray = Regex.Split(searchString, "\\s");
var model = new List<MyTable>();
foreach (string word in keywordArray)
{
foreach (var record in myTableRepository.MyTable.Where(x => x.title.ToLower().Contains(word.ToLower()) || (x.description != null && x.description.ToLower().Contains(word.ToLower())) || (x.poc != null && x.poc.ToLower().Contains(word.ToLower()))).ToList())
{
model.Add(new MyTable
{
id = record.id,
title = record.title,
description = record.description,
poc = record.poc
});
}
}
return View(model);
For example, if the user gave the following search criteria "john test phase", then the results could look like this:
title description poc
Lorem Ipsum Test A Phase A lorem ipsum john doe
Lorem Ipsum phase This is john test for jack jane doe
Lorem Ipsum John test for jim clark phase
etc..
Thanks in advance for advice and tips!
It might be better to search one record at a time and check for all the keywords within one record. Perhaps you could collect all the words associated with a particular entry in one variable and then search that for all the keywords.
// searchString contains all keywords delimited by spaces
string[] keywordArray = Regex.Split(searchString, "\\s");
var model = new List<MyTable>();
Boolean searchResult;
foreach (var record in myTableRepository.MyTable)
{
searchResult = true;
var recordTerms = record.title + " " + record.description + " " + record.poc;
recordTerms = recordTerms.toLower();
recordArray = Regex.Split(recordTerms, "\\s");
foreach (var word in keywordArray)
{
if (!recordArray.asList.contains(word))
searchResult = false;
}
}
if (searchResult) {
model.Add(new MyTable
{
id = record.id,
title = record.title,
description = record.description,
poc = record.poc
});
}
}
return View(model);

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Extracting text from an IEnumerable<T>

I have a IEnumerable<T> collection with Name, FullName and Address.
The Address looks like this:
Street1=54, Street2=redfer street, Pin=324234
Street1=54, Street2=fdgdgdfg street, Pin=45654
Street1=55, Street2=tryry street, Pin=65464
I want to loop through this collection and print only those Names, FullNames whose Street1=54
How can i do it in LINQ?
Ok I was able to do this to extract Street1 of the Address
coll.Address.Split(",".ToCharArray())[0]returns me Street1=54 .
Now how do I add this to the condition and print only those Name, FullName whose Street1=54
Based on your update, you can adapt Jared Par's code this way:
var result = collection.Where(x => x.Address.Contains("Street1=54"));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
If you want to be able to plug in your Street1 value with a variable, then do this:
var street1 = "54";
var result = collection.Where(x => x.Address.Contains("Street1=" + street1 ));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
BTW, you really should update your question or add a comment to a specific answer rather than adding a new answer that isn't.
Try this
var result = collection.Where(x => x.Address.Street1==54);
foreach ( var cur in result ) {
Console.WriteLine(var.Name);
}
Select the correct list of :
IList<T> matches = myListOfEnumerables.Where(m => m.Street1 == 54).ToList();
Then loop and print.
Actually the record looks like this:
{Name="Jan" FullName="Kathy Jan" Address="Street1=54, Street2=redfer street, Pin=324234"}
I have to loop through this collection and print only those Names, FullNames whose Street1=54
If the updated information is accurate, you should change the way you store the data.
It looks like you've packed the address information into a string. Why not store it as an object. In fact, why not just as more fields in the same object as the Name and FullName? (and why duplicate the first-name information?)
public class Person
{
public string FirstName, LastName, Street1, Street2, Pin;
}
IEnumerable<Person> persons = GetAllPersonsSomehow();
foreach (Person person in persons.Where(p => p.Street1 == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
Assuming you have to keep the address information in a string, you need a parser for it.
public static IDictionary<string, string> GetAddressFields(string address)
{
return address.Split(',').ToDictionary(
s => s.Substring(0, s.IndexOf('=')).Trim(),
s => s.Substring(s.IndexOf('=') + 1).Trim());
}
foreach (Person person in persons.Where(p =>
GetAddressFields(p.Address)["Street1"] == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
So, what you could do is that you could write a generator for parsing the Address field and then enumerating properties of that. This is a fairly common thing in the functional programming world.
To be fair you would want this code to be lazy in that it would only compute a minimal set. I'm gonna suggest some code from the BCL but you can (and probably should) rewrite the same helper methods with generators.
public static IEnumerable<KeyValuePair<string,string>> NameValueSplit( this string s )
{
foreach (var x in s.Split(','))
{
var y = x.Split(new char[] { '=' }, 2, StringSplitOptions.None);
yield return new KeyValuePair<string, string>(y[0].TrimStart(), y[1].TrimEnd());
}
}
With that helper function you can write code like this
var result = collection.Where(x => x.Address
.NameValueSplit().Any(x => x.Key == "Street1" && x.Value == "54"));
foreach ( var item in result )
{
Console.WriteLine(item.Name);
}
Now this code will not run on your SQL Server if you were thinking of that, but you could write a WHERE clause where you would search the Address field for a sub string %Street1=54%. I like lazy evaluation for string operations and think that's a lacking feature in the BCL. That is why I suggested that kind of solution.

Resources