ArgumentOutOfRangeException: extracting string from string - c#

I have a method that extracts a username from a string using conditionals to check common conventions, although it is resulting in an ArgumentOutOfRangeException on the GetPart utility method, even after explicitly checking before calling it?
Here is the extraction method
public bool TryExtractUsernameFromString(string str, out string username)
{
if (str.Contains("un: "))
{
username = GetPart(str, "un: ", " ");
}
else if (str.Contains("un:"))
{
username = str.Split(" ").Where(x => x.StartsWith("un:")).First().Substring(3);
}
else if (str.Contains("un- "))
{
username = str.IndexOf(" ", str.IndexOf("un- ") + 1) > 0 ? GetPart(str, "un- ", " ") : str[str.IndexOf("un- ")..str.Length];
}
else if (str.Contains("un-"))
{
username = str.Split(" ").Where(x => x.StartsWith("un-")).First().Substring(3);
}
else
{
username = "";
}
return username.Length > 0;
}
I am passing this as the first argument to TryExtractUsernameFromString (without quotes)
"😊un- jennyfromtheblock"
So it happens here,
else if (str.Contains("un- "))
{
username = str.IndexOf(" ", (str.IndexOf("un- ") + 1)) > 0 ? GetPart(str, "un- ", " ") : str[str.IndexOf("un -")..str.Length];
}
But shouldn't be calling GetPart() if it doesn't contain a second space after the first one in the str.Contains check.
GetPart method:
public static string GetPart(string s, string start, string end)
{
return s[(s.IndexOf(start) + start.Length)..s.IndexOf(end)];
}

str.IndexOf("un- ") + 1 is returning the index of the START + 1 of that substring. Try using str.IndexOf("un- ") + 4 instead. That'll get you the index of the second space you're looking for.

#DanRayson looks correct; But I wanted to add there is likely a much cleaner approach to this.
If statements can suck, case statements aren't really better. If you assume any name could have 0 or more matches:
public static void CleanName(string nameString, List<string> badPrefixes)
{
var matchedPrefixes = badPrefixes.Where(w => nameString.Contains(w)
&& && nameString.IndexOf(w) == 0).ToList();
foreach(var prefix in matchedPrefixes)
{
Console.WriteLine(nameString.Replace(prefix, "").Trim());
}
if (!matchedPrefixes.Any())
{
Console.WriteLine(nameString);
}
}
Another option would be using .FirstOrDefault instead of selecting all of the matches. But essentially, just find the match(es) and then remove it, and finally trim spaces.
Example
public static void Main()
{
List<string> badPrefixes = new List<string>()
{
"un:",
"un-",
"un ",
"Un", //Fun example too
};
string longUserName1 = "un- Austin";
string riskyLongName = "un: theUndying";
CleanName(longUserName1, badPrefixes);
// output: Austin
CleanName(riskyLongName, badPrefixes);
// output: theUndying
}

Related

How to get string, wich is after a certain string in a text [duplicate]

i'm having a string in c# for which i have to find a specific word "code" in the string and have to get the remaining string after the word "code".
The string is
"Error description, code : -1"
so i have to find the word code in the above string and i have to get the error code.
I have seen regex but now clearly understood. Is there any simple way ?
string toBeSearched = "code : ";
string code = myString.Substring(myString.IndexOf(toBeSearched) + toBeSearched.Length);
Something like this?
Perhaps you should handle the case of missing code :...
string toBeSearched = "code : ";
int ix = myString.IndexOf(toBeSearched);
if (ix != -1)
{
string code = myString.Substring(ix + toBeSearched.Length);
// do something here
}
var code = myString.Split(new [] {"code"}, StringSplitOptions.None)[1];
// code = " : -1"
You can tweak the string to split by - if you use "code : ", the second member of the returned array ([1]) will contain "-1", using your example.
Simpler way (if your only keyword is "code" ) may be:
string ErrorCode = yourString.Split(new string[]{"code"}, StringSplitOptions.None).Last();
add this code to your project
public static class Extension {
public static string TextAfter(this string value ,string search) {
return value.Substring(value.IndexOf(search) + search.Length);
}
}
then use
"code : string text ".TextAfter(":")
use indexOf() function
string s = "Error description, code : -1";
int index = s.indexOf("code");
if(index != -1)
{
//DO YOUR LOGIC
string errorCode = s.Substring(index+4);
}
string founded = FindStringTakeX("UID: 994zxfa6q", "UID:", 9);
string FindStringTakeX(string strValue,string findKey,int take,bool ignoreWhiteSpace = true)
{
int index = strValue.IndexOf(findKey) + findKey.Length;
if (index >= 0)
{
if (ignoreWhiteSpace)
{
while (strValue[index].ToString() == " ")
{
index++;
}
}
if(strValue.Length >= index + take)
{
string result = strValue.Substring(index, take);
return result;
}
}
return string.Empty;
}
string originalSting = "This is my string";
string texttobesearched = "my";
string dataAfterTextTobeSearch= finalCommand.Split(new string[] { texttobesearched }, StringSplitOptions.None).Last();
if(dataAfterTextobeSearch!=originalSting)
{
//your action here if data is found
}
else
{
//action if the data being searched was not found
}

How to parse a .txt file with uncommon delimiters in C#

I am currently trying to parse a .txt file containing information listed like this: name/ID number/email/GPA. Here are a few lines showing what the text file looks like.
(LIST (LIST 'Doe 'Jane 'F ) '8888675309 'jfdoe#mail.university.edu 2.3073320999676614 )
(LIST (LIST 'Doe 'John 'F ) 'NONE 'johnfdoe#mail.university.edu 3.1915725161177115 )
(LIST (LIST 'Doe 'Jim 'F ) '8885551234 'jimdoe#mail.university.edu 3.448215586562192 )
In my current code all I am doing is printing the text file line by line to a console window.
static void Main(string[] args)
{
StreamReader inFile;
string inLine;
if (File.Exists("Students.txt"))
{
try
{
inFile = new StreamReader("Students.txt");
while ((inLine = inFile.ReadLine()) != null)
{
Console.WriteLine(inLine);
}
}
catch (System.IO.IOException exc)
{
Console.WriteLine("Error");
}
Console.ReadLine();
}
}
I need to able to, for example, find all the students that have a GPA above 3.0 and print their name and GPA to another text file. I understand how to print to another file, however, I am unsure how to access the individual columns, such as the GPA, since this file does not seem to have any common delimiters that would make using a Split() practical. Any help or insight on how to accomplish this would be appreciated.
IMPORTANT
I considered that the provided string in your question has a fixed format as shown.
IMPLEMENTATION
First, you need to create a class that is blueprint of the information you are getting from the string. It will give you a container to hold a meaningful information about the data.
public class StudentInfo
{
public string Name { get; set; }
public string Number { get; set; }
public string Email { get; set; }
public double GPA { get; set; }
}
Following is an example how to parse the string (string from your question) and convert it to relative information. I assume that you can read/write files in C#.
This sample demonstrates parsing and storing iformation in List. You can further use this to write files.
In you code, you are reading lines and that is why in this sample, I tried to read lines from string so you can understand it better.
I created this sample in C# Console application.
static void Main(string[] args)
{
List<StudentInfo> studentInfo = new List<StudentInfo>();
string input = "(LIST(LIST 'Abbott 'Ashley 'J ) '8697387888 'ajabbott#mail.university.edu 2.3073320999676614 )" + Environment.NewLine +
"(LIST(LIST 'Abbott 'Bradley 'M ) 'NONE 'bmabbott#mail.university.edu 3.1915725161177115 )" + Environment.NewLine +
"(LIST(LIST 'Abbott 'Ryan 'T ) '8698689793 'rtabbott#mail.university.edu 3.448215586562192 )";
string[] lines = input.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
if (lines != null && lines.Count() > 0)
{
foreach (var line in lines)
{
var data = line.Replace("(LIST(LIST ", string.Empty)
.Replace(")", string.Empty)
.Replace("'", string.Empty)
.Trim()
.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (data != null && data.Count() > 0)
{
studentInfo.Add(
new StudentInfo()
{
Name = data[0] + " " + data[1] + " " + data[2],
Number = data[3],
Email = data[4],
GPA = Convert.ToDouble(data[5])
});
}
}
}
// GET STUDENTS WHO GOT GPA > 3 (LINQ QUERY)
if (studentInfo.Count > 0)
{
var gpaGreaterThan3 = studentInfo.Where(s => s.GPA >= 3).Select(s => s).ToList();
if (gpaGreaterThan3 != null && gpaGreaterThan3.Count > 0)
{
// LOOP gpaGreaterThan3 TO PRINT STUDENT DATA
foreach (var stud in gpaGreaterThan3)
{
Console.WriteLine("Name: " + stud.Name);
Console.WriteLine("Number: " + stud.Number);
Console.WriteLine("Email: " + stud.Email);
Console.WriteLine("GPA: " + stud.GPA);
Console.WriteLine(string.Empty);
}
}
}
Console.ReadLine();
}
Try this:
var data = inLine.Replace("(LIST(LIST ", string.Empty)
.Replace(")", string.Empty)
.Replace("'", string.Empty)
.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
There are many ways to go about this, but most importantly you need to consider variations to the string format that might trip up any of the approaches
is gpa field always present and at the end?
will it have a defined identifiable format etc
Can there be more than one and if so which one would you pick etc
Below are a couple of approaches with assumptions. You would have to adjust the code per your assumption and how critical this piece of code would be.
// split on both space and closing bracket
// Assumption: GPA field is present and at the end
Console.WriteLine(line.Split(new[] { " ", ")" }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault());
// regex for gpa defined as digit followed by literal . followed by one or more digits
// Assumption: GPA field is present once somewhere in the string.
// No other token conflicts with similar format
var gpaRegex = new Regex(#"\d\.\d+");
Console.WriteLine(gpaRegex.Matches(line)[0]);
See https://dotnetfiddle.net/6Xy0uW for working example
See https://regex101.com/r/P1D7zf/1 for the regex in action where you might try more strict variations

Create user-readable, display-friendly formatted string from List<string>

I know several questions have been asked and answered about how to create a comma delimited string from a list. I'm looking for help on something slightly different.
What I would like to do is create a display-friendly human-readable string from a List<string> that reads like "A, B and C are invalid values". The grammar and format of the string should change based on the number of items in the list. The list could contain any number of items.
For example:
List<string> myString = new List<string>() { "Scooby", "Dooby", "Doo" };
// Should return "Scooby, Dooby and Doo are invalid values."
List<string> myString = new List<string>() { "Scooby", "Dooby" };
// Should return "Scooby and Dooby are invalid values."
List<string> myString = new List<string>() { "Scooby" };
// Should return "Scooby is an invalid value."
Here's what I've done so far:
string userMessage = "";
foreach(string invalidValue in invalidValues)
{
userMessage = " " + userMessage + invalidValue + ",";
}
// Remove the trailing comma
userMessage = userMessage.Substring(0, userMessage.LastIndexOf(','));
if (invalidValues.Count > 1)
{
int lastCommaLocation = userMessage.LastIndexOf(',');
userMessage = userMessage.Substring(0, lastCommaLocation) + " and " + userMessage.Substring(lastCommaLocation + 1) + " are invalid values.";
}
else
{
userMessage = userMessage + " is an invalid value.";
}
Is there a better or more efficient way to do this?
public static string FormatList(List<string> invalidItems)
{
if(invalidItems.Count == 0) return string.Empty;
else if(invalidItems.Count == 1) return string.Format("{0} is an invalid value", invalidItems[0]);
else return string.Format("{0} and {1} are invalid values", string.Join(", ", invalidItems.Take(invalidItems.Count - 1)), invalidItems.Last());
}
Heavily indebted to #Lee in his other answer, here's a much-expanded version of his method. It allows you to specify a beginning text and/or an ending text, and it uses the Oxford comma, the omission of which, as we all know, once had catastrophic results for the plaintiff in a lawsuit... but if you hate the darn thing you can just remove it from the method as written.
public static string GrammaticallyCorrectStringFrom(List<string> items, string prefaceTextWithNoSpaceAtEnd, string endingTextWithoutPeriod)
{
var returnString = string.Empty;
if (items.Count != 0)
{
returnString = prefaceTextWithNoSpaceAtEnd + " ";
}
if (items.Count == 1)
{
returnString += string.Format("{0}", items[0]);
}
else if (items.Count == 2)
{
returnString += items[0] + " and " + items[1];
}
else if (items.Count > 2)
{
//remove the comma in the string.Format part if you're an anti-Oxford type
returnString += string.Format("{0}, and {1}", string.Join(", ", items.Take(items.Count - 1)), items.Last());
}
if (String.IsNullOrEmpty(returnString) == false)
{
returnString += endingTextWithoutPeriod + ".";
}
return returnString;
}
There's probably a better, less-verbose way to do the same thing, but this is working for me, and it's hard to argue with results. Actually I guess it isn't, here, but still... Anyway, anyone with a fire to improve this is welcome to.

How to mask string?

I have a string with value "1131200001103".
How can I display it as a string in this format "11-312-001103" using Response.Write(value)?
Thanks
This produces the required result
string result = Int64.Parse(s.Remove(5,2)).ToString("00-000-000000");
assuming that you want to drop 2 characters at the position of the 2 first nulls.
Any reason you don't want to just use Substring?
string dashed = text.Substring(0, 2) + "-" +
text.Substring(2, 3) + "-" +
text.Substring(7);
Or:
string dashed = string.Format("{0}-{1}-{2}", text.Substring(0, 2),
text.Substring(2, 3), text.Substring(7));
(I'm assuming it's deliberate that you've missed out two of the 0s? It's not clear which 0s, admittedly...)
Obviously you should validate that the string is the right length first...
You can try a regular expression and put this inside an extension method ToMaskedString()
public static class StringExtensions
{
public static string ToMaskedString(this String value)
{
var pattern = "^(/d{2})(/d{3})(/d*)$";
var regExp = new Regex(pattern);
return regExp.Replace(value, "$1-$2-$3");
}
}
Then call
respne.Write(value.ToMaskedString());
I wrote a quick extension method for same / similar purpose (similar in a sense that there's no way to skip characters).
Usage:
var testString = "12345";
var maskedString = testString.Mask("##.## #"); // 12.34 5
Method:
public static string Mask(this string value, string mask, char substituteChar = '#')
{
int valueIndex = 0;
try
{
return new string(mask.Select(maskChar => maskChar == substituteChar ? value[valueIndex++] : maskChar).ToArray());
}
catch (IndexOutOfRangeException e)
{
throw new Exception("Value too short to substitute all substitute characters in the mask", e);
}
}
Maybe something like
string result = str.SubString(0, 2) + "-" + str.SubString(2, 3) + "-" + str.SubString(7);
str being the "11312000011103" string
Here's a C# 8 approach you can take:
Response.Write($"{value[..2]}-{value[2..5]}-{value[^6..]}");
I've complemented the Answer from #ESipalis
The extension method accepts a Array of chars to be replaced
public static string Mask(this string value, string mask, char[] substituteChar)
{
int valueIndex = 0;
try
{
return new string(mask.Select(maskChar => substituteChar.Contains(maskChar) ? value[valueIndex++] : maskChar).ToArray());
}
catch (IndexOutOfRangeException e)
{
return "#ERROR";
}
}
And you can call the extension:
yourString.Mask("AAA-999(SSS)","AS9".ToArray())

How do I iterate "between" items in an array / collection / list?

This problem has bugged me for years, and I always feel like I'm coming up with a hack when there's a much better solution. The issue at hand occurs when you want to do something to all items in a list and then add something inbetween those items. In short, I want to:
Do something to every item in the list.
Do something else to all but the last item in the list (in effect, do something "inbetween" the items in the list).
For example, let's say I have a class called Equation:
public class Equation
{
public string LeftSide { get; set; }
public string Operator { get; set; }
public string RightSide { get; set; }
}
I want to iterate over a list of Equations and return a string that formats these items together; something like the following:
public string FormatEquationList(List<Equation> listEquations)
{
string output = string.Empty;
foreach (Equation e in listEquations)
{
//format the Equation
string equation = "(" + e.LeftSide + e.Operator + e.RightSide + ")";
//format the "inbetween" part
string inbetween = " and ";
//concatenate the Equation and "inbetween" part to the output
output += equation + inbetween;
}
return ouput;
}
The problem with the above code is that it is going to include and at the end of the returned string. I know that I could hack some code together, replace the foreach with a for loop, and add the inbetween element only if it's not the last item; but this seems like a hack.
Is there a standard methodology for how to deal with this type of problem?
You basically have a few different strategies for dealing with this kind problem:
Process the first (or last) item outside of the loop.
Perform the work and then "undo" the extraneous step.
Detect that your're processing the first or last item inside the loop.
Use a higher-level abstraction that allows you to avoid the situation.
Any of these options can be a legitimate way to implement a "between the items" style of algorithm. Which one you choose depends on things like:
which style you like
how expensive "undoing work" is
how expensive each "join" step is
whether there are any side effects
Amongst other things. For the specific case of string, I personally prefer using string.Join(), as I find it illustrates the intent most clearly. Also, in the case of strings, if you aren't using string.Join(), you should try to use StringBuilder to avoid creating too many temporary strings (a consequence of strings being immutable in .Net).
Using string concatentation as the example, the different options break down into examples as follows. (For simplicity, assume Equation has ToString() as: "(" + LeftSide + Operator + RightSide + ")"
public string FormatEquation( IEnumerable<Equation> listEquations )
{
StringBuilder sb = new StringBuilder();
if( listEquations.Count > 0 )
sb.Append( listEquations[0].ToString() );
for( int i = 1; i < listEquations.Count; i++ )
sb.Append( " and " + listEquations[i].ToString() );
return sb.ToString();
}
The second option looks like:
public string FormatEquation( IEnumerable<Equation> listEquations )
{
StringBuilder sb = new StringBuilder();
const string separator = " and ";
foreach( var eq in listEquations )
sb.Append( eq.ToString() + separator );
if( listEquations.Count > 1 )
sb.Remove( sb.Length, separator.Length );
}
The third would look something like:
public string FormatEquation( IEnumerable<Equation> listEquations )
{
StringBuilder sb = new StringBuilder();
const string separator = " and ";
foreach( var eq in listEquations )
{
sb.Append( eq.ToString() );
if( index == list.Equations.Count-1 )
break;
sb.Append( separator );
}
}
The last option can take multiple forms in .NET, using either String.Join or Linq:
public string FormatEquation( IEnumerable<Equation> listEquations )
{
return string.Join( " and ", listEquations.Select( eq => eq.ToString() ).ToArray() );
}
or:
public string FormatEquation( IEnumerable<Equation> listEquations )
{
return listEquations.Aggregate((a, b) => a.ToString() + " and " + b.ToString() );
}
Personally, I avoid using Aggregate() for string concatenation because it results in many intermediate, discarded strings. It's also not the most obvious way to "join" a bunch of results together - it's primarily geared for computing a "scalar" results from a collection in some arbitrary, caller-defined fashion.
You can use String.Join().
String.Join(" and ",listEquations.Select(e=>String.Format("({0}{1}{2})",e.LeftSide,e.Operator,e.RightSide).ToArray());
You can do this with LINQ's Aggregate operator:
public string FormatEquationList(List<Equation> listEquations)
{
return listEquations.Aggregate((a, b) =>
"(" + a.LeftSide + a.Operator + a.RightSide + ") and (" +
b.LeftSide + b.Operator + b.RightSide + ")");
}
Using a for loop with counter is perfectly reasonable if you don't want a foreach loop. This is why there is more than one type of looping statement.
If you want to process items pairwise, loop at LINQ's Aggregate operator.
I usualy add it before the condition, and check if its the 1st item.
public string FormatEquationList(List<Equation> listEquations)
{
string output = string.Empty;
foreach (Equation e in listEquations)
{
//use conditional to insert your "between" data:
output += (output == String.Empty) ? string.Empty : " and ";
//format the Equation
output += "(" + e.LeftSide + e.Operator + e.RightSide + ")";
}
return ouput;
}
I have to say I would look at the string.Join() function as well, +1 for Linqiness on that. My example is a more of a traditional solution.
I generally try to prefix separators based on a condition rather than add them to the end.
string output = string.Empty;
for (int i = 0; i < 10; i++)
{
output += output == string.Empty ? i.ToString() : " and " + i.ToString();
}
0 and 1 and 2 and 3 and 4 and 5 and 6 and 7 and 8 and 9
I like the String.Join method already posted.
But when you're not using an Array this has normally been my solution to this problem:
public string FormatEquationList(List<Equation> listEquations)
{
string output = string.Empty;
foreach (Equation e in listEquations)
{
// only append " and " when there's something to append to
if (output != string.Empty)
output += " and ";
output += "(" + e.LeftSide + e.Operator + e.RightSide + ")";
}
return output;
}
Of course, it's usually faster to use a StringBuilder:
public string FormatEquationList(List<Equation> listEquations)
{
StringBuilder output = new StringBuilder();
foreach (Equation e in listEquations)
{
// only append " and " when there's something to append to
if (output.Length > 0)
output.Append(" and ");
output.Append("(");
output.Append(e.LeftSide);
output.Append(e.Operator);
output.Append(e.RightSide);
output.Append(")");
}
return output.ToString();
}

Categories