Replace string occurrences with list entries - c#

I've been wrapping my head around this to find an "elegant" solution but I'm not quite satisfied with it.
Possible input strings:
foo () bar ()
() bar
foo
()()foo () bar
There can be "unlimited" brackets and optional, non-bracket text inbetween the brackets. The content of these empty brackets are supposed to filled with data taking from a List<string> in the order of the list entries. If there no entries or not sufficient entries the brackets are untouched.
Possible string replacements:
foo () bar () replaced with x, y will result in foo (x) bar (y)
foo () bar () replaced with x will result in foo (x) bar ()
foo () bar () replaced with x, y, z will result in foo (x) bar (y)
I hope you get the idea.
Solutions:
The solutions I had so far are fiddling around with indexes and a lot special logic to handle the different cases.
I wondered if there is a more elegant solution with, for example regex. Maybe I'm too close at the problem right now and there is a simple solution :-)
Here is an approach I'm not really happy about (readability / easy to understand):
var guiIdentifierIndex = 0;
var guiIdentifierList = new List<string>{"x", "y", "z", "x", "y"};
var sourcePathItem = "foo ()";
string targetString = "";
var splittedPath = sourcePathItem.Split(new string[] { BRACKETS }, StringSplitOptions.None);
for (int index = 0; index < splittedPath.Length; index++)
{
var subPath = splittedPath[index];
var guiIdentifier = string.Empty;
if (guiIdentifierIndex < guiIdentifierList.Count)
{
guiIdentifier = guiIdentifierList[guiIdentifierIndex];
guiIdentifierIndex++;
}
targetString += subPath;
if (index < splittedPath.Length - 1)
targetString += string.Format("({0})", guiIdentifier);
}
http://volatileread.com/utilitylibrary/snippetcompiler?id=22718

You can use regular expressions, e.g.
String source = "foo () bar ()";
var guiIdentifierList = new List<String> {
"x", "y", "z", "x", "y" };
int guiIdentifierIndex = 0;
// result == "foo (x) bar (y)"
String result = Regex.Replace(source, #"\(\)", (MatchEvaluator) (
(match) => "(" + (guiIdentifierIndex < guiIdentifierList.Count
? guiIdentifierList[guiIdentifierIndex++]
: "") + ")"
));

Split the replacement string (x,y,z) on commas, then loop through the resulting array replacing the first occurrence of () by the appropriate value.
This link shows you how to replace the first instance : Replace first occurrence of pattern in a string

How about this:
var template = "foo () bar ()";
var replacements = new[] {"x", "y", "z"};
var components = template.Split(new []{"()"}, StringSplitOptions.RemoveEmptyEntries);
var sb = new StringBuilder();
var replacementCount = replacements.Count();
for (int i = 0; i < components.Count(); i++)
{
var value = i >= replacementCount ? String.Empty : replacements[i];
sb.AppendFormat("{0}({1})", components[i], value);
}
var substitutedTemplate = sb.ToString();

I would do this way:
List<string> guiIdentifiers = new List<string>{"x", "y", "z", "x", "y"};
string input = "foo () () () () () ()";
string[] splitPath = Regex.Split(input, #"(\(\))");
for (int i = 0; i < splitPath.Length; i++)
{
if (splitPath[i] == "()" && guiIdentifiers.Count > 0)
{
splitPath[i] = string.Format("({0})", guiIdentifiers.First());
guiIdentifiers.Remove(guiIdentifiers.First());
}
}
string result = string.Join("", splitPath);
And note that split is an irregular verb that has the third form as split also, splitted is really weird and I do not recommend you to use.

var numbers = new List<string>(new[] { "1", "2", "3" });
string original = "() test ()()";
String[] tokens = original.Split(new [] {"()"}, StringSplitOptions.None);
if (tokens.Count() >= numbers.Count())
return original;
return string.Concat(tokens.Take(tokens.Count() - 1)
.Select((t, i) => t + "(" + numbers[i] + ")"));

You could try to construct the target string in one foor loop like this ("Copy and Substitute")
EDIT: Now with "lookahead" to not substitute if closing bracket is missing.
var guiIdentifierIndex = 0;
var guiIdentifierList = new List<string> { "x", "y", "z" };
var sourcePathItem = "foo () bar () func()";
string targetString = "";
int srcIndex = 0;
foreach(char c in sourcePathItem)
{
targetString += c;
char lookahead = srcIndex < sourcePathItem.Length - 1 ? sourcePathItem[++srcIndex] : ' ';
if (c == '(' && lookahead == ')'
&& guiIdentifierIndex < guiIdentifierList.Count)
{
targetString += guiIdentifierList[guiIdentifierIndex++];
}
}
Console.WriteLine("Target: " + targetString);
This substitutes foo () bar () with x, y to foo (x) bar (y).
Generally I think using a Stringbuilder for the target string would be better when sourcePathItem and guidIdentifierList is getting bigger. e.g. less resource usage
Greetings

Hi you could used Regex groups. https://msdn.microsoft.com/en-us/library/ewy2t5e0(v=vs.110).aspx
The code below matches on ( and create a regex group which you could the used to replace.
var sourcePathItem = "()()foo () bar";
var q = new Queue<string>(new string[]{ "x", "y", "z", "x", "y" });
var replaced = Regex.Replace(sourcePathItem, #"\(", m =>
(m.Groups[1].Value + "(" + q.Dequeue())
);

StringBuilder sb = new StringBuilder();
String[] parts = sourcePathItem.Split(new String[]{"()"}, StringSplitOptions.None);
for (Int32 i = 0; i < parts.Length; i++){
sb.Append(parts[i]);
if (i < guiIdentifierList.Count && i + 1 < parts.Length)
sb.Append("(" + guiIdentifierList[i] + ")");
}
var result = sb.ToString();

Related

How to tokenizing a syntax from source code based on operators C#

I'm reading all the lines from a TextBox and I am trying to remove all the whitespace that will be in the list.
I need to be able to tokenize the following expression:
if(x==0)
{
cout<<x;
}
into
if
(
x
==
0
)
{
cout
<<
x
;
}
My code:
public static string[] Tokenize(string sourceCode)
{
Regex RE = new Regex(#"([\s+\+\-\*\%\,\;\&\|\<\>\=\!\{\}])");
string[] x = RE.Split(sourceCode);
var list = new List<string>(x);
list.Remove(" ");
for (int m = 0; m < list.Count(); m++)
{
Console.WriteLine(list[m]);
}
return (RE.Split(sourceCode));
}
My output:
if(x
=
=
0)
{
cout
<
<
x
;
}
How can I split with symbols like == << && and how to remove spaces from the list?
Is there a better way of achieving what I want?
I agree to #juharr's comment.
But if you really want to use regex, it would be better to use the Match method instead of Split because it allows you to specify the tokens you are looking for instead of the token boundaries:
Regex RE = new Regex(#"\w+|\(|\)|\++|-+|\*|%|,|;|&+|\|+|<+|>+|=+|!|\{|\}");
foreach (Match m in RE.Matches(sourceCode))
{
Console.WriteLine(m.Value);
}
Result:
if
(
x
==
0
)
{
cout
<<
x
;
}
You could do something like:
var rx = new Regex(#"([\p{L}_][\p{L}\p{N}_]*|[+-]?[0-9]+|==|!=|>=|<=|<<|>>|\|\||&&|[!=+\-*/%{}();]|\s+)*");
Match match = rx.Match(str);
Group g = match.Groups[1];
foreach (var capture in g.Captures)
{
Console.WriteLine(capture);
}
(I've included many other operators compared to your example). It is still a bad idea.
Now... This is still a bad idea, but you could make it something more complex:
string str = #"if(x==0)
{
cout<<x;
var x1 = '\a';
var x2 = '\'';
var x3 = 'X';
var x4 = ""He\""llo\n"";
}";
var fragments = new[]
{
// The order of these pattern is important! Longer patterns should go first (so += before + for example)
new { Name = "Keyword", Pattern = #"(?:if|for|while|var|int|long|string|char|return)\b", Escape = false },
new { Name = "Symbol", Pattern = #"[\p{L}_][\p{L}\p{N}_]*\b", Escape = false },
new { Name = "Number", Pattern = #"[+-]?[0-9]+(?:\.[0-9]+)?\b", Escape = false },
new { Name = "OperatorAssign", Pattern = #"<<=|>>=|&&=|\|\|=|[+\-*/%&|^]=", Escape = false },
new { Name = "Operator", Pattern = #"==|!=|>=|<=|>|<|<<|>>|&&|\|\||[+\-*/%&|^!]", Escape = false },
new { Name = "Space", Pattern = #"\s+", Escape = false },
new { Name = "Assign", Pattern = #"=", Escape = true },
new { Name = "OpenBrace", Pattern = #"{", Escape = true },
new { Name = "CloseBrace", Pattern = #"}", Escape = true },
new { Name = "Semicolon", Pattern = #";", Escape = true },
new { Name = "OpenRoundParenthesis", Pattern = #"(", Escape = true },
new { Name = "CloseRoundParenthesis", Pattern = #")", Escape = true },
new { Name = "OpenSquareParenthesis", Pattern = #"[", Escape = true },
new { Name = "CloseSquareParenthesis", Pattern = #"]", Escape = true },
new { Name = "Char", Pattern = #"'(?:\\.|.)'", Escape = false },
new { Name = "String", Pattern = #"\""(?:\\.|[^""])*""", Escape = false },
};
string allPatterns = string.Join('|', fragments.Select(x => $"(?<{x.Name}>{(x.Escape ? Regex.Escape(x.Pattern) : x.Pattern)})"));
var rx = new Regex(#"\G(?:" + allPatterns + ")");
int ix = 0;
while (ix < str.Length)
{
var match = rx.Match(str, ix);
if (!match.Success)
{
Console.WriteLine($"Error starting at: {str.Substring(ix)}");
break;
}
var group = match.Groups.OfType<Group>().Skip(1).Single(x => x.Success);
string name = group.Name;
string value = match.Value;
if (name != "Space")
{
Console.WriteLine($"Match: {name}: {value}");
}
else
{
Console.WriteLine("Skipping some space");
}
ix += value.Length;
}

c# substring from a word in quotes

I am trying to use substring to get a value from a string as such:
surname='Smith',name="John"
I basically want to use the text "name" and the quotes to get the value "John"..
Is there a way to do this please?
you could use linq query to get name
var query = #"surname='Smith',name = \""John\""";
var name = query
.Split(',')
.Select(s => new KeyValuePair<string, string>(
s.Split('=').GetValue(0).ToString().Trim(),
s.Split('=').GetValue(1).ToString().Trim()
))
.FirstOrDefault(kvp => kvp.Key == "name").Value;
Console.WriteLine(name);
There are many ways to do that.
That is one:
char[] quotes = { '\'', '\"' };
string input = "surname='Smith',name=\"John\"";
string[] sections = input.Split(',');
for (int i = 0; i < sections.Length; i++)
{
string[] pair = sections[i].Split('=');
if (pair[0] == "surname")
Debug.WriteLine("surname=" + pair[1].Trim(quotes));
if (pair[0] == "name")
Debug.WriteLine("name=" + pair[1].Trim(quotes));
}

C# Curly Brace in String.Format [duplicate]

I have 3 strings. The first set of strings are:
"1.0536"
"2.1"
"2"
The second is something like:
"Round"
"Square"
"Hex"
And the last are:
"6061-T6"
"T351"
"ASF.3.4.5"
I need to combine the three strings together with identical spacing in between each string. I can't use \t for tabbing as after I combine the strings, I send them to an Access Database.
When I combine the strings they look like:
"1.0536 Round 6061-T6"
"2.1 Square T351"
"2 Hex ASF.3.4.5"
I would really like them to look like this with the same exact amount of spacing in between each string:
"1.0536 Round 6061-T6"
"2.1 Square T351"
"2 Hex ASF.3.4.5"
How can I do this with C#?
You can use advanced features of string.Format:
string.Format("{0,-10}{1,-10}{2}", ...)
You can do the same thing by writing str.PadRight(10)
If you know the maximum lengths of each column then do the following:
String result = String.Format("{0} {1} {2}", strCol1.PadRight(10), strCol2.PadRight(9), strCol3.PadRight(9));
To make life easier, utility methods:
Usage
var data = new[] {
new[] { "ID", "NAME", "DESCRIPTION" },
new[] { "1", "Frank Foo", "lorem ipsum sic dolor" },
new[] { "2", "Brandon Bar", "amet forthrightly" },
new[] { "3", "B. Baz", "Yeehah!" }
};
var tabbedData = EvenColumns(20, data);
var tabbedData2 = string.Join("\n", EvenColumns(20, false, data)); // alternate line separator, alignment
Results
ID NAME DESCRIPTION
1 Frank Foo lorem ipsum sic dolor
2 Brandon Bar amet forthrightly
3 B. Baz Yeehah!
ID NAME DESCRIPTION
1 Frank Foolorem ipsum sic dolor
2 Brandon Bar amet forthrightly
3 B. Baz Yeehah!
Code
public string EvenColumns(int desiredWidth, IEnumerable<IEnumerable<string>> lists) {
return string.Join(Environment.NewLine, EvenColumns(desiredWidth, true, lists));
}
public IEnumerable<string> EvenColumns(int desiredWidth, bool rightOrLeft, IEnumerable<IEnumerable<string>> lists) {
return lists.Select(o => EvenColumns(desiredWidth, rightOrLeft, o.ToArray()));
}
public string EvenColumns(int desiredWidth, bool rightOrLeftAlignment, string[] list, bool fitToItems = false) {
// right alignment needs "-X" 'width' vs left alignment which is just "X" in the `string.Format` format string
int columnWidth = (rightOrLeftAlignment ? -1 : 1) *
// fit to actual items? this could screw up "evenness" if
// one column is longer than the others
// and you use this with multiple rows
(fitToItems
? Math.Max(desiredWidth, list.Select(o => o.Length).Max())
: desiredWidth
);
// make columns for all but the "last" (or first) one
string format = string.Concat(Enumerable.Range(rightOrLeftAlignment ? 0 : 1, list.Length-1).Select( i => string.Format("{{{0},{1}}}", i, columnWidth) ));
// then add the "last" one without Alignment
if(rightOrLeftAlignment) {
format += "{" + (list.Length-1) + "}";
}
else {
format = "{0}" + format;
}
return string.Format(format, list);
}
Specific to the Question
// for fun, assume multidimensional declaration rather than jagged
var data = new[,] {
{ "1.0536", "2.1", "2" },
{ "Round", "Square", "Hex" },
{ "6061-T6", "T351", "ASF.3.4.5" },
};
var tabbedData = EvenColumns(20, Transpose(ToJaggedArray(data)));
with Transpose:
public T[][] Transpose<T>(T[][] original) {
// flip dimensions
var h = original.Length;
var w = original[0].Length;
var result = new T[h][];
for (var r = 0; r < h; r++) {
result[r] = new T[w];
for (var c = 0; c < w; c++)
{
result[r][c] = original[c][r];
}
}
return result;
}
And multidimensional arrays (source):
public T[][] ToJaggedArray<T>(T[,] multiArray) {
// via https://stackoverflow.com/questions/3010219/jagged-arrays-multidimensional-arrays-conversion-in-asp-net
var h = multiArray.GetLength(0);
var w = multiArray.GetLength(1);
var result = new T[h][];
for (var r = 0; r < h; r++) {
result[r] = new T[w];
for (var c = 0; c < w; c++) {
result[r][c] = multiArray[r, c];
}
}
return result;
}
I know this has long since been answered, but there is a new way as of C# 6.0
string[] one = new string[] { "1.0536", "2.1", "2" };
string[] two = new string[] { "Round", "Square", "Hex" };
string[] three = new string[] { "1.0536 Round 6061-T6", "2.1 Square T351", "2 Hex ASF.3.4.5" };
for (int i = 0; i < 3; i++) Console.WriteLine($"{one[i],-10}{two[i],-10}{three[i],-10}");
The $"{one[i],-10}{two[i],-10}{three[i],-10}" is the new replacement for string.format . I have found it very useful in many of my projects. Here is a link to more information about string interpolation in c# 6.0:
https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation
Use String.Format("{0,10}", myString)
Where 10 is the number of characters you want
To do it more dynamically you could do something like this: (hardcoding ahead!)
int padding = 3;
int maxCol0width = "Hello World!".Length;
int maxCol1width = "90.345".Length;
int maxCol2width = "value".Length;
string fmt0 = "{0,-" + (maxCol0width + padding) + "}";
string fmt1 = "{1,-" + (maxCol1width + padding) + "}";
string fmt2 = "{2,-" + (maxCol2width + padding) + "}";
string fmt = fmt0 + fmt1 + fmt2;
Console.WriteLine(fmt, "Hello World!", 90.345, "value");
Console.WriteLine(fmt, "Hi!", 1.2, "X");
Console.WriteLine(fmt, "Another", 100, "ZZZ");
You will of course need to figure out your max word widths by looping through each column's values. Also the creation of the format string could be significantly cleaned up and shortened.
Also note that you will need to use a non-proportional font for display, otherwise your columns will still not line up properly. Where are you displaying this data? There may be better ways of getting tabular output.

C# Replace with mixed values

What I am looking for is a way to replicate PHP str_replace() function in C#.
In PHP code It would look like:
$string = 'This is my demo y string!';
$search_for = array("y","x","z");
$replace_with = array("1","2","3");
if( str_replace($search_for,$replace_with,$string) ) {
$changed = true;
};
Where the y is changed to 1, x changed to 2 etc., And if anything was changed then set the variable changed or do any other code.
How to do the same in C#?
Thank you.
You can chain your string replaces like so:
var result = myString.Replace("y", "1").Replace("x", "2").Replace("z", "3");
That should do what you are looking for. An alternative approach if you had arrays with the replacements would be:
var originalChar = new List<char> { '1', '2', '3' };
var replaceWith = new List<char> { 'x', 'y', 'z' };
originalChar.ForEach(x => myString = myString.Replace(x, replaceWith[originalChar.IndexOf(x)]));
This could probably be more efficient but you get the idea.
Checking for a change:
As noted in the comments, checking for a change is as simple as checking if the original and the modified string are no longer equal:
var changed = (result != myString);
You can do it using LINQ and a Dictionary
var mappings = new Dictionary<char,char> { {'y', '1' }, { 'x', '2'} , {'z','3'}};
var newChars = inputString
.Select(x =>
{
if (mappings.ContainsKey(x))
return mappings[x];
return x;
})
.ToArray();
var output = new string(newChars);
bool changed = !(output == inputString);
This is a straightforward port, although I think it would be better to use C# the "conventional" way.
string inputString = "This is my demoy y string!";
string[] searchFor = new string[] { "y", "x", "z" }; // or char[] if you prefer
string[] replaceWith = new string[] { "1", "2", "3" };
if (ArrayReplace(searchFor, replaceWith, ref inputString))
{
bool changed = true;
}
bool ArrayReplace(string[] searchFor, string[] replaceWith, ref string inputString)
{
string copy = inputString;
for (int i = 0; i < searchFor.Length && i < replaceWith.Length; i++)
{
inputString = inputString.Replace(searchFor[i], replaceWith[i]);
}
return copy != inputString;
}

Combining arrays of strings together

I'm looking to combine the contents of two string arrays, into a new list that has the contents of both joined together.
string[] days = { "Mon", "Tue", "Wed" };
string[] months = { "Jan", "Feb", "Mar" };
// I want the output to be a list with the contents
// "Mon Jan", "Mon Feb", "Mon Mar", "Tue Jan", "Tue Feb" etc...
How can I do it ? For when it's only two arrays, the following works and is easy enough:
List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
foreach (var wordOne in wordsOne)
{
foreach (string wordTwo in wordsTwo)
{
combinedWords.Add(wordOne + " " + wordTwo);
}
}
return combinedWords;
}
But I'd like to be able to pass varying numbers of arrays in (i.e. to have a method with the signature below) and have it still work.
List<string> CombineWords(params string[][] arraysOfWords)
{
// what needs to go here ?
}
Or some other solution would be great. If it's possible to do this simply with Linq, even better!
What you want to do is actually a cartesian product of all the arrays of words, then join the words with spaces. Eric Lippert has a simple implementation of a Linq cartesian product here. You can use it to implement CombineWords:
List<string> CombineWords(params string[][] arraysOfWords)
{
return CartesianProduct(arraysOfWords)
.Select(x => string.Join(" ", x))
.ToList();
}
To cross join on any amount of arrays of strings:
// Define other methods and classes here
List<string> CombineWords(params string[][] arraysOfWords)
{
if (arraysOfWords.Length == 0)
return new List<string>();
IEnumerable<string> result = arraysOfWords[0];
foreach( string[] words in arraysOfWords.Skip(1) )
{
var tempWords = words;
result = from r in result
from w in tempWords
select string.Concat(r, " ", w);
}
return result.ToList();
}
Code below works for any number of arrays (and uses linq to some degree):
List<string> CombineWords(params string[][] wordsToCombine)
{
if (wordsToCombine.Length == 0)
return new List<string>();
IEnumerable<string> combinedWords = wordsToCombine[0].ToList();
for (int i = 1; i < wordsToCombine.Length; ++i)
{
var temp = i;
combinedWords = (from x in combinedWords from y in wordsToCombine[temp]
select x + " " + y);
}
return combinedWords.ToList();
}
public static List<string> CombineWords(params string[][] arraysOfWords)
{
var strings = new List<string>();
if (arraysOfWords.Length == 0)
{
return strings;
}
Action<string, int> combineWordsInternal = null;
combineWordsInternal = (baseString, index) =>
{
foreach (var str in arraysOfWords[index])
{
string str2 = baseString + " " + str;
if (index + 1 < arraysOfWords.Length)
{
combineWordsInternal(str2, index + 1);
}
else
{
strings.Add(str2);
}
}
};
combineWordsInternal(string.Empty, 0);
return strings;
}
Second try... I'm not able to do it in LINQ... A little too much complex to linquize correctly :-)
I'm using a local anonymous function (and showing that it's quite complex to recurse on anonymous functions, because you have to declare them separately)
This is a non-recursive solution which buffers strings as it progresses, to reduce the number of concatenations. Therefore it should also be usable for more arrays.
It also preserves your desired order - the items in the first array will always be at the beginning of the resulting string.
var s1 = new string[] { "A", "B", "C" };
var s2 = new string[] { "1", "2", "3", "4" };
var s3 = new string[] { "-", "!", "?" };
var res = Combine(s1, s2, s3);
And the function in question:
private List<string> Combine(params string[][] arrays)
{
if (arrays.Length == 1)
{
// The trivial case - exit.
return new List<string>(arrays[0]);
}
IEnumerable<string> last = arrays[arrays.Length - 1];
// Build from the last array, progress forward
for (int i = arrays.Length - 2; i >= 0; i--)
{
var buffer = new List<string>();
var current = arrays[i];
foreach (var head in current)
{
foreach (var tail in last)
{
// Concatenate with the desired space.
buffer.Add(head + " " + tail);
}
}
last = buffer;
}
return (List<string>)last;
}
Could you try this method ?
static List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
for(int x = 0; (x <= wordsOne.Length - 1); ++x)
{
for(int y = 0; (x <= wordsTwo.Length - 1); ++y)
{
combinedWords.Add(string.Format("{0} {1}", wordsOne[x], wordsTwo[y]));
}
}
return combinedWords;
}
Kris

Categories

Resources