FirstUnmatchedIndex using CurrentCultureIgnoreCase - c#

I need to support languages that can use non ascii letters in input text, so i need to implement StringComparison.CurrentCultureIgnoreCase for FirstUnmatchedIndex. Ignoring casing isn't so bad, but i don't know how to convert combined symbols to a standard representation and then compare. So here are a few cases where the function should return -1 but returns something else instead....
encyclopædia = encyclopaedia
Archæology = Archaeology
ARCHÆOLOGY = archaeology
Archæology = archaeology
Weißbier = WEISSBIER
How to know if a char needs to be expanded and convert each char to the expanded form when needed ?
/// <summary>
/// Gets a first different char occurence index
/// </summary>
/// <param name="a">First string</param>
/// <param name="b">Second string</param>
/// <param name="compareSmallest">
/// If true, Returns the first difference found or -1 if the end of a string is reached without finding a difference.
/// IE, Return -1 if the smallest string is contained in the other.
/// Otherwise returns -1 only if both string are really the same and will return the position where the smallest string ends if no difference is found.
/// </param>
/// <returns>
/// Returns first difference index or -1 if no difference is found
/// </returns>
public static int FirstUnmatchedIndex(this string a, string b, bool compareSmallest = false, StringComparison comparisonType = StringComparison.CurrentCulture)
{
//Treat null as empty
if (String.IsNullOrEmpty(a)) {
if (String.IsNullOrEmpty(b)) {
//Equal, both empty.
return -1;
} else {
//If compareSmallest, empty is always found in longest.
//Otherwise, difference at pos 0.
return compareSmallest ? -1 : 0;
}
}
if (object.ReferenceEquals(a, b)) {
//Same Ref.
return -1;
}
//Convert strings before compare.
switch (comparisonType) {
case StringComparison.CurrentCulture:
//FIXME
break;
case StringComparison.CurrentCultureIgnoreCase:
//FIXME
var currentCulture = System.Globalization.CultureInfo.CurrentCulture;
a = a.ToLower(currentCulture);
b = b.ToLower(currentCulture);
break;
case StringComparison.InvariantCulture:
//FIXME
break;
case StringComparison.InvariantCultureIgnoreCase:
//FIXME
a = a.ToLowerInvariant();
b = b.ToLowerInvariant();
break;
case StringComparison.OrdinalIgnoreCase:
a = a.ToLower();
b = b.ToLower();
break;
case StringComparison.Ordinal:
//Ordinal(Binary) comprare, nothing special to do.
default:
break;
}
string longStr = a.Length > b.Length ? a : b;
string shortStr = a.Length > b.Length ? b : a;
int count = shortStr.Length;
for (int idx = 0; idx < count; idx++) {
//FIXME Check if char needs to be expanded ?
if (shortStr[idx] != longStr[idx]) {
return idx;
}
}
return compareSmallest || longStr.Length == count ? -1 : count;
}

I'm not sure if I understood your question correctly, but you can use "dictionary + regular expression" combination. The idea is to create dictionary with the characters you want to expand and find them with the help of regex. The following code shows example how to do it.
Explanation of regular expression:
(?i) - This enables case insensitive search (same as
RegexOptions.IgnoreCase, but in-line)
[^\p{IsBasicLatin}]+ -
This will search for all characters that don't fit basic Latin
characters set (from \u0000 through \u007F).
The code uses ToLower method which avoids adding upper-case non-Latin characters to dictionary. You can, of course, not to do it, if you want to be explicit (i.e. adding all both lower-case and upper-case characters into dictionary and removing ToLower).
var dic = new Dictionary<string, string>
{
["æ"] = "ae",
["ß"] = "ss"
};
var words = new[] { "encyclopædia", "Archæology", "ARCHÆOLOGY", "Archæology", "Weißbier" };
var pattern = #"(?i)[^\p{IsBasicLatin}]+";
int x = -1;
foreach(var word in words)
{
// Each match (m.Value) is passed to dictionary
words[++x] = Regex.Replace(word, pattern, m => dic[m.Value.ToLower()]);
}
words.ToList().ForEach(WriteLine);
/*
Output:
encyclopaedia
Archaeology
ARCHaeOLOGY
Archaeology
Weissbier
*/

Related

Remove set of elements in an char array

I have a char array where I have to remove the elements from the same if I find the certain char. For eg: "paragraphs" is assigned in an char array. I have given search keyword as 'g'. If it is found then I have to remodify the original char array as "raphs" by removing all elements including the found one.
char[] ch = "paragraphs"
search chr = 'g'
Desired result(if chr found):
char[] ch = "raphs"
To explain bit clearer
I have to write a func. to find whether str(user input) contains all the char of the word "abcdef" in the same sequence as specify in the "abcdef". Return True if contains all the char in the same sequence or else false.
User Input: fgajbdcmdgeoplfqz
Output: true
User Input: asrbcyufde
Output: false
You can use LINQ's SkipWhile, which will skip elements until the search character is found.
An additionnal Skip is necessary to obtain raphs instead of graphs, and the ToArray() for the input string and result because you want to work with arrays.
char[] ch = "paragraphs".ToArray();
char search = 'g';
ch = ch.SkipWhile(c => c != search).Skip(1).ToArray(); // raphs
But honestly since your input is a string, I'd work with that:
string ch = "paragraphs";
char search = 'g';
ch = ch.Substring(ch.IndexOf(search) + 1);
and, if really necessary, convert it with .ToArray() afterwards.
And now to answer your 'clarification' (which is pretty much an other question by the way).
There are probably better ways to do it, but here's something that will accomplish what you want in O(n)
private bool ContainsInSequence(string input, string substring)
{
int substringIndex = 0;
for (int i = 0; i < input.Count(); i++)
{
if (input[i] == substring[substringIndex])
{
substringIndex++;
if (substringIndex == substring.Length)
{
return true;
}
}
}
return false;
}
Basically, you go through the input string in order, and each time you encounter the current letter from your substring you move to the next one.
When you reach the end of the substring, you know the input string contained all your substring, so you return true.
If we're not at the end after going through all the input this means there was a letter either out of order or missing, so we return false.
ContainsInSequence("fgajbdcmdgeoplfqz", "abcdef"); // true
ContainsInSequence("asrbcyufde ", "abcdef"); // false
ContainsInSequence("abcdfe", "abcdef"); // false
Try
char[] ch = "paragraphs".ToCharArray();
int index = Array.IndexOf(ch, 'g');
char[] result = new string(ch).Substring(index+1).ToCharArray();
Here's my version without using Linq:
static void Main(string[] args)
{
Console.WriteLine("Please provide a list of characters");
string Charactersinput = Console.ReadLine();
Console.WriteLine("Please provide the search character");
char Searchinput = Console.ReadKey().KeyChar;
Console.WriteLine("");
List<char> ListOfCharacters = new List<char>();
//fill the list of characters with the characters from the string
// Or empty it, if th esearch charcter is found
for (int i = 0; i < Charactersinput .Length; i++)
{
if (Searchinput == Charactersinput[i])
{
ListOfCharacters.Clear();
}
else
ListOfCharacters.Add(Charactersinput [i]);
}
//get your string back together
string Result = String.Concat(ListOfCharacters);
Console.WriteLine("Here's a list of all characters after processing: {0}", Result);
Console.ReadLine();
}
To answer your "clarification" question, which is very different from the original question:
I have to write a func. to find whether str(user input) contains all
the char of the word "abcdef" in the same sequence as specify in the
"abcdef". Return true if contains all the char in the same sequence or
else false.
Input: fgajbdcmdgeoplfqz Output: true
Input: asrbcyufde Output: false
The following function takes in two strings, a source string to search and a string containing the sequence of characters to match. It then searches through the source string, looking for each character (starting at the found position of the previous character). If any character is not found, it returns false. Otherwise it returns true:
public static bool ContainsAllCharactersInSameSequence(string sourceString,
string characterSequence)
{
// Short-circuit argument check
if (sourceString == null) return characterSequence == null;
if (characterSequence == null) return false;
if (characterSequence.Length > sourceString.Length) return false;
if (sourceString == characterSequence) return true;
int startIndex = 0;
foreach (char character in characterSequence)
{
int charIndex = sourceString.IndexOf(character, startIndex);
if (charIndex == -1) return false;
startIndex = charIndex + 1;
}
return true;
}

Increment a string with both letters and numbers

I have a string which i need to increment by 1 The string has both characters and numeric values.
The string layout i have is as follows "MD00494"
How would i increment this to "MD00496" & "MD00497" ect
If it was a normal string with numbers i would parse it to an int.
I have tried the following
int i = int.Parse(sdesptchNo);
i++;
txtDispatchNo.Text = i.ToString();
Anyone any ideas how i would go about this.
You first should figure out any commonality between the strings. If there is always a prefix of letters followed by digits (with a fixed width) at the end, then you can just remove the letters, parse the rest, increment, and stick them together again.
E.g. in your case you could use something like the following:
var prefix = Regex.Match(sdesptchNo, "^\\D+").Value;
var number = Regex.Replace(sdesptchNo, "^\\D+", "");
var i = int.Parse(number) + 1;
var newString = prefix + i.ToString(new string('0', number.Length));
Another option that might be a little more robust might be
var newString = Regex.Replace(x, "\\d+",
m => (int.Parse(m.Value) + 1).ToString(new string('0', m.Value.Length)));
This would replace any number in the string by the incremented number in the same width – but leaves every non-number exactly the same and in the same place.
Here is one Non-Regex way :P
string str = "MD00494";
string digits = new string(str.Where(char.IsDigit).ToArray());
string letters = new string(str.Where(char.IsLetter).ToArray());
int number;
if (!int.TryParse(digits, out number)) //int.Parse would do the job since only digits are selected
{
Console.WriteLine("Something weired happened");
}
string newStr = letters + (++number).ToString("D5");
output would be:
newStr = "MD00495"
Assuming that you only need to increment the numeric portion of the string, and that the structure of the strings is always - bunch of non-numeric characters followed by a bunch of numerals, you can use a regular expression to break up the string into these two components, convert the numeric portion to an integer, increment and then concatenate back.
var match = Regex.Match("MD123", #"^([^0-9]+)([0-9]+)$");
var num = int.Parse(match.Groups[2].Value);
var after = match.Groups[1].Value + (num + 1);
You need to find the position of the first digit in the string.
Then split the string into 2 fields.
0 1 2 3 4 5 6
M D 0 0 4 9 4
The first field will be the non numeric part "MD"
The second field will be the numeric part "00494"
Increment the numeric only part to "00495"
You will lose the leading zero's so you'll need to pad your new number with the same amount of zero's once you've incremented.
Then join the 2 fields.
The accepted answer does not work if there is a number in the middle of the string e.g. XXX123YYY456, exceptions are thrown.
I have written a generic method that will increment the end of the string and you can pass it the minimum amount of digits.
public static string IncrementStringEnd(string name, int minNumericalCharacters = 1)
{
var prefix = System.Text.RegularExpressions.Regex.Match(name, #"\d+$");
if (prefix.Success)
{
var capture = prefix.Captures[0];
int number = int.Parse(capture.Value) + 1;
name = name.Remove(capture.Index, capture.Length) + number.ToString("D" + minNumericalCharacters);
}
return name;
}
Test Results:
MD00494 : MD00495
XXX123YYY456 : XXX123YYY457
SD50MRF999 : SD50MRF1000
SD50MRF9 : SD50MRF010
For testing purposes https://dotnetfiddle.net/j1f6wh
You can use regex:
int kod = int.Parse(Regex.Replace(sdesptchNo, "[^0-9]", "")) + 1;
string zeroStr=Regex.Replace(sdesptchNo, "[^0-9]", "");
string newZeroStr="";
for (int x=0;x<zeroStr.length;x++)
if (zeroStr[x]=='0') newZeroStr=newZeroStr+"0";
else break;
string newVal=Regex.Replace(sdesptchNo, "[0-9]", "") + newZeroStr + kod;
UPDATED: This will save your zero
string sDispatchNo = "MS00914";
var pattern = #"^[a-zA-Z]+";
var strPart = Regex.Match(sDispatchNo, pattern).Value;
var noPart = Regex.Replace(sDispatchNo, pattern, "");
var no = int.Parse(noPart);
var length = noPart.Length;
length = (no + 1)/(Math.Pow(10,length)) == 1 ? length + 1 : length;
var output = strPart + (no + 1).ToString("D" + length);
Here's my solution:
string str = Console.ReadLine();
string digits = new string(str.Where(char.IsDigit).ToArray());
string letters = new string(str.Where(char.IsLetter).ToArray());
string newStr;
int number;
if (!int.TryParse(digits, out number))
{
Console.WriteLine("Something weird happened");
}
if (digits.StartsWith("0"))
{
newStr = letters + (++number).ToString("D5");
}
else
{
newStr = letters + (++number).ToString();
}
Try it!
I use this to Increment/Decrement Barcodes
/// <summary>
/// Gets the number portion of the string and adds 1 to it
/// </summary>
public static string IncrementNumbers(this string numString)
{
if (numString.IsEmpty())
return numString;
else if (!numString.Where(Char.IsDigit).Any())
return numString;
else
{
string prefix = Regex.Match(numString, "^\\D+").Value;
string number = Regex.Replace(numString, "^\\D+", "");
int i = int.Parse(number) + 1;
return prefix + i.ToString($"D{numString.Length - prefix.Length}");
}
}
/// <summary>
/// Gets the number portion of the string and subtracts 1 from it
/// </summary>
public static string DecrementNumbers(this string numString)
{
if (numString.IsEmpty())
return numString;
else if (!numString.Where(Char.IsDigit).Any())
return numString;
else
{
string prefix = Regex.Match(numString, "^\\D+").Value;
string number = Regex.Replace(numString, "^\\D+", "");
int i = int.Parse(number) - 1;
return prefix + i.ToString($"D{numString.Length - prefix.Length}");
}
}
/// <summary>
/// Shortented IsNullOrWhiteSpace
/// </summary>
public static bool IsEmpty(this string str)
{
if (str.TrimFix() == null)
return true;
return false;
}
/// <summary>
/// Trims the String and returns Null if it's empty space
/// </summary>
public static string TrimFix(this string rawString)
{
if (!string.IsNullOrWhiteSpace(rawString))
{
return rawString.Trim();
}
return null;
}

Parsing strings recursively

I am trying to extract information out of a string - a fortran formatting string to be specific. The string is formatted like:
F8.3, I5, 3(5X, 2(A20,F10.3)), 'XXX'
with formatting fields delimited by "," and formatting groups inside brackets, with the number in front of the brackets indicating how many consecutive times the formatting pattern is repeated. So, the string above expands to:
F8.3, I5, 5X, A20,F10.3, A20,F10.3, 5X, A20,F10.3, A20,F10.3, 5X, A20,F10.3, A20,F10.3, 'XXX'
I am trying to make something in C# that will expand a string that conforms to that pattern. I have started going about it with lots of switch and if statements, but am wondering if I am not going about it the wrong way?
I was basically wondering if some Regex wizzard thinks that Regular expressions can do this in one neat-fell swoop? I know nothing about regular expressions, but if this could solve my problem I am considering putting in some time to learn how to use them... on the other hand if regular expressions can't sort this out then I'd rather spend my time looking at another method.
This has to be doable with Regex :)
I've expanded my previous example and it test nicely with your example.
// regex to match the inner most patterns of n(X) and capture the values of n and X.
private static readonly Regex matcher = new Regex(#"(\d+)\(([^(]*?)\)", RegexOptions.None);
// create new string by repeating X n times, separated with ','
private static string Join(Match m)
{
var n = Convert.ToInt32(m.Groups[1].Value); // get value of n
var x = m.Groups[2].Value; // get value of X
return String.Join(",", Enumerable.Repeat(x, n));
}
// expand the string by recursively replacing the innermost values of n(X).
private static string Expand(string text)
{
var s = matcher.Replace(text, Join);
return (matcher.IsMatch(s)) ? Expand(s) : s;
}
// parse a string for occurenses of n(X) pattern and expand then.
// return the string as a tokenized array.
public static string[] Parse(string text)
{
// Check that the number of parantheses is even.
if (text.Sum(c => (c == '(' || c == ')') ? 1 : 0) % 2 == 1)
throw new ArgumentException("The string contains an odd number of parantheses.");
return Expand(text).Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
I would suggest using a recusive method like the example below( not tested ):
ResultData Parse(String value, ref Int32 index)
{
ResultData result = new ResultData();
Index startIndex = index; // Used to get substrings
while (index < value.Length)
{
Char current = value[index];
if (current == '(')
{
index++;
result.Add(Parse(value, ref index));
startIndex = index;
continue;
}
if (current == ')')
{
// Push last result
index++;
return result;
}
// Process all other chars here
}
// We can't find the closing bracket
throw new Exception("String is not valid");
}
You maybe need to modify some parts of the code, but this method have i used when writing a simple compiler. Although it's not completed, just a example.
Personally, I would suggest using a recursive function instead. Every time you hit an opening parenthesis, call the function again to parse that part. I'm not sure if you can use a regex to match a recursive data structure.
(Edit: Removed incorrect regex)
Ended up rewriting this today. It turns out that this can be done in one single method:
private static string ExpandBrackets(string Format)
{
int maxLevel = CountNesting(Format);
for (int currentLevel = maxLevel; currentLevel > 0; currentLevel--)
{
int level = 0;
int start = 0;
int end = 0;
for (int i = 0; i < Format.Length; i++)
{
char thisChar = Format[i];
switch (Format[i])
{
case '(':
level++;
if (level == currentLevel)
{
string group = string.Empty;
int repeat = 0;
/// Isolate the number of repeats if any
/// If there are 0 repeats the set to 1 so group will be replaced by itself with the brackets removed
for (int j = i - 1; j >= 0; j--)
{
char c = Format[j];
if (c == ',')
{
start = j + 1;
break;
}
if (char.IsDigit(c))
repeat = int.Parse(c + (repeat != 0 ? repeat.ToString() : string.Empty));
else
throw new Exception("Non-numeric character " + c + " found in front of the brackets");
}
if (repeat == 0)
repeat = 1;
/// Isolate the format group
/// Parse until the first closing bracket. Level is decremented as this effectively takes us down one level
for (int j = i + 1; j < Format.Length; j++)
{
char c = Format[j];
if (c == ')')
{
level--;
end = j;
break;
}
group += c;
}
/// Substitute the expanded group for the original group in the format string
/// If the group is empty then just remove it from the string
if (string.IsNullOrEmpty(group))
{
Format = Format.Remove(start - 1, end - start + 2);
i = start;
}
else
{
string repeatedGroup = RepeatString(group, repeat);
Format = Format.Remove(start, end - start + 1).Insert(start, repeatedGroup);
i = start + repeatedGroup.Length - 1;
}
}
break;
case ')':
level--;
break;
}
}
}
return Format;
}
CountNesting() returns the highest level of bracket nesting in the format statement, but could be passed in as a parameter to the method. RepeatString() just repeats a string the specified number of times and substitutes it for the bracketed group in the format string.

Clever way to append 's' for plural form in .Net (syntactic sugar)

I want to be able to type something like:
Console.WriteLine("You have {0:life/lives} left.", player.Lives);
instead of
Console.WriteLine("You have {0} {1} left.", player.Lives, player.Lives == 1 ? "life" : "lives");
so that for player.Lives == 1 the output would be: You have 1 life left.
for player.Lives != 1 : You have 5 lives left.
or
Console.WriteLine("{0:day[s]} till doomsday.", tillDoomsdayTimeSpan);
Some systems have that built-in. How close can I get to that notation in C#?
EDIT: Yes, I am specifically looking for syntactic sugar, and not a method to determine what singular/plural forms are.
You may checkout the PluralizationService class which is part of the .NET 4.0 framework:
string lives = "life";
if (player.Lives != 1)
{
lives = PluralizationService
.CreateService(new CultureInfo("en-US"))
.Pluralize(lives);
}
Console.WriteLine("You have {0} {1} left", player.Lives, lives);
It is worth noting that only English is supported for the moment. Warning, this don't work on the Net Framework 4.0 Client Profile!
You could also write an extension method:
public static string Pluralize(this string value, int count)
{
if (count == 1)
{
return value;
}
return PluralizationService
.CreateService(new CultureInfo("en-US"))
.Pluralize(value);
}
And then:
Console.WriteLine(
"You have {0} {1} left", player.Lives, "life".Pluralize(player.Lives)
);
You can create a custom formatter that does that:
public class PluralFormatProvider : IFormatProvider, ICustomFormatter {
public object GetFormat(Type formatType) {
return this;
}
public string Format(string format, object arg, IFormatProvider formatProvider) {
string[] forms = format.Split(';');
int value = (int)arg;
int form = value == 1 ? 0 : 1;
return value.ToString() + " " + forms[form];
}
}
The Console.WriteLine method has no overload that takes a custom formatter, so you have to use String.Format:
Console.WriteLine(String.Format(
new PluralFormatProvider(),
"You have {0:life;lives} left, {1:apple;apples} and {2:eye;eyes}.",
1, 0, 2)
);
Output:
You have 1 life left, 0 apples and 2 eyes.
Note: This is the bare minimum to make a formatter work, so it doesn't handle any other formats or data types. Ideally it would detect the format and data type, and pass the formatting on to a default formatter if there is some other formatting or data types in the string.
With the newfangled interpolated strings, I just use something like this:
// n is the number of connection attempts
Console.WriteLine($"Needed {n} attempt{(n!=1 ? "s" : "")} to connect...");
EDIT: just ran across this answer again--since I originally posted this, I've been using an extension method that makes it even easier. This example is ordered by peculiarity:
static class Extensions {
/// <summary>
/// Pluralize: takes a word, inserts a number in front, and makes the word plural if the number is not exactly 1.
/// </summary>
/// <example>"{n.Pluralize("maid")} a-milking</example>
/// <param name="word">The word to make plural</param>
/// <param name="number">The number of objects</param>
/// <param name="pluralSuffix">An optional suffix; "s" is the default.</param>
/// <param name="singularSuffix">An optional suffix if the count is 1; "" is the default.</param>
/// <returns>Formatted string: "number word[suffix]", pluralSuffix (default "s") only added if the number is not 1, otherwise singularSuffix (default "") added</returns>
internal static string Pluralize(this int number, string word, string pluralSuffix = "s", string singularSuffix = "")
{
return $#"{number} {word}{(number != 1 ? pluralSuffix : singularSuffix)}";
}
}
void Main()
{
int lords = 0;
int partridges = 1;
int geese = 1;
int ladies = 8;
Console.WriteLine($#"Have {lords.Pluralize("lord")}, {partridges.Pluralize("partridge")}, {ladies.Pluralize("lad", "ies", "y")}, and {geese.Pluralize("", "geese", "goose")}");
lords = 1;
partridges = 2;
geese = 6;
ladies = 1;
Console.WriteLine($#"Have {lords.Pluralize("lord")}, {partridges.Pluralize("partridge")}, {ladies.Pluralize("lad", "ies", "y")}, and {geese.Pluralize("", "geese", "goose")}");
}
(formats are the same). The output is:
Have 0 lords, 1 partridge, 8 ladies, and 1 goose
Have 1 lord, 2 partridges, 1 lady, and 6 geese
using #Darin Dimitrov solution, I would create an extention for string ....
public static Extentions
{
public static string Pluralize(this string str,int n)
{
if ( n != 1 )
return PluralizationService.CreateService(new CultureInfo("en-US"))
.Pluralize(str);
return str;
}
}
string.format("you have {0} {1} remaining",liveCount,"life".Pluralize());
string message = string.format("You have {0} left.", player.Lives == 1 ? "life" : "lives");
Of course this assumes that you have a finite number of values to pluralize.
I wrote an open-source library called SmartFormat that does exactly that! It's written in C# and is on GitHub:
http://github.com/scottrippey/SmartFormat
Although it supports several languages, English "plural rules" are the default. Here's the syntax:
var output = Smart.Format("You have {0} {0:life:lives} left.", player.Lives);
It also supports "zero" quantity, and nested placeholders, so you could do:
var output = Smart.Format("You have {0:no lives:1 life:{0} lives} left.", player.Lives);
See the Inflector class that is part of Castle ActiveRecord. It is licensed under the Apache license.
It has a set of regular expression rules that define how words are pluralized. The version I have used has some errors in these rules though, e.g. it has a 'virus' → 'virii' rule.
I have three extension methods which wrap Inflector, the first of which may be right up your street:
/// <summary>
/// Pluralises the singular form word specified.
/// </summary>
/// <param name="this">The singular form.</param>
/// <param name="count">The count.</param>
/// <returns>The word, pluralised if necessary.</returns>
public static string Pluralise(this string #this, long count)
{
return (count == 1) ? #this :
Pluralise(#this);
}
/// <summary>
/// Pluralises the singular form word specified.
/// </summary>
/// <param name="this">The singular form word.</param>
/// <returns>The plural form.</returns>
public static string Pluralise(this string #this)
{
return Inflector.Pluralize(#this);
}
/// <summary>
/// Singularises the plural form word.
/// </summary>
/// <param name="this">The plural form word.</param>
/// <returns>Th singular form.</returns>
public static string Singularise(this string #this)
{
return Inflector.Singularize(#this);
}
For C# 6.0 onwards, you can use Interpolated Strings to do this tricks.
Example:
Console.WriteLine("\n --- For REGULAR NOUNS --- \n");
{
int count1 = 1;
Console.WriteLine($"I have {count1} apple{(count1 == 1 ? "" : "s")}.");
int count2 = 5;
Console.WriteLine($"I have {count2} apple{(count2 == 1 ? "" : "s")}.");
}
Console.WriteLine("\n --- For IRREGULAR NOUNS --- \n");
{
int count1 = 1;
Console.WriteLine($"He has {count1} {(count1 == 1 ? "leaf" : "leaves")}.");
int count2 = 5;
Console.WriteLine($"He has {count2} {(count2 == 1 ? "leaf" : "leaves")}.");
}
Output:
--- For REGULAR NOUNS ---
I have 1 apple.
I have 5 apples.
--- For IRREGULAR NOUNS ---
He has 1 leaf.
He has 5 leaves.
You can play around on my .NET Fiddle.
For more details, go to Interpolated String documentation.
I'm thinking the easiest way to do it is to create an Interface IPlural which has an method .ToString(int quantity) which returns the singular form when quantity == 1 an the plural form all other times.
I am using this extension method with .NET 4.6
public static string Pluralize(this string #string)
{
if (string.IsNullOrEmpty(#string)) return string.Empty;
var service = new EnglishPluralizationService();
return service.Pluralize(#string);
}
I did a little bit of work with PluralizationService and came up with. I just made the PluralizationService static for performance and combined all.
Reference System.Data.Entity.Design
using System.Data.Entity.Design.PluralizationServices;
using System.Reflection;
public static class Strings
{
private static PluralizationService pluralizationService = PluralizationService.CreateService(System.Globalization.CultureInfo.CurrentUICulture);
public static string Pluralize(this MemberInfo memberInfo)//types, propertyinfos, ect
{
return Pluralize(memberInfo.Name.StripEnd());
}
public static string Pluralize(this string name)
{
return pluralizationService.Pluralize(name); // remove EF type suffix, if any
}
public static string StripEnd(this string name)
{
return name.Split('_')[0];
}
}
If your application is English on you can use all the solutions shown here. However if you plan to localize the application the plural enabled message must be done in a proper way. This means that you may need multiple patterns (from 1 to 6) depending on the language and the rule to choose what the pattern is used depends on the language.
For example in English you would have two patterns
"You have {0} live left"
"You have {0} lives left"
Then you would have a Format function where you pass these two patterns with the liveAmount variable.
Format("You have {0} live left", "You have {0} lives left", liveAmount);
In a real application you would not hard code the string but would use resource strings.
Format would know what the active language is and if English it would use
if (count == 1)
useSingularPattern
else
usePluralPattern
To implement this yourself is a bit complex bu you don't have to. You can you an open source project I have made
https://github.com/jaska45/I18N
By using it you can easily get the string
var str = MultiPattern.Format("one;You have {0} live left;other;You have {0} lives left", liveAmount);
That's it. The library knows what pattern to use depending on the passed liveAmount paramter. The rules have been extracted from CLDR into library .cs file.
If you want to localize the application you just put the multi pattern string into .resx and let the translator to translate it. Depending on the target language the multi pattern string might contains 1, 2, 3, 4, 5 or 6 patterns.
A bit late to the party, but I wrote a library called MessageFormat.NET that handles this.
var str = #"You have {lives, plural,
zero {no lives}
one {one life}
other {# lives}
} left.";
var result = MessageFormatter.Format(str, new {
lives = 1337
});
The whitespace in the string surrounding the text is not required, but merely for readability.
This is great when translating, as languages have different rules when it comes to pluralization.
Looking at how strings are typically written that account for simultaneous single and multiple values, e.g.
"Expected {0} file(s), but found {1} file(s)."
The approach I took was to keep the same string, but add some parsing to determine whether the (s) should be removed completely, or to keep the s inside the round brackets. For irregular words, a slash can separate the singular and plural form.
Other examples:
"Had {0:n2} child(ren)".Pluralize(1.0) => "Had 1.00 child"
"Had {0} cherry(ies)".Pluralize(2) => "Had 2 cherries"
"Had {0} calf/calves".Pluralize(1) => "Had 1 calf"
"Had {0} son(s)-in-law".Pluralize(2) => "Had 2 sons-in-law"
"Had {0} able seaman/seamen".Pluralize(1) => "Had 1 able seaman"
"Had {0} sheep, {1} goat(s)".Pluralize(1, 2) => "Had 1 sheep, 2 goats"
///<summary>
/// Examples:
/// "{0} file(s)".Pluralize(1); -> "1 file"
/// "{0} file(s)".Pluralize(2); -> "2 files"
///</summary>
public static String Pluralize(this String s, params Object[] counts) {
String[] arr = s.Split(new [] { ' ' }, StringSplitOptions.None);
for (int i = 0; i < arr.Length; i++) {
String t = arr[i];
if (t.Length == 0 || t[0] != '{' || t[t.Length - 1] != '}')
continue;
int w = 1;
while (w < t.Length) {
char c = t[w];
if (c < '0' || c > '9')
break;
w++;
}
if (w == 1)
continue;
int n = int.Parse(t.Substring(1, w-1));
if (n >= counts.Length)
continue;
Object o = counts[n];
if (o == null)
continue;
bool isSingle = false;
if (o is int)
isSingle = 1 == (int) o;
else if (o is double)
isSingle = 1 == (double) o;
else if (o is float)
isSingle = 1 == (float) o;
else if (o is decimal)
isSingle = 1 == (decimal) o;
else if (o is byte)
isSingle = 1 == (byte) o;
else if (o is sbyte)
isSingle = 1 == (sbyte) o;
else if (o is short)
isSingle = 1 == (short) o;
else if (o is ushort)
isSingle = 1 == (ushort) o;
else if (o is uint)
isSingle = 1 == (uint) o;
else if (o is long)
isSingle = 1 == (long) o;
else if (o is ulong)
isSingle = 1 == (ulong) o;
else
continue;
for (int j = i + 1; j < arr.Length && j < i + 4; j++) {
String u = arr[j];
if (u.IndexOf('{') >= 0)
break; // couldn't find plural word and ran into next token
int b1 = u.IndexOf('(');
int b2 = u.IndexOf(')', b1 + 1);
if (b1 >= 0 && b2 >= 0) {
String u1 = u.Substring(0, b1);
String u2 = u.Substring(b2+1);
char last = (u1.Length > 0 ? u1[u1.Length - 1] : ' ');
String v = (isSingle ? "" : u.Substring(b1+1, (b2 - b1) - 1));
if ((last == 'y' || last == 'Y') && String.Compare(v, "ies", true) == 0)
u1 = u1.TrimEnd('y', 'Y');
arr[j] = u1 + v + u2;
break;
}
int s1 = u.IndexOf('/');
if (s1 >= 0) {
arr[j] = (isSingle ? u.Substring(0, s1) : u.Substring(s1 + 1));
break;
}
}
}
s = String.Join(" ", arr);
s = String.Format(s, counts);
return s;
}

which side has more characters

string a = "asdf xyz 123 xx 3212";
string seperator = "z 1";
need to write a script that returns 0 if left of the seperator has more characters, otherwise return 1
in this case it should return 1
quick and dirty:
private int YourMethod(string a, string separator)
{
if (a.IndexOf(seperator) > 0)
{
if ((a.Length - seperator.Length) / 2 > a.IndexOf(seperator))
return 1;
else
return 0;
}
return 0;
}
I hope this satisfies your requirements.
/// <summary>
/// Determines whether a string has more characters to the left of the separator.
/// </summary>
/// <param name="a">An arbitrary string, possibly delimited into two parts.</param>
/// <param name="separator">The characters that partition the string.</param>
/// <returns>0 if left of the separator has more characters, otherwise returns 1.</returns>
/// <exception cref="ArgumentException">No separator was supplied.</exception>
public static int MoreCharactersLeftOfSeparator(string a, string separator)
{
if (string.IsNullOrEmpty(separator))
throw new ArgumentException("No separator was supplied.", "separator");
if (a == null)
return 1;
int separatorIndex = a.LastIndexOf(separator, StringComparison.Ordinal);
if (separatorIndex == -1)
return 1;
int charactersRight = a.Length - separatorIndex - separator.Length;
if (charactersRight >= separatorIndex)
return 1;
return 0;
}
int left = a.IndexOf(separator);
if (left < 0)
return -1; //no separator?
int right = a.Length - (left + 1 + separator.Length);
if (left > right)
return 0;
else
return 1;
public static LeftOrRightString(string s, string separator)
{
var strArr = s.Split(separator);
if (strArr.Count != 2)
throw new Exception();
return strArr[0].Length < strArr[1].Length;
}

Categories