Sorting strings in C# - c#

I have a delimited string that I need sorted. First I need to check if 'Francais' is in the string, if so, it goes first, then 'Anglais' is next, if it exists. After that, everything else is alphabetical. Can anyone help me? Here's what I have so far, without the sorting
private string SortFrench(string langs)
{
string _frenchLangs = String.Empty;
string retval = String.Empty;
_frenchLangs = string.Join(" ; ",langs.Split(';').Select(s => s.Trim()).ToArray());
if (_frenchLangs.Contains("Francais"))
retval += "Francais";
if (_frenchLangs.Contains("Anglais"))
{
if (retval.Length > 0)
retval += " ; ";
retval += "Anglais";
}
//sort the rest
return retval;
}

Someone liked my comment, so figured I'd go ahead and convert that into your code:
private string SortFrench(string langs)
{
var sorted = langs.Split(';')
.Select(s => s.Trim())
.OrderByDescending( s => s == "Francais" )
.ThenByDescending( s => s == "Anglais" )
.ThenBy ( s => s )
.ToArray();
return string.Join(" ; ",sorted);
}
My syntax may be off slightly as I've been in the Unix world for awhile now and haven't used much LINQ lately, but hope it helps.

Here's what I came up with. You could change the .Sort() for a OrderBy(lang => lang) after the Select, but I find it's cleaner that way.
public string SortLanguages(string langs)
{
List<string> languages = langs.Split(';').Select(s => s.Trim()).ToList();
languages.Sort();
PlaceAtFirstPositionIfExists(languages, "anglais");
PlaceAtFirstPositionIfExists(languages, "francais");
return string.Join(" ; ", languages);
}
private void PlaceAtFirstPositionIfExists(IList<string> languages, string language)
{
if (languages.Contains(language))
{
languages.Remove(language);
languages.Insert(0, language);
}
}

You should use a custom comparer class
it will allow you to use the built in collection sorting functions, or the linq OrderBy using your own criteria

Try this:
private string SortFrench(string langs)
{
string _frenchLangs = String.Empty;
List<string> languages = langs
.Split(';')
.Select(s => s.Trim())
.OrderBy(s => s)
.ToList();
int insertAt = 0;
if (languages.Contains("Francais"))
{
languages.Remove("Francais");
languages.Insert(insertAt, "Francais");
insertAt++;
}
if(languages.Contains("Anglais"))
{
languages.Remove("Anglais");
languages.Insert(insertAt, "Anglais");
}
_frenchLangs = string.Join(" ; ", languages);
return _frenchLangs;
}

All can be done in single line
private string SortFrench(string langs)
{
return string.Join(" ; ", langs.Split(';').Select(s => s.Trim())
.OrderBy(x => x != "Francais")
.ThenBy(x => x != "Anglais")
.ThenBy(x=>x));
}

Sorting alphabetically is simple; adding .OrderBy(s => s) before that .ToArray() will do that. Sorting based on the presence of keywords is trickier.
The quick and dirty way is to split into three:
Strings containing "Francais": .Where(s => s.Contains("Francais")
Strings containing "Anglais": .Where(s => s.Contains("Anglais")
The rest: .Where(s => !francaisList.Contains(s) && !anglaisList.Contains(s))
Then you can sort each of these alphabetically, and concatenate them.
Alternatively, you can implement IComparer using the logic you described:
For strings A and B:
If A contains "Francais"
If B contains "Francais", order alphabetically
Else
If B contains "Francais", B goes first
Else
If A contains "Anglais"
If B contains "Anglais", order alphabetically
Else, A goes first
Else, order alphabetically
There may be room for logical re-arrangement to simplify that. With all that logic wrapped up in a class that implements IComparer, you can specify that class for use by .OrderBy() to have it order your query results based on your custom logic.

You can also use Array.Sort(yourStringArray)

This code creates a list of the languages, sorts them using a custom comparer, and then puts the sorted list back together:
const string langs = "foo;bar;Anglais;Francais;barby;fooby";
var langsList = langs.Split(';').ToList();
langsList.Sort((s1, s2) =>
{
if (s1 == s2)
return 0;
if (s1 == "Francais")
return -1;
if (s2 == "Francais")
return 1;
if (s1 == "Anglais")
return -1;
if (s2 == "Anglais")
return 1;
return s1.CompareTo(s2);
});
var sortedList = string.Join(";", langsList);
Console.WriteLine(sortedList);

This way you can set any list of words in front:
private static string SortFrench(string langs, string[] setStartList)
{
string _frenchLangs = String.Empty;
List<string> list = langs.Split(';').Select(s => s.Trim()).ToList();
list.Sort();
foreach (var item in setStartList){
if (list.Contains(item))
{
list.Remove(setFirst);
}
}
List<string> tempList = List<string>();
tempList.AddRange(setStartList);
tempList.AddRange(list);
list = tempList;
_frenchLangs = string.Join(" ; ", list);
return _frenchLangs;
}

Related

How To: Use LINQ to search data for a string containing many possible types of "Single Quote"

The Problem:
My customers want to be able to search for first and last names in my site, using any version of ', `, or those goofy microsoft 'start' and 'end' quotes.
I would like to be able to write a single statement that looks for all possible permutations of that evil symbol, like such:
var customers = _sms.CurrentSession.Query<customer>()
.Where(c => c.FirstName.Contains(firstName)
&& c.LastName.Contains(lastName)
&& !c.IsEmployee)
.ToList();
and if the user was searching for "O'Brien", I would show them results for "O'Brien", and "O`Brien", and any other version of that stupid thing.
What's the best way to do that? Is there a way to do it (without a massive recursion where I do things like
if (firstName.Contains("'")
{
var customers = _sms.CurrentSession.Query<customer>()
.Where(c => c.FirstName.Contains(firstName)
&& c.LastName.Contains(lastName)
&& !c.IsEmployee)
.ToList();
firstName = firstName.Replace("'", "`");
customers.AddRange(_sms.CurrentSession.Query<customer>()
.Where(c => c.FirstName.Contains(firstName)
&& c.LastName.Contains(lastName)
&& !c.IsEmployee)
.ToList());
}
etc...
Update
private static readonly string[] Quotes = { "#", "'", "‘", "’", "`" };
private IEnumerable<customer> CustomersByFirstName(string firstName, bool showInactive = true)
{
firstName = firstName.Replace(Quotes[1], Quotes[0]);
var customers = _sms.CurrentSession.Query<customer>()
.Where(c => (c.FirstName.Replace(Quotes[1], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[2], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[3], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[4], Quotes[0]).Contains(firstName))
&& !c.IsEmployee)
.ToList();
if (!showInactive)
{
customers = customers.Where(c => !c.Disabled).ToList();
}
return customers;
}
This is almost it... Almost
Final Update
This turns out to be exactly the solution, tho not as elegant and small as I might like:
private IEnumerable<customer> CustomersByFirstName(string firstName, bool showInactive = true)
{
firstName = firstName.Replace(Quotes[1], Quotes[0]);
firstName = firstName.Replace(Quotes[2], Quotes[0]);
firstName = firstName.Replace(Quotes[3], Quotes[0]);
firstName = firstName.Replace(Quotes[4], Quotes[0]);
var customers = _sms.CurrentSession.Query<customer>()
.Where(c => (c.FirstName.Replace(Quotes[1], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[2], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[3], Quotes[0]).Contains(firstName) ||
c.FirstName.Replace(Quotes[4], Quotes[0]).Contains(firstName))
&& !c.IsEmployee)
.ToList();
if (!showInactive)
{
customers = customers.Where(c => !c.Disabled).ToList();
}
return customers;
}
Sorry, I posted an answer without fully understanding the question. Here's the corrected answer.
I would define your criteria for a match in a separate function. Here's how I implemented it:
// Add quote characters as needed.
List<char> quotes = new List<char> { '\'', '‘', '’' };
public string CleanName(string name)
{
StringBuilder cleanName = new StringBuilder(name);
foreach (char quote in quotes)
{
cleanName = cleanName.Replace(quote, '\'');
}
return cleanName.ToString();
}
public bool IsMatch(string n0, string n1)
{
return String.Compare(CleanName(n0), CleanName(n1)) == 0;
}
Then, search for matching names using the IsMatch function, as demonstrated in this unit test:
[TestMethod]
public void TestQuoteSearch()
{
string searchName = "O'Brien";
var matchingNames = names.Where(name => IsMatch(name, searchName));
Assert.AreEqual(4, matchingNames.Count());
Assert.IsTrue(matchingNames.Contains("O'Brien"));
Assert.IsTrue(matchingNames.Contains("O‘Brien"));
Assert.IsTrue(matchingNames.Contains("O’Brien"));
Assert.IsFalse(matchingNames.Contains("Jones"));
}
You could do something like :
char[] all_quotes= new char[] { '\'', '‘', '’' /* any other quote you like */ };
Then replace them in your names with a special char (lets say *), or cut them out all together, and then search for those matches then.
have a look at this answer for an example of how to do it, or brew your own:
var customers = _sms.CurrentSession.Query<customer>()
.Where(c => c.FirstName.Replace(all_quotes, "*")
.Contains(firstName.Replace(all_quotes, "*"))
&& // same for others)
.ToList();
Regarding Final Update:
I wouldn't have the one to replace as part as the quotes. I'd do something like :
string inplace = "#";
private static readonly string[] Quotes = {"'", "‘", "’", "`" };
foreach (var q in Quotes)
firstName = firstName.Replace(q, inplace);
you can wrap the foreach in { } if you'd like. It's shorter, and if you end up adding a quote to the array, you don't need another line of "replace"
Create Store Procedure and import to Entity Modal then call like method.

Find the longest repetition character in String

Let's say i have a string like
string text = "hello dear";
Then I want to determine the longest repetition of coherented characters - in this case it would be ll. If there are more than one with the same count, take any.
I have tried to solve this with linq
char rchar = text.GroupBy(y => y).OrderByDescending(y => y.Count()).Select(x => x).First().Key;
int rcount = text.Where(x => x == rchar).Count();
string routput = new string(rchar, rcount);
But this returns ee. Am I on the right track?
Another solution using LINQ:
string text = "hello dear";
string longestRun = new string(text.Select((c, index) => text.Substring(index).TakeWhile(e => e == c))
.OrderByDescending(e => e.Count())
.First().ToArray());
Console.WriteLine(longestRun); // ll
It selects a sequence of substrings starting with the same repeating character, and creates the result string with the longest of them.
Although a regex or custom Linq extension is fine, if you don't mind "doing it the old way" you can achieve your result with two temporary variables and a classic foreach loop.
The logic is pretty simple, and runs in O(n). Loop through your string and compare the current character with the previous one.
If it's the same, increase your count. If it's different, reset it to 1.
Then, if your count is greater than the previous recorded max count, overwrite the rchar with your current character.
string text = "hello dear";
char rchar = text[0];
int rcount = 1;
int currentCount = 0;
char previousChar = char.MinValue;
foreach (char character in text)
{
if (character != previousChar)
{
currentCount = 1;
}
else
{
currentCount++;
}
if (currentCount >= rcount)
{
rchar = character;
rcount = currentCount;
}
previousChar = character;
}
string routput = new string(rchar, rcount);
It's indeed verbose, but gets the job done.
If you prefer doing things with LINQ, you can do it fairly simply with a LINQ extension:
var text = "hello dear";
var result = string.Join("",
text
.GroupAdjacentBy((l, r) => (l == r)) /* Create groups where the current character is
Is the same as the previous character */
.OrderByDescending(g => g.Count()) //Order by the group lengths
.First() //Take the first group (which is the longest run)
);
With this extension (taken from Use LINQ to group a sequence of numbers with no gaps):
public static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
}
Might be a bit overkill - but I've found GroupAdjacentBy quite useful in other situations as well.
RegEx would be a option
string text = "hello dear";
string Result = string.IsNullOrEmpty(text) ? string.Empty : Regex.Matches(text, #"(.)\1*", RegexOptions.None).Cast<Match>().OrderByDescending(x => x.Length).First().Value;

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());
}

LINQ way to check Contains in a List (regardless of white spaces)

I have following code that checks whether userRoles collection has any of the values in authorizedRolesList. It does not work if the userRoleName has a whitespace.
What is the most efficient LINQ way for handling this?
CODE
List<string> authorizedRolesList = null;
string AuthorizedRolesValues = "A, B ,C,D";
if (!String.IsNullOrEmpty(AuthorizedRolesValues))
{
authorizedRolesList = new List<string>((AuthorizedRolesValues).Split(','));
}
string userRoleName = String.Empty;
Collection<string> userRoles = new Collection<string>();
userRoles.Add("B ");
bool isAuthorizedRole = false;
if (userRoles != null)
{
foreach (string roleName in userRoles)
{
userRoleName = roleName.Trim();
if (authorizedRolesList != null)
{
//Contains Check
if (authorizedRolesList.Contains(userRoleName))
{
isAuthorizedRole = true;
}
}
}
}
REFERENCE:
When to use .First and when to use .FirstOrDefault with LINQ?
Intersect with a custom IEqualityComparer using Linq
Ignoring hyphen in case insensitive dictionary keys
C#: splitting a string and not returning empty string
When does IEnumerable.Any(Func) return a value?
Is IEnumerable.Any faster than a for loop with a break?
I guess most efficient LINQ way means most readable here.
The obvious way is to use StringSplitOptions.RemoveEmptyEntries when calling Split() and not storing the whitespace in the first place.
authorizedRolesList = AuthorizedRolesValues.Split(new []{','}, StringSplitOptions.RemoveEmptyEntries);
But if for some reason you want to keep the additional whitespace or can't change the entries in authorizedRolesList, you can easily change your if clause from
if (authorizedRolesList.Contains(userRoleName))
to
if (authorizedRolesList.Any(x => x.Trim() == userRoleName))
BTW, talking about LINQ:
You could just replace your code with
bool isAuthorizedRole = userRoles.Any(ur => authorizedRolesList.Any(ar => ar.Trim() == ur.Trim()))
if you ensure userRoles and authorizedRolesList are not null (use an empty collection instead).
Even more readable IMHO would be something like
bool isAuthorizedRole = userRoles.Intersect(authorizedRolesList, new IgnoreWhitespaceStringComparer()).Any();
where IgnoreWhitespaceStringComparer would look like
class IgnoreWhitespaceStringComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Trim().Equals(y.Trim());
}
public int GetHashCode(string obj)
{
return obj.Trim().GetHashCode();
}
}
Just trim every entry in your list like so:
authorizedRolesList.ForEach(a => a = a.Trim());
just try like this remove whitespace when splitting string and use Trim()
List<string> authorizedRolesList = null;
string AuthorizedRolesValues = "A, B ,C,D";
if (!String.IsNullOrEmpty(AuthorizedRolesValues))
{
string[] separators = {","};
authorizedRolesList = new List<string>(
((AuthorizedRolesValues)
.Split(separators , StringSplitOptions.RemoveEmptyEntries))
.Select(x => x.Trim());
}
and after this do use Trim() like this in below code
//Contains Check
if (authorizedRolesList.Contains(userRoleName.Trim()))
{
isAuthorizedRole = true;
}
string authorizedRolesValues = "A, B ,C,D";
var authorizedRolesList = authorizedRolesValues
.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
.Select(role => role.Trim());
var userRoles = new Collection<string> {"B "};
bool isAuthorizedRole = userRoles
.Select(roleName => roleName.Trim())
.Any(authorizedRolesList.Contains);
How about trimming the original list also?
authorizedRolesList = new List<string>((AuthorizedRolesValues).Split(',').Select(x => x.Trim()));
try this
bool ifExists = userRoles.Any(AuthorizedRolesValues.Split(',').Select(val => val = val.trim());

Categories