The below code is from my other questions that I have asked here on SO. Everyone has been so helpful and I almost have a grasp with regards to RegEx but I ran into another hurdle.
This is what I basically need to do in a nutshell. I need to take this line that is in a text file that I load into my content variable:
X17.8Y-1.Z0.1G0H1E1
I need to do a wildcard search for the X value, Y value, Z value, and H value. When I am done, I need this written back to my text file (I know how to create the text file so that is not the problem).
X17.8Y-1.G54G0T2
G43Z0.1H1M08
I have code that the kind users here have given me, except I need to create the T value at the end of the first line, and use the value from the H and increment it by 1 for the T value. For example:
X17.8Y-1.Z0.1G0H5E1
would translate as:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
The T value is 6 because the H value is 5.
I have code that does everything (does two RegEx functions and separates the line of code into two new lines and adds some new G values). But I don't know how to add the T value back into the first line and increment it by 1 of the H value. Here is my code:
StreamReader reader = new StreamReader(fDialog.FileName.ToString());
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, #"X[-\d.]+Y[-\d.]+", "$0G54G0");
content = Regex.Replace(content, #"(Z(?:\d*\.)?\d+)[^H]*G0(H(?:\d*\.)?\d+)\w*", "\nG43$1$2M08"); //This must be created on a new line
This code works great at taking:
X17.8Y-1.Z0.1G0H5E1
and turning it into:
X17.8Y-1.G54G0
G43Z0.1H5M08
but I need it turned into this:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
(notice the T value is added to the first line, which is the H value +1 (T = H + 1).
Can someone please modify my RegEx statement so I can do this automatically? I tried to combine my two RegEx statements into one line but I failed miserably.
Update1: Stephen in the comments below suggests, "there's no arithmetic operators in regex, you'll need to use a group to pull out the H value, turn it into an int, add one and build a new string.". But I have no idea on how to do this in C# code.
The easiest way to do this is with a simple program that uses a few Regex patterns that capture (named) groups, I had a little spare time so here you go:
Program.cs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
const string InputFileName = #"input.txt";
const string OutputFileName = #"output.txt";
List<Line> parsedLineList = new List<Line>();
using (StreamReader sr = new StreamReader(InputFileName))
{
string inputLine;
int lineNum = 0;
while ((inputLine = sr.ReadLine()) != null)
{
lineNum++;
Line parsedLine = new Line(inputLine);
if (parsedLine.IsMatch)
{
parsedLineList.Add(parsedLine);
}
else
{
Debug.WriteLine("Line {0} did not match pattern {1}", lineNum, inputLine);
}
}
}
using (StreamWriter sw = new StreamWriter(OutputFileName))
{
foreach (Line line in parsedLineList)
{
sw.WriteLine(line.ToString());
}
}
}
}
}
With input.txt containing:
X17.8Y-1.Z0.1G0H1E1
this program creates output.txt containing:
X17.8Y-1.G54G0T2G43Z0.1H1M08
The above code in Program.cs requires the following simple Line and Fragment class definitions:
Line.cs
namespace Fragments
{
class Line
{
private readonly static Regex Pattern =
new Regex(#"^(?<X>X[^Y]+?)(?<Y>Y[^Z]+?)(?<Z>Z[^G]+?)(?<G>G[^H]+?)(?<H>H[^E]+?)(?<E>E[^$])$");
public readonly string OriginalText;
public string Text
{
get
{
return this.X.ToString() + this.Y.ToString() + this.G54.ToString() + this.G.ToString() + this.T.ToString() + Environment.NewLine +
this.G43.ToString() + this.Z.ToString() + this.H.ToString() + this.M08.ToString();
}
}
public readonly bool IsMatch;
public Fragment X { get; set; }
public Fragment Y { get; set; }
public readonly Fragment G54 = new Fragment("G54");
public Fragment G { get; set; }
public Fragment T { get; set; }
public readonly Fragment G43 = new Fragment("G43");
public Fragment Z { get; set; }
public Fragment H { get; set; }
public readonly Fragment M08 = new Fragment("M08");
public Fragment E { get; set; }
public Line(string text)
{
this.OriginalText = text;
Match match = Line.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.X = new Fragment(match.Groups["X"].Value);
this.Y = new Fragment(match.Groups["Y"].Value);
this.G = new Fragment(match.Groups["G"].Value);
this.Z = new Fragment(match.Groups["Z"].Value);
this.H = new Fragment(match.Groups["H"].Value);
this.E = new Fragment(match.Groups["E"].Value);
this.T = new Fragment('T', this.H.Number + 1.0);
}
}
public override string ToString()
{
return this.Text;
}
}
}
Fragment.cs
namespace Fragments
{
class Fragment
{
private readonly static Regex Pattern =
new Regex(#"^(?<Letter>[A-Z]{1})(?<Number>.+)$");
public readonly string Text;
public readonly bool IsMatch;
public readonly char Letter;
public readonly double Number;
public Fragment(string text)
{
this.Text = text;
Match match = Fragment.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.Letter = match.Groups["Letter"].Value[0];
string possibleNumber = match.Groups["Number"].Value;
double parsedNumber;
if (double.TryParse(possibleNumber, out parsedNumber))
{
this.Number = parsedNumber;
}
else
{
Debug.WriteLine("Couldn't parse double from input {0}", possibleNumber);
}
}
else
{
Debug.WriteLine("Fragment {0} did not match fragment pattern", text);
}
}
public Fragment(char letter, double number)
{
this.Letter = letter;
this.Number = number;
this.Text = letter + number.ToString();
this.IsMatch = true;
}
public override string ToString()
{
return this.Text;
}
}
}
Create a new C# Console Application project, add these three files, update your using statements and you're ready to go. You can very easily alter the code in Program.cs to read the input and output filenames from Main's command line arguments to make the program reusable.
I'm not sure you can do this just with Regular Expressions, and even in case you can, thinking on maintainability of the code, I wouldn't implement it that way. What you can easily do with RegEx, is to capture the pieces you need into groups, and from those create the output expression.
Here's the code for that:
System.Text.StringBuilder content = new System.Text.StringBuilder();
using (var reader = new StreamReader(fDialog.FileName.ToString()))
{
string line = reader.ReadLine();
while (line != null)
{
var matchingExpression = Regex.Match(line, #"(X[-\d.]+)(Y[-\d.]+)(Z(?:\d*\.)?\d+)[^H]*G0H((?:\d*\.)?\d+)\w*");
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"{0}{1}G54G0T{2}\n",
matchingExpression.Groups[0].Value,
matchingExpression.Groups[1].Value,
Int32.Parse(matchingExpression.Groups[3].Value) + 1);
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"G43{0}H{1}M08\n",
matchingExpression.Groups[2].Value,
matchingExpression.Groups[3].Value);
line = reader.ReadLine();
}
}
And to get the output string you should do:
content.ToString();
Related
I tried doing this
using System;
using System.Collections.Generic;
using System.Text;
namespace UrlsDetector
{
class UrlDetector
{
public static string RemoveUrl(string input)
{
var words = input;
while(words.Contains("https://"))
{
string urlToRemove = words.Substring("https://", #" ");
words = words.Replace("https://" + urlToRemove , #"");
}
}
}
class Program
{
static void Main()
{
Console.WriteLine(UrlDetector.RemoveUrl(
"I saw a cat and a horse on https://www.youtube.com/"));
}
}
}
but it doesn't work
what I want to achieve is remove the entire "https://www.youtube.com/" and display "I saw a cat and a horse on"
I also want to display a message like "the sentence you input doesn't have url" if the sentence doesn't have any url. but as you can I didnt put any code to do that I just need to fix this code first but if you want to help me do that too, I gladly appreciated it.
thanks for responses.
If you are looking for a non RegEx way to do this, here you go. But the method I encoded below assumes that a URL begins with "http://" or "https://", which means it will not work with URL's that begin with something like ftp:// or file://, although the code below can be easily modified to support that. Also, it assumes the URL path continues until it reaches either the end of the string or a white space character (like a space or a tab or a new line). Again, this can easily be modified if your requirements are different.
Also, if the string contains no URL, currently it just returns a blank string. You can modify this easily too!
using System;
public class Program
{
public static void Main()
{
string str = "I saw a cat and a horse on https://www.youtube.com/";
UrlExtraction extraction = RemoveUrl(str);
Console.WriteLine("Original Text: " + extraction.OriginalText);
Console.WriteLine();
Console.WriteLine("Url: " + extraction.ExtractedUrl);
Console.WriteLine("Text: " + extraction.TextWithoutUrl);
}
private static UrlExtraction RemoveUrl(string str)
{
if (String.IsNullOrWhiteSpace(str))
{
return new UrlExtraction("", "", "");
}
int startIndex = str.IndexOf("https://",
StringComparison.InvariantCultureIgnoreCase);
if (startIndex == -1)
{
startIndex = str.IndexOf("http://",
StringComparison.InvariantCultureIgnoreCase);
}
if (startIndex == -1)
{
return new UrlExtraction(str, "", "");
}
int endIndex = startIndex;
while (endIndex < str.Length && !IsWhiteSpace(str[endIndex]))
{
endIndex++;
}
return new UrlExtraction(str, str.Substring(startIndex, endIndex - startIndex),
str.Remove(startIndex, endIndex - startIndex));
}
private static bool IsWhiteSpace(char c)
{
return
c == '\n' ||
c == '\r' ||
c == ' ' ||
c == '\t';
}
private class UrlExtraction
{
public string ExtractedUrl {get; set;}
public string TextWithoutUrl {get; set;}
public string OriginalText {get; set;}
public UrlExtraction(string originalText, string extractedUrl,
string textWithoutUrl)
{
OriginalText = originalText;
ExtractedUrl = extractedUrl;
TextWithoutUrl = textWithoutUrl;
}
}
}
A simplified version of what you're doing. Instead of using SubString or IndexOf, I split the input into a list of strings, and remove the items that contain a URL. I iterate over the list in reverse as removing an item in a forward loop direction will skip an index.
public static string RemoveUrl(string input)
{
List<string> words = input.Split(" ").ToList();
for (int i = words.Count - 1; i >= 0; i--)
{
if (words[i].StartsWith("https://")) words.RemoveAt(i);
}
return string.Join(" ", words);
}
This methods advantage is avoiding SubString and Replace methods that essentially create new Strings each time they're used. In a loop this excessive string manipulation can put pressure on the Garbage Collector and bloat the Managed Heap. A Split and Join has less performance cost in comparison especially when used in a loop like this with a lot of data.
#Moshi is correct with large amounts of data, so this is more of a Production Code Base example:
public static class Ext
{
public static LinkedList<T> RemoveAll<T>(this LinkedList<T> list, Predicate<T> match)
{
if (list == null)
{
throw new ArgumentNullException("list");
}
if (match == null)
{
throw new ArgumentNullException("match");
}
var count = 0;
var node = list.First;
while (node != null)
{
var next = node.Next;
if (match(node.Value))
{
list.Remove(node);
count++;
}
node = next;
}
return list;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var s= "I saw a https://www.youtube.com/cat and a https://www.youtube.com/horse on https://www.youtube.com/";
//Uncomment for second run
//s= #"I saw a https://www.youtube.com/cat and a https://www.youtube.com/horse on https://www.youtube.com/
//but it doesnt work
//what I want to achieve is remove the entire https://www.youtube.com/ and display I saw a cat and a horse on
//I also want to display a message like the sentence you input doesn't have url if the sentence doesn't have any url. but as you can I didnt put any code to do that I just need to fix this code first but if you want to help me do that too, I gladly appreciated it.
//thanks for responses.";
Stopwatch watch = new Stopwatch();
watch.Start();
var resultList = RemoveUrl(s);
watch.Stop(); Debug.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
var wordsLL = new LinkedList<string>(s.Split(' '));
var result = string.Join(' ', wordsLL.RemoveAll(x => x.StartsWith("https://")));
watch.Stop(); Debug.WriteLine(watch.Elapsed.ToString());
}
}
var s one line:
watch.Elapsed = {00:00:00.0116388}
watch.Elapsed = {00:00:00.0134778}
var s multilines:
watch.Elapsed = {00:00:00.0013588}
watch.Elapsed = {00:00:00.0009252}
Using basic string manipulation will never get you where you want to be.
Using regular expressions makes this very easy for you.
search for a piece of text that looks like
"http(s)?:\/\/\S*[^\s\.]":
http: the text block http
(s)?: the optional (?) letter s
:\/\/: the characters ://
\S*: any amount (*) non white characters (\S)
[^\s\.]: any character that is not (^) in the list ([ ]) of characters being white characters (\s) or dot (\.). This allows you to exclude the dot at the end of a sentence from your url.
using System;
using System.Text.RegularExpressions;
namespace UrlsDetector
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine(UrlDetector.RemoveUrl(
"I saw a cat and a horse on https://www.youtube.com/ and also on http://www.example.com."));
Console.ReadLine();
}
}
class UrlDetector
{
public static string RemoveUrl(string input)
{
var regex = new Regex($#"http(s)?:\/\/\S*[^\s.]");
return regex.Replace(input, "");
}
}
}
Using regular expressions you can also detect matches Regex.Match(...) which allows you to detect any urls in your text.
Better way to use, split and StringBuilder. Code will be look like this. StringBuilder is optimized this kind of situation.
Pseudocode:
var words = "I saw a cat and a horse on https://www.youtube.com/".Split(" ").ToList();
var sb = new StringBuilder();
foreach(var word in words){
if(!word.StartsWith("https://")) sb.Append(word + " ");
}
return sb.ToString();
I am sure its possible, but I haven't figured out how. How would I go about using string interpolation for scriptableObjects? Some basic code I have for it:
public class ScriptableCardBase : ScriptableObject
{
public string cardDes;
}
public class CardUI : MonoBehaviour
{
public List<ScriptableCardBase> card = new List<ScriptableCardBase>();
public Text desText;
public void DisplayCard(int i)
{
desText.text = card[i].cardDes;
}
}
For my cards, I just want to be able to display the damage of the card, or number of effects, etc. Here is a screenshot of what the SO Card looks like and where I try to input the string. Any help would be greatly appreciated!
CardSO
I guess a long questions short is, can I use string interpolation with scriptable objects?
What you want is to convert your string to its Formattable version.
You can do it via reflection or some sort of IL you want. Some start could be:
public static string ParseFromFormattable(this string str, object context) {
// Define a random char that is unlikely to appear on normal text.
const char weirdChar = '°'; // This will prepend the interpolation parameters
var outputStr = "";
// Split all the parts and iterate through them, using reflection to get the value from values inside '{}'
foreach (var part in GetStringParts()) {
if (part[0] == weirdChar) { outputStr += context.GetType().GetField(part.Substring(1)).GetValue(context).ToString(); }
else { outputStr += part; }
}
return "";
List<string> GetStringParts() {
var strParts = new List<string>();
var currentPart = "";
var writingParam = false;
foreach (var c in str) {
if (c == '{') {
if (currentPart != "") { strParts.Add(currentPart); }
currentPart = "";
writingParam = true;
}
else if (writingParam && c == '}') {
strParts.Add(weirdChar + currentPart);
currentPart = "";
writingParam = false;
}
else { currentPart += c; }
}
strParts.Add(currentPart); // Don't forget to add the last string
return strParts;
}
}
Note this is a very basic version that doesn't support complex interpolation parameters.. Only parameters that are field members of the class that is passed as 'context' will be supported.
You can expand the reflection parts to support propeties, methods, and references if you want.. or even direct code (math operations, etc).
I would highly recommend just using some IL instead, though.
I have an initial file containing lines such as:
34 964:0.049759 1123:0.0031 2507:0.015979
32,48 524:0.061167 833:0.030133 1123:0.002549
34,52 534:0.07349 698:0.141667 1123:0.004403
106 389:0.013396 417:0.016276 534:0.023859
The first part of a line is the class number. A line can have several classes.
For each class, I create a new file.
For instance for class 34 the resulting file will be :
+1 964:0.049759 1123:0.0031 2507:0.015979
-1 524:0.061167 833:0.030133 1123:0.002549
+1 534:0.07349 698:0.141667 1123:0.004403
-1 389:0.013396 417:0.016276 534:0.023859
For class 106 the resulting file will be :
-1 964:0.049759 1123:0.0031 2507:0.015979
-1 524:0.061167 833:0.030133 1123:0.002549
-1 534:0.07349 698:0.141667 1123:0.004403
+1 389:0.013396 417:0.016276 534:0.023859
The problem is I have 13 files to write for 200 class.
I already ran a less optimized version of my code and it took several hours.
With my code below it takes 1 hour to generate the 2600 files.
Is there a way to perform such a replacement in a faster way? Are regex a viable option?
Below is my implementation (works on LINQPAD with this data file)
static void Main()
{
const string filePath = #"C:\data.txt";
const string generatedFilesFolderPath = #"C:\";
const string fileName = "data";
using (new TimeIt("Whole process"))
{
var fileLines = File.ReadLines(filePath).Select(l => l.Split(new[] { ' ' }, 2)).ToList();
var classValues = GetClassValues();
foreach (var classValue in classValues)
{
var directoryPath = Path.Combine(generatedFilesFolderPath, classValue);
if (!Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath);
var classFilePath = Path.Combine(directoryPath, fileName);
using (var file = new StreamWriter(classFilePath))
{
foreach (var line in fileLines)
{
var lineFirstPart = line.First();
string newFirstPart = "-1";
var hashset = new HashSet<string>(lineFirstPart.Split(','));
if (hashset.Contains(classValue))
{
newFirstPart = "+1";
}
file.WriteLine("{0} {1}", newFirstPart, line.Last());
}
}
}
}
Console.Read();
}
public static List<string> GetClassValues()
{
// In real life there is 200 class values.
return Enumerable.Range(0, 2).Select(c => c.ToString()).ToList();
}
public class TimeIt : IDisposable
{
private readonly string _name;
private readonly Stopwatch _watch;
public TimeIt(string name)
{
_name = name;
_watch = Stopwatch.StartNew();
}
public void Dispose()
{
_watch.Stop();
Console.WriteLine("{0} took {1}", _name, _watch.Elapsed);
}
}
The output:
Whole process took 00:00:00.1175102
EDIT: I also ran a profiler and it looks like the split method is the hottest spot.
EDIT 2: Simple example:
2,1 1:0.8 2:0.2
3 1:0.4 3:0.6
12 1:0.02 4:0.88 5:0.1
Expected output for class 2:
+1 1:0.8 2:0.2
-1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1
Expected output for class 3:
-1 1:0.8 2:0.2
+1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1
Expected output for class 4:
-1 1:0.8 2:0.2
-1 1:0.4 3:0.6
-1 1:0.02 4:0.88 5:0.1
I have eliminated the hottest paths from your code by removing the split and using a bigger buffer on the FileStream.
Instead of Split I now call ToCharArray and then parse the first Chars to the first space and while I'm at it a match with classValue on a char by char basis is performed. The boolean found indicates an exact match for anything before the , of the first space. The rest of the handling is the same.
var fsw = new FileStream(classFilePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
64*1024*1024); // use a large buffer
using (var file = new StreamWriter(fsw)) // use the filestream
{
foreach(var line in fileLines) // for( int i = 0;i < fileLines.Length;i++)
{
char[] chars = line.ToCharArray();
int matched = 0;
int parsePos = -1;
bool takeClass = true;
bool found = false;
bool space = false;
// parse until space
while (parsePos<chars.Length && !space )
{
parsePos++;
space = chars[parsePos] == ' '; // end
// tokens
if (chars[parsePos] == ' ' ||
chars[parsePos] == ',')
{
if (takeClass
&& matched == classValue.Length)
{
found = true;
takeClass = false;
}
else
{
// reset matching
takeClass = true;
matched = 0;
}
}
else
{
if (takeClass
&& matched < classValue.Length
&& chars[parsePos] == classValue[matched])
{
matched++; // on the next iteration, match next
}
else
{
takeClass = false; // no match!
}
}
}
chars[parsePos - 1] = '1'; // replace 1 in front of space
var correction = 1;
if (parsePos > 1)
{
// is classValue before the comma (or before space)
if (found)
{
chars[parsePos - 2] = '+';
}
else
{
chars[parsePos - 2] = '-';
}
correction++;
}
else
{
// is classValue before the comma (or before space)
if (found)
{
// not enough space in the array, write a single char
file.Write('+');
}
else
{
file.Write('-');
}
}
file.WriteLine(chars, parsePos - correction, chars.Length - (parsePos - correction));
}
}
Instead of iterating over the un-parsed lines 200 times, how about parsing the lines upfront into a data structure then iterating over that 200 times? This should minimize the numer of string manipulation operations.
Also using StreamReader instead of File.ReadLines, so the entire file is not in memory twice -- once as string[] and another time as Detail[].
static void Main(string[] args)
{
var details = ReadDetail("data.txt").ToArray();
var classValues = Enumerable.Range(0, 10).ToArray();
foreach (var classValue in classValues)
{
// Create file/directory etc
using (var file = new StreamWriter("out.txt"))
{
foreach (var detail in details)
{
file.WriteLine("{0} {1}", detail.Classes.Contains(classValue) ? "+1" : "-1", detail.Line);
}
}
}
}
static IEnumerable<Detail> ReadDetail(string filePath)
{
using (StreamReader reader = new StreamReader(filePath))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
int separator = line.IndexOf(' ');
Detail detail = new Detail
{
Classes = line.Substring(0, separator).Split(',').Select(c => Int32.Parse(c)).ToArray(),
Line = line.Substring(separator + 1)
};
yield return detail;
}
}
}
public class Detail
{
public int[] Classes { get; set; }
public string Line { get; set; }
}
I have an ANSI 835 (text) file. For simplicity's sake it looks like this:
ISA*00
GS*Foo*12345
ST*835*000001
LX*1
CLP*123456
NM1*Lastname
REF*010101
DTM*20120512
SVC*393939
LQ*19
LX*2
CLP*23456
NM1*Smith
REF*58774
DTM*20120601
SVC*985146
LX*3
CLP*34567
NM1*Doe
REF*985432
DTM*20121102
SVC*864253
LQ*19
LQ*84
The records are broken up into LX segments. Everything after LX*1 is one record, everything after LX*2 is another record, and so on. I need to get certain items from each line, assign them to variables, and eventually add them as a row to a datagridview. Again for simplicity's sake, I have the following variables and here's what should go in each:
string ItemNumber should be the group of characters after the * in the CLP line
string LastName should be the group of characters after the * in the NM1 line
string Date should be the group of characters after the * in the REF line
string Error should be the group of characters after the * in the LQ line
The biggest problem I'm facing is that there may be more than one LQ line in each LX segment. In that case, the 2nd error can just be added to the end of the first error, separated by a comma.
I tried loading the file into a string array and going line by line, but I'm not sure how say "start at LX*1 and do stuff until you hit LX*2".
string[] lines = File.ReadAllLines(MyFile);
foreach (string line in lines)
{
string[] splitline = line.Split('*');
if (splitline[0] = "LX")
{
//this is where i need to loop through the next lines
//until i hit the next line starting with LX.
}
}
Any ideas? As always, thank you for your time!
Start with a simple data model:
public class LXRecord
{
public string ItemNumber { get; set; }
public string LastName { get; set; }
public string Date { get; set; }
public List<string> Errors { get; set; }
public LXRecord()
{
Errors = new List<String>();
}
}
Define your significant tokens:
public static class Tokens
{
public const string TOKEN_SPLITTER = "*";
public const string NEW_RECORD = "LX";
public const string ITEM_NUMBER = "CLP";
public const string LAST_NAME = "NM1";
public const string DATE = "REF";
public const string ERROR = "LQ";
}
Loop through the lines, do a switch/case on the tokens, and just start a new LXRecord when you see the "LX" flag:
List<LXRecord> records = new List<LXRecord>();
LXRecord currentRecord = null;
foreach(string line in lines)
{
int tokenIndex = line.IndexOf(Tokens.TOKEN_SPLITTER);
if (tokenIndex < 1 || tokenIndex == line.Length - 1) //no token or no value?
continue;
string token = line.Substring(0, tokenIndex);
string value = line.Substring(tokenIndex + 1);
switch(token)
{
case(Tokens.NEW_RECORD) :
currentRecord = new LXRecord();
records.Add(currentRecord);
break;
case(Tokens.ITEM_NUMBER) :
currentRecord.ItemNumber = value;
break;
case(Tokens.LAST_NAME) :
currentRecord.LastName = value;
break;
case(Tokens.DATE) :
currentRecord.Date = value;
break;
case(Tokens.ERROR) :
currentRecord.Errors.Add(value);
break;
}
}
Notice this way you can relatively easily ignore non-supported flags, add new flags, or add parsing (for example, ItemNumber could use Int32.Parse and store it as an integer, or "Date" could store a DateTime) In this case, I chose to store the errors as a List<String>, but you could comma delimit it instead if you wish. I also avoided splitting on the * character in case the content contained a second asterisk as well.
EDIT: From your comment, you can have some more complicated/specialized parsing in the case or moved into another method. Instead of the case I have above for "LAST_NAME", you could have:
case(Tokens.LAST_NAME) :
ParseName(currentRecord, value);
break;
Where ParseName is:
public static void ParseName(LXRecord record, string value)
{
int tokenIndex = value.IndexOf(Tokens.TOKEN_SPLITTER);
if (tokenIndex < 1 || tokenIndex == value.Length - 1) //no last name and first name?
{
record.LastName = value;
}
else
{
record.LastName = value.Substring(0, tokenIndex);
record.FirstName = value.Substring(tokenIndex + 1);
}
}
The token check might be tweaked there, but it should give you a good idea.
I have a string like this;
string text = "6A7FEBFCCC51268FBFF";
And I have one method for which I want to insert the logic for appending the hyphen after 4 characters to 'text' variable. So, the output should be like this;
6A7F-EBFC-CC51-268F-BFF
Appending hyphen to above 'text' variable logic should be inside this method;
public void GetResultsWithHyphen
{
// append hyphen after 4 characters logic goes here
}
And I want also remove the hyphen from a given string such as 6A7F-EBFC-CC51-268F-BFF. So, removing hyphen from a string logic should be inside this method;
public void GetResultsWithOutHyphen
{
// Removing hyphen after 4 characters logic goes here
}
How can I do this in C# (for desktop app)?
What is the best way to do this?
Appreciate everyone's answer in advance.
GetResultsWithOutHyphen is easy (and should return a string instead of void
public string GetResultsWithOutHyphen(string input)
{
// Removing hyphen after 4 characters logic goes here
return input.Replace("-", "");
}
for GetResultsWithHyphen, there may be slicker ways to do it, but here's one way:
public string GetResultsWithHyphen(string input)
{
// append hyphen after 4 characters logic goes here
string output = "";
int start = 0;
while (start < input.Length)
{
output += input.Substring(start, Math.Min(4,input.Length - start)) + "-";
start += 4;
}
// remove the trailing dash
return output.Trim('-');
}
Use regex:
public String GetResultsWithHyphen(String inputString)
{
return Regex.Replace(inputString, #"(\w{4})(\w{4})(\w{4})(\w{4})(\w{3})",
#"$1-$2-$3-$4-$5");
}
and for removal:
public String GetResultsWithOutHyphen(String inputString)
{
return inputString.Replace("-", "");
}
Here's the shortest regex I could come up with. It will work on strings of any length. Note that the \B token will prevent it from matching at the end of a string, so you don't have to trim off an extra hyphen as with some answers above.
using System;
using System.Text.RegularExpressions;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string text = "6A7FEBFCCC51268FBFF";
for (int i = 0; i <= text.Length;i++ )
Console.WriteLine(hyphenate(text.Substring(0, i)));
}
static string hyphenate(string s)
{
var re = new Regex(#"(\w{4}\B)");
return re.Replace (s, "$1-");
}
static string dehyphenate (string s)
{
return s.Replace("-", "");
}
}
}
var hyphenText = new string(
text
.SelectMany((i, ch) => i%4 == 3 && i != text.Length-1 ? new[]{ch, '-'} : new[]{ch})
.ToArray()
)
something along the lines of:
public string GetResultsWithHyphen(string inText)
{
var counter = 0;
var outString = string.Empty;
while (counter < inText.Length)
{
if (counter % 4 == 0)
outString = string.Format("{0}-{1}", outString, inText.Substring(counter, 1));
else
outString += inText.Substring(counter, 1);
counter++;
}
return outString;
}
This is rough code and may not be perfectly, syntactically correct
public static string GetResultsWithHyphen(string str) {
return Regex.Replace(str, "(.{4})", "$1-");
//if you don't want trailing -
//return Regex.Replace(str, "(.{4})(?!$)", "$1-");
}
public static string GetResultsWithOutHyphen(string str) {
//if you just want to remove the hyphens:
//return input.Replace("-", "");
//if you REALLY want to remove hyphens only if they occur after 4 places:
return Regex.Replace(str, "(.{4})-", "$1");
}
For removing:
String textHyphenRemoved=text.Replace('-',''); should remove all of the hyphens
for adding
StringBuilder strBuilder = new StringBuilder();
int startPos = 0;
for (int i = 0; i < text.Length / 4; i++)
{
startPos = i * 4;
strBuilder.Append(text.Substring(startPos,4));
//if it isn't the end of the string add a hyphen
if(text.Length-startPos!=4)
strBuilder.Append("-");
}
//add what is left
strBuilder.Append(text.Substring(startPos, 4));
string textWithHyphens = strBuilder.ToString();
Do note that my adding code is untested.
GetResultsWithOutHyphen method
public string GetResultsWithOutHyphen(string input)
{
return input.Replace("-", "");
}
GetResultsWithOutHyphen method
You could pass a variable instead of four for flexibility.
public string GetResultsWithHyphen(string input)
{
string output = "";
int start = 0;
while (start < input.Length)
{
char bla = input[start];
output += bla;
start += 1;
if (start % 4 == 0)
{
output += "-";
}
}
return output;
}
This worked for me when I had a value for a social security number (123456789) and needed it to display as (123-45-6789) in a listbox.
ListBox1.Items.Add("SS Number : " & vbTab & Format(SSNArray(i), "###-##-####"))
In this case I had an array of Social Security Numbers. This line of code alters the formatting to put a hyphen in.
Callee
public static void Main()
{
var text = new Text("THISisJUSTanEXAMPLEtext");
var convertText = text.Convert();
Console.WriteLine(convertText);
}
Caller
public class Text
{
private string _text;
private int _jumpNo = 4;
public Text(string text)
{
_text = text;
}
public Text(string text, int jumpNo)
{
_text = text;
_jumpNo = jumpNo < 1 ? _jumpNo : jumpNo;
}
public string Convert()
{
if (string.IsNullOrEmpty(_text))
{
return string.Empty;
}
if (_text.Length < _jumpNo)
{
return _text;
}
var convertText = _text.Substring(0, _jumpNo);
int start = _jumpNo;
while (start < _text.Length)
{
convertText += "-" + _text.Substring(start, Math.Min(_jumpNo, _text.Length - start));
start += _jumpNo;
}
return convertText;
}
}