how can I convert this foreach loop to linq? - c#

I'm very new to Linq. I want to convert these lines of code to linq lambda expression, If it makes sense how can I achieve?
foreach (var Type in Essay.Text)
{
string text =
$"{"here is result"}\n{method(Type)}";
if (text.Length <= 20 && !string.IsNullOrEmpty(method(Type)))
{
Essay.Total += text;
}
}

With help of Resharper:
Essay.Total = string.Concat(
Essay.Text.Select(Type => new {Type, text = $"{"here is result"}\n{method(Type)}"})
.Where(#t => #t.text.Length <= 20 && !string.IsNullOrEmpty(method(#t.Type)))
.Select(#t => #t.text)
);

Something like this:
foreach (var type in from type in Essay.Text
let text = $"{"here is result"}\n{method(Type)}"
where text.Length <= 20 && !string.IsNullOrEmpty(method(Type)) select type)
{
Essay.Total += type;
}

A few pointers:
There is no reason to call method twice, just cache it by using let
You can also check the length of the text before constructing the final string
This should do the job for you:
var texts = from type in essay.Text
let methodResult = method(type)
where !string.IsNullOrEmpty(methodResult)
let text = $"here is result\n{methodResult}"
where text.Length <= 20
select text;
essay.Total += string.Concat(texts);

Related

How to perform word search using LINQ?

I have a list which contains the name of suppliers. Say
SuppId Supplier Name
----------------------------------
1 Aardema & Whitelaw
2 Aafedt Forde Gray
3 Whitelaw & Sears-Ewald
using following LINQ query
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey));
I can return records correctly in the following conditions,
1) If i am using search string as "Whitelaw & Sears-Ewald" it will return 3rd record.
2) If i am using "Whitelaw" or "Sears-Ewald" it will return 3rd record.
But how can i return 3rd record if i am giving search string as "Whitelaw Sears-Ewald". It always returns 0 records.
Can i use ALL to get this result, but i dont know how to use it for this particular need.
What I usually do in this situation is split the words into a collection, then perform the following:
var searchopts = SearchKey.Split(' ').ToList();
supplierListQuery = supplierListQuery
.Where(x => searchopts.Any(y=> x.SupplierName.Contains(y)));
This works for me:
IEnumerable<string> keyWords = SearchKey.Split('');
supplierListQuery = supplierListQuery
.AsParallel()
.Where
(
x => keyWords.All
(
keyword => x.SupplierName.ContainsIgnoreCase(keyword)
)
);
Thank you all for your quick responses. But the one which worked or a easy fix to handle this was timothyclifford's note on this. Like he said i alterd my answer to this
string[] filters = SearchKey.ToLower().Split(new[] { ' ' });
objSuppliersList = (from x in objSuppliersList
where filters.All(f => x.SupplierName.ToLower().Contains(f))
select x).ToList();
Now it returns the result for all my serach conditions.
Because "Whitelaw" appears in both you will get both records. Otherwise there is no dynamic way to determine you only want the last one. If you know you only have these 3 then append .Last() to get the final record.
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey.Split(' ')[0]));
You need to use some sort of string comparer to create your own simple Search Engine and then you can find strings that are most likely to be included in your result :
public static class SearchEngine
{
public static double CompareStrings(string val1, string val2)
{
if ((val1.Length == 0) || (val2.Length == 0)) return 0;
if (val1 == val2) return 100;
double maxLength = Math.Max(val1.Length, val2.Length);
double minLength = Math.Min(val1.Length, val2.Length);
int charIndex = 0;
for (int i = 0; i < minLength; i++) { if (val1.Contains(val2[i])) charIndex++; }
return Math.Round(charIndex / maxLength * 100);
}
public static List<string> Search(this string[] values, string searchKey, double threshold)
{
List<string> result = new List<string>();
for (int i = 0; i < values.Length; i++) if (CompareStrings(values[i], searchKey) > threshold) result.Add(values[i]);
return result;
}
}
Example of usage :
string[] array = { "Aardema & Whitelaw", "Aafedt Forde Gray", "Whitelaw & Sears-Ewald" };
var result = array.Search("WhitelawSears-Ewald", 80);
// Results that matches this string with 80% or more
foreach (var item in result)
{
Console.WriteLine(item);
}
Output: Whitelaw & Sears-Ewald
If you want an easy (not very handy) solution,
var result = supplierListQuery
.Select(x => normalize(x.SupplierName))
.Where(x => x.Contains(normalize(SearchKey)));
string normalize(string inputStr)
{
string retVal = inputStr.Replace("&", "");
while (retVal.IndexOf(" ") >= 0)
{
retVal = retVal.Replace(" ", " ");
}
return retVal;
}

Find a fixed length string with specific string part in C#

I want to find a string of fixed length with specific substring. But I need to do it like we can do in SQL queries.
Example:
I have strings like -
AB012345
AB12345
AB123456
AB1234567
AB98765
AB987654
I want to select strings that have AB at first and 6 characters afterwards. Which can be done in SQL by SELECT * FROM [table_name] WHERE [column_name] LIKE 'AB______' (6 underscores after AB).
So the result will be:
AB012345
AB123456
AB987654
I need to know if there is any way to select strings in such way with C#, by using AB______.
You can use Regular Expressions to filter the result:
List<string> sList = new List<string>(){"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
var qry = sList.Where(s=>Regex.Match(s, #"^AB\d{6}$").Success);
Considering you have an string array:
string[] str = new string[3]{"AB012345", "A12345", "AB98765"};
var result = str.Where(x => x.StartsWith("AB") && x.Length == 8).ToList();
The logic is if it starts with AB, and its length is 8. It is your best match.
this should do it
List<string> sList = new List<string>(){
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"};
List<string> sREsult = sList.Where(x => x.Length == 8 && x.StartsWith("AB")).ToList();
first x.Length == 8 determines the length and x.StartsWith("AB") determines the required characters at the start of the string
This can be achieved by using string.Startwith and string.Length function like this:
public bool CheckStringValid (String input)
{
if (input.StartWith ("AB") && input.Length == 8)
{
return true;
}
else
{
return false;
}
}
This will return true if string matches your criteria.
Hope this helps.
var strlist = new List<string>()
{
"AB012345",
"AB12345",
"AB123456",
"AB1234567",
"AB98765",
"AB987654"
};
var result = strlist.Where(
s => (s.StartsWith("AB") &&(s.Length == 8))
);
foreach(var v in result)
{
Console.WriteLine(v.ToString());
}

Is there any way to do this, assign a value within a List<T>.ForEach() statement?

I have this:
var lineArray = line.Split(';');
lineArray.ToList().ForEach(x =>
{
if (x == "(null)")
x = "NULL";
else
x = string.Format("'{0}'", x);
});
This runs fine, but does not seem to change the elements within lineArray. I thought of assigning the results of ForEach to a var but it returns void.
Any ideas ?
EDIT: I think it's because ToList() return value is not assigned anywhere...
var lineArray = line.Split(';')
.Select(x=>x == "(null)"
? "NULL"
: string.Format("'{0}'", x))
.ToArray();
you are trying to use List<T>.ForEach(Action<T> action) with lambda expression (T is string here)
if lambda expression is replaced with named method it turns out that only method argument is modified, but changes are not reflected on calling side, because x is not ref argument
private void Replace(string x)
{
if (x == "(null)")
x = "NULL";
else
x = string.Format("'{0}'", x);
}
var list = lineArray.ToList();
list.ForEach(Replace);
// check list here and make sure that there are no changes
ForEach could work if T is a reference type and action modifies some properties but not the reference itself
don't use ForEach like that - use a for loop.
for (int i = 0; i < lineArray.Length; i++)
{
if (lineArray[i] == "(null)")
lineArray[i] = "NULL";
else
lineArray[i] = string.Format("'{0}'", lineArray[i]);
}
you can construct a new list from your original one :
var newList = lineArray.Select(x => x == "(null)" ? "NULL" : string.Format("'{0}'", x));
I think this small change will work
var lineArray = line.Split(';').ToList();
lineArray.ForEach(x =>
{
if (x == "(null)")
x = "NULL";
else
x = string.Format("'{0}'", x);
});
Below is a working example
string line = "a;b;null;c;";
var lineArray = line.Split(';').ToList();
lineArray.ForEach(x =>
{
if (x == "(null)")
x = "NULL";
else
x = string.Format("'{0}'", x);
});
Result:
If you need just to remove (null) values from the list, you do not need to loop
just use removeAll
lineArray.RemoveAll(a=>a.Equals("(null)"));
working example below

Change the foreach loop into the LINQ query

I stuck in an easy scenario. I have a List<string> object, all of its items has the body of:
item_1_2_generatedGUID //generatedGUID is Guid.NewGuid()
but there may be much more numbers
item_1_2_3_4_5_generatedGUID etc
now, I'm wondering how to change that loop into the LINQ's query. Any ideas ?
string one = "1"; //an exaplme
string two = "2"; //an exaplme
foreach (var item in myStringsList)
{
string[] splitted = item.Split(new char[] { '_' },
StringSplitOptions.RemoveEmptyEntries);
if(splitted.Length >= 3)
{
if(splitted[1] == one && splitted[2] == two)
{
resultList.Add(item);
}
}
}
var result = from s in lst
let spl = s.Split('_')
where spl.Length >= 3 && spl[1] = one && spl[2] == two
select s;
Try this:
var query = from item in myStringsList
let splitted = item.Split(new[] { '_' }, SSO.RemoveEmptyEntries)
where splitted.Length >= 3
where splitted[1] == one && splitted[2] == two
select item;
var resultList = query.ToList();
This is a different approach:
var items = myStringsList.
Where(x => x.Substring(x.IndexOf("_")).StartsWith(one+"_"+two+"_"));
You probably will need to add a +1 in the IndexOf, but I'm not sure.
What it does is:
Removes the first item (that's the substring for). In your example, it should be "1_2_3_4_5_generatedGUID"
Checks the string starts with what you are expecting. In your example: 1_2_
Edited: Added the pattern for anything at the first "position"
var result = items.Where(i => Regex.IsMatch(i, "^[^_]_1_2_"));

Problem calling string manipulation method within linq-to-sql query

I'm having a frustrating issue trying to use LINQ to call a string manipulation method. I've done lots of searching now and have tried various method to get the line noted as 'FAILS' below to work. It currently throws an exception.
Some things I've tried:
a) Initially the creation of the concatenated key was in the same query, didn't change anything
b) Converting the non-string fields to strings (another whole can of works with .ToString not working in linq. String.Concat and String.Format were tried, work ok in some cases but not when you try to refer to that value later on)
c) Using the concat etc instead of the '+' to join the things together.
As you can see it seems fairly tolerant of appending strings to non-strings, but not when that method is invoked.
There are lots of rows so I'd prefer not to convert the data to a list/array etc, but if that's the only option then any suggestions appreciated.
Many thanks! - Mark
var vouchers = from v in db.Vouchers
select new
{
v.Amount,
v.Due_Date,
v.Invoice_Date,
v.PO_CC,
v.Vendor_No_,
v.Invoice_No_,
invoiceNumeric = MFUtil.StripNonNumeric(v.Invoice_No_)
};
var keyedvouchers = from vv in vouchers
select new
{
thekey = vv.Vendor_No_ + "Test", // works with normal string
thekey2 = vv.Amount + "Test", // works with decimal
thekey3 = vv.Invoice_Date + "Test", // works with date
thekey4 = vv.invoiceNumeric, // works
thekey5 = vv.invoiceNumeric + "Test" // FAILS
};
-- The method to strip chars ---
public static string StripNonNumeric(string str)
{
StringBuilder sb = new StringBuilder();
foreach (char c in str)
{
// only append if its withing the acceptable boundaries
// strip special chars: if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') | || (c >= 'a' && c <= 'z') | c == '.' || c == '_')
// strip any nonnumeric chars
if (c >= '0' && c <= '9')
{
sb.Append(c);
}
}
return sb.ToString();
}
-- The Exception Message--
System.InvalidOperationException was unhandled by user code
Message=Could not translate expression 'Table(Voucher).Select(v => new <>f__AnonymousType07(Amount = v.Amount, Due_Date = v.Due_Date, Invoice_Date = v.Invoice_Date, PO_CC = v.PO_CC, Vendor_No_ = v.Vendor_No_, Invoice_No_ = v.Invoice_No_, invoiceNumeric = StripNonNumeric(v.Invoice_No_))).Select(vv => new <>f__AnonymousType15(thekey = (vv.Vendor_No_ + "Test"), thekey2 = (Convert(vv.Amount) + "Test"), thekey3 = (Convert(vv.Invoice_Date) + "Test"), thekey4 = vv.invoiceNumeric, thekey5 = (vv.invoiceNumeric + "Test")))' into SQL and could not treat it as a local expression.
It's because it tries to build an SQL query of the expression and the MFUtil.StripNonNumeric cannot be translated into SQL.
Try returning it first and then convert the reult into a list and then use a second query to convert it.
var vouchers_temp = from v in db.Vouchers
select new
{
v.Amount,
v.Due_Date,
v.Invoice_Date,
v.PO_CC,
v.Vendor_No_,
v.Invoice_No_
};
var vouchers = vouchers_temp.ToList().Select( new {
Amount,
Due_Date,
Invoice_Date,
PO_CC,
Vendor_No_,
Invoice_No_,
invoiceNumeric = MFUtil.StripNonNumeric(Invoice_No_)
});
It FAILS to work, because it is not suppose to work.
Create a SQL-side function and call that in the query.

Categories