Modifying JsonHelper - c#

I apologize for asking this question in this manner, but I evidently don't have enough "Reputation" to post a question as a comment in the originating thread.
Someone here made a nice little class to properly indent a JSON string so it's more human-friendly. It works great, except that I'd like to modify it so that empty arrays are represented by "[]" rather than having a bunch of whitespace between the characters (newline, likely several indent characters, etc.). It seemed like such a simple plan.
The original code looks like this:
private const string INDENT_STRING = " ";
public static string FormatJson(string str)
{
var indent = 0;
var quoted = false;
var sb = new StringBuilder();
for (var i = 0; i < str.Length; i++)
{
var ch = str[i];
switch (ch)
{
case '{':
case '[':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case '}':
case ']':
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
}
sb.Append(ch);
break;
case '"':
sb.Append(ch);
bool escaped = false;
var index = i;
while (index > 0 && str[--index] == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case ':':
sb.Append(ch);
if (!quoted)
sb.Append(" ");
break;
default:
sb.Append(ch);
break;
}
}
return sb.ToString();
}
I tried modifying it so it had this in it:
case '[':
sb.Append(ch);
if (!quoted)
{
if (str[i + 1] != ']')
{
sb.AppendLine();
Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
}
}
break;
It blows up on me, complaining that I'm referencing an index that's too large (actually, it complains about something that has a number of possibilities, 'ArgumentOutOfRangeException'
being the most likely). I tried adding a tracker to see if i+1 > str.Length, but it still blows up. And the spot in the string it's blowing up at isn't anywhere near a [ or a ]. Indeed, ch is a '{' and str[i+1] is a ','.
Am I making any sense?
I thought about just taking the resulting string and using Regex to rip out any whitespace between [ and ], but that seemed inelegant.
Does anyone have a recommendation for how to modify this otherwise-excellent code to be the way I want? I tried, really I did...

I have used a different approach. When i encounter a square bracket i check where is the pairing square bracket, and verify it's not an empty string in between. The expression is this:
isEmptyArray = nextMatchingBracketIndex != -1 ? stringRemainder.Substring(1, nextMatchingBracketIndex - 1).Trim().Length == 0 : false;
The full working code is:
class JsonHelper
{
private const string INDENT_STRING = " ";
public static string FormatJson(string str)
{
var indent = 0;
var quoted = false;
var isEmptyArray = false;
var sb = new StringBuilder();
char ch = default(char);
for (var i = 0; i < str.Length; i++)
{
ch = str[i];
switch (ch)
{
case '{':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case '[':
sb.Append(ch);
var stringRemainder = str.Substring(i);
var nextMatchingBracketIndex = stringRemainder.IndexOf(']');
isEmptyArray = nextMatchingBracketIndex != -1 ? stringRemainder.Substring(1, nextMatchingBracketIndex - 1).Trim().Length == 0 : false;
if (!quoted && !isEmptyArray)
{
sb.AppendLine();
Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case '}':
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
}
sb.Append(ch);
break;
case ']':
if (!quoted && !isEmptyArray)
{
sb.AppendLine();
Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
}
sb.Append(ch);
isEmptyArray = false;
break;
case '"':
sb.Append(ch);
bool escaped = false;
var index = i;
while (index > 0 && str[--index] == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case ':':
sb.Append(ch);
if (!quoted)
sb.Append(" ");
break;
default:
if (!isEmptyArray)
sb.Append(ch);
break;
}
}
return sb.ToString();
}
}
Here are the test that i verified to work as expected:
Empty types array:
"types":[]
Empty types array with characters in between:
"types":[ ]
Full types array with empty characters at the beginning
"types":[ "locality", "political"]
note: in 1,2,3 i refer to a substring in the original JSON file provided in your link:
{"status":"OK", "results":[ {"types":[ "locality", "political"], "formatted_address":"New York, NY, USA", "address_components":[ {"long_name":"New York", "short_name":"New York", "types":[ "locality", "political"]}, {"long_name":"New York", "short_name":"New York", "types":[ "administrative_area_level_2", "political"]}, {"long_name":"New York", "short_name":"NY", "types":[ "administrative_area_level_1", "political"]}, {"long_name":"United States", "short_name":"US", "types":[ "country", "political"]}], "geometry":{"location":{"lat":40.7143528, "lng":-74.0059731}, "location_type":"APPROXIMATE", "viewport":{"southwest":{"lat":40.5788964, "lng":-74.2620919}, "northeast":{"lat":40.8495342, "lng":-73.7498543}}, "bounds":{"southwest":{"lat":40.4773990, "lng":-74.2590900}, "northeast":{"lat":40.9175770, "lng":-73.7002720}}}}]}
I have tested the output i got in different tests with JSONLint and it was valid.
It's 1 a.m. my time and I am pretty sure there are some edge case i haven't covered. But this code should give you an alternative approach that works better than what you have right now.

Related

Replacing certain letters with symbol

I'm doing an assignment for school which includes replacing every letter except A,a,S,s,N,n in a string.
So far I figured out how to replace these letters, which is the opposite of the assignment.
Can someone help?
This is what I have rn.
string texti = null;
Console.WriteLine ("");
Console.WriteLine ("Put in your sentence ..");
texti = Console.ReadLine();
Console.WriteLine ("You entered the following ..");
Console.WriteLine (texti);
texti = texti.Replace ("a", "*").Replace ("A", "*").Replace ("s", "*").Replace ("S", "*").Replace ("N", "*").Replace ("n", "*");
Console.WriteLine ("Your new text");
Console.WriteLine (texti);
but again .. this above is the opposite of my assignment
Here I have a similar project, but this replaces everything with #
Console.WriteLine();
for (int i = 0; i < text.Length; i++)
{
newtext = newtext + "#";
}
Console.WriteLine(newtext);
Console.ReadLine();
You can use LINQ:
var letters = "AaSsNn";
var result = String.Join("", input.Select(c => letters.Any(x => x == c) ? c : '*'));
Use a simple loop to build a new string. but with some checks and modifications.
string text = Console.ReadLine();
string newText = "";
string ommit = "AaSsNn"; // should not remove these.
for (int i = 0; i < text.Length; i++)
{
if (ommit.Contains(text[i])) // if character exist in ommit.
{
newText += text[i]; // put the original
}
else
{
newText += "*"; // replace
}
}
You can use a bit longer condition instead of using string like ommit.
if(text[i].ToString().ToUpper() == "A" ||
text[i].ToString().ToUpper() == "S" ||
text[i].ToString().ToUpper() == "N")
string texti = string.Empty;
Console.WriteLine("");
Console.WriteLine("Put in your sentence ..");
texti = Console.ReadLine();
Console.WriteLine("You entered the following ..");
Console.WriteLine(texti);
foreach (char str in texti)
{
switch (str)
{
case 'A':
case 'a':
case 'S':
case 's':
case 'N':
case 'n':
{
break;
}
default:
{
texti = texti.Replace(str, '*');
break;
}
}
}
Console.WriteLine("Your new text");
Console.WriteLine(texti);

Escape quotes for interpolated string

I have a program that generates C# from bits of C# stored in an XML file. If I have a snippet like:
foo {bar}
I need to transform that into an interpolated string, like this:
$#"foo {bar}"
The problem is that, if I have quotes outside a placeholder, e.g.:
"foo" {bar}
I need to double those:
$#"""foo"" {bar}"
but ignore quotes inside placeholders:
foo {"bar"}
should produce:
$#"foo {"bar"}"
Also, need to look out for doubled braces:
foo {{"bar"}}
should produce:
$#"foo {{""bar""}}"
And perhaps the trickiest of all, if the placeholder is preceded and/or followed by an even number of braces:
foo {{{"bar"}}}
should produce:
$#"foo {{{"bar"}}}"
In short, if there's a placeholder then ignore everything inside. For the rest of the text, double quotes.
Can this be accomplished using regular expressions? If not, what alternatives do I have?
You will need at least 2 steps:
Add quotes inside the expression:
"(?=[^}]*(?:}})*[^}]*$)|(?<=^[^{]*(?:{{)*)" =>
replace with ""
See demo
Enclose in $#"..." with string.Format("$#\"{0}\"", str);
Here is an IDEONE demo
var s = "\"foo\" {bar}";
var rx = new Regex(#"(?<!(?<!{){[^{}]*)""(?![^{}]*}(?!}))");
Console.WriteLine(string.Format("$#\"{0}\"",rx.Replace(s,"\"\"")));
And another demo here
This cannot be done with regular expressions. Knowing when a placeholder starts is easy, knowing when it ends is the hard part, since a placeholder can hold almost any C# expression, so you have to keep track of blocks ({}) and literals (strings, chars, comments) because any brace in a literal is not significant.
This is the code I came up with:
enum ParsingMode {
Text,
Code,
InterpolatedString,
InterpolatedVerbatimString,
String,
VerbatimString,
Char,
MultilineComment
}
public static string EscapeValueTemplate(string valueTemplate) {
if (valueTemplate == null) throw new ArgumentNullException(nameof(valueTemplate));
var quoteIndexes = new List<int>();
var modeStack = new Stack<ParsingMode>();
modeStack.Push(ParsingMode.Text);
Func<ParsingMode> currentMode = () => modeStack.Peek();
for (int i = 0; i < valueTemplate.Length; i++) {
char c = valueTemplate[i];
Func<char?> nextChar = () =>
i + 1 < valueTemplate.Length ? valueTemplate[i + 1]
: default(char?);
switch (currentMode()) {
case ParsingMode.Code:
switch (c) {
case '{':
modeStack.Push(ParsingMode.Code);
break;
case '}':
modeStack.Pop();
break;
case '\'':
modeStack.Push(ParsingMode.Char);
break;
case '"':
ParsingMode stringMode = ParsingMode.String;
switch (valueTemplate[i - 1]) {
case '#':
if (i - 2 >= 0 && valueTemplate[i - 2] == '$') {
stringMode = ParsingMode.InterpolatedVerbatimString;
} else {
stringMode = ParsingMode.VerbatimString;
}
break;
case '$':
stringMode = ParsingMode.InterpolatedString;
break;
}
modeStack.Push(stringMode);
break;
case '/':
if (nextChar() == '*') {
modeStack.Push(ParsingMode.MultilineComment);
i++;
}
break;
}
break;
case ParsingMode.Text:
case ParsingMode.InterpolatedString:
case ParsingMode.InterpolatedVerbatimString:
switch (c) {
case '{':
if (nextChar() == '{') {
i++;
} else {
modeStack.Push(ParsingMode.Code);
}
break;
case '"':
switch (currentMode()) {
case ParsingMode.Text:
quoteIndexes.Add(i);
break;
case ParsingMode.InterpolatedString:
modeStack.Pop();
break;
case ParsingMode.InterpolatedVerbatimString:
if (nextChar() == '"') {
i++;
} else {
modeStack.Pop();
}
break;
}
break;
case '\\':
if (currentMode() == ParsingMode.InterpolatedString) {
i++;
}
break;
}
break;
case ParsingMode.String:
switch (c) {
case '\\':
i++;
break;
case '"':
modeStack.Pop();
break;
}
break;
case ParsingMode.VerbatimString:
if (c == '"') {
if (nextChar() == '"') {
i++;
} else {
modeStack.Pop();
}
}
break;
case ParsingMode.Char:
switch (c) {
case '\\':
i++;
break;
case '\'':
modeStack.Pop();
break;
}
break;
case ParsingMode.MultilineComment:
if (c == '*') {
if (nextChar() == '/') {
modeStack.Pop();
i++;
}
}
break;
}
}
var sb = new StringBuilder(valueTemplate, valueTemplate.Length + quoteIndexes.Count);
for (int i = 0; i < quoteIndexes.Count; i++) {
sb.Insert(quoteIndexes[i] + i, '"');
}
return sb.ToString();
}

Finding the index of chars that repeats

I'm really bad at explaining things, but I'll try my best.
I'm making a small program that converts one word into another as you type. Each letter that is typed goes through this section of code where it is changed to a different letter depending on its Index position of the whole word.
My issue here is that when there are repeating letters, the letters that repeat don't change according to their position within the word but rather the first occurrence.
For example this made up word "bacca". If you put that through the code, it SHOULD change to "vrwiy" but instead it changes to "vrwwr". I know why this is too. It's because the switch statement loops through the word that needs to be converted. However I'm without a clue on how to make it change each char according to it's own individual position within the index of the string. I thought maybe the LastIndexOf() method would work but instead it just reverses the order. So if I were to type the letter "a", it would come out as "n", but if I were to type "aa", it would switch the first "a" to "r" because the second is at the IndexOf 1 get's changed to "r".
private void inputTbox_TextChanged(object sender, EventArgs e)
{
List<string> rawZnWordList = new List<string>();
foreach (char a in inputTextBox.Text)
{
switch (inputTextBox.Text.IndexOf(a))
{
case 0:
switch (a)
{
case 'a':
rawZnWordList.Add("n");
continue;
case 'b':
rawZnWordList.Add("v");
continue;
case 'c':
rawZnWordList.Add("a");
continue;
default:
break;
}
continue;
case 1:
switch (a)
{
case 'a':
rawZnWordList.Add("r");
continue;
case 'b':
rawZnWordList.Add("x");
continue;
case 'c':
rawZnWordList.Add("z");
continue;
default:
break;
}
continue;
case 2:
switch (a)
{
case 'a':
rawZnWordList.Add("t");
continue;
case 'b':
rawZnWordList.Add("l");
continue;
case 'c':
rawZnWordList.Add("w");
continue;
default:
continue;
}
continue;
case 3:
switch (a)
{
case 'a':
rawZnWordList.Add("u");
continue;
case 'b':
rawZnWordList.Add("i");
continue;
case 'c':
rawZnWordList.Add("o");
continue;
default:
break;
}
continue;
case 4:
switch (a)
{
case 'a':
rawZnWordList.Add("y");
continue;
case 'b':
rawZnWordList.Add("m");
continue;
case 'c':
rawZnWordList.Add("d");
continue;
default:
break;
}
continue;
default:
break;
}
}
string finalZnWord = string.Join("", rawZnWordList.ToArray());
outputTextBox.Text = finalZnWord;
}
You should try using a for loop instead, like this:
for (int i = 0; i < inputTextBox.Text.Length; i++)
{
char a = inputTextBox.Text[i];
switch (i)
{
case 0:
switch (a)
...
Hope this helps ;).
You need to keep track of the index inside your foreach instead of using .IndexOf. You could also use a regular for loop instead of a foreach but this way is a minimal change to your code.
private void inputTbox_TextChanged(object sender, EventArgs e)
{
List rawZnWordList = new List();
int index = 0;
foreach (char a in inputTextBox.Text)
{
switch (index)
{
case 0:
switch (a)
{
case 'a':
rawZnWordList.Add("n");
continue;
case 'b':
rawZnWordList.Add("v");
continue;
case 'c':
rawZnWordList.Add("a");
continue;
default:
break;
}
continue;
case 1:
switch (a)
{
case 'a':
rawZnWordList.Add("r");
continue;
case 'b':
rawZnWordList.Add("x");
continue;
case 'c':
rawZnWordList.Add("z");
continue;
default:
break;
}
continue;
case 2:
switch (a)
{
case 'a':
rawZnWordList.Add("t");
continue;
case 'b':
rawZnWordList.Add("l");
continue;
case 'c':
rawZnWordList.Add("w");
continue;
default:
continue;
}
continue;
case 3:
switch (a)
{
case 'a':
rawZnWordList.Add("u");
continue;
case 'b':
rawZnWordList.Add("i");
continue;
case 'c':
rawZnWordList.Add("o");
continue;
default:
break;
}
continue;
case 4:
switch (a)
{
case 'a':
rawZnWordList.Add("y");
continue;
case 'b':
rawZnWordList.Add("m");
continue;
case 'c':
rawZnWordList.Add("d");
continue;
default:
break;
}
continue;
default:
break;
}
index++;
}
string finalZnWord = string.Join("", rawZnWordList.ToArray());
outputTextBox.Text = finalZnWord;
}
I think this does the same thing and is a lot more readable. Of course replace the letter rings with your own values. I only went up to 5 characters. I'm guessing you would want more.
//replacement letter rings
char[][] aRep = {
"xfhygaodsekzcpubitlvnjqmrw".ToCharArray(),
"wqtnsepkbalmzyxvordhjgifcu".ToCharArray(),
"nyxgmcibplovkwrszaehftqjud".ToCharArray(),
"soqjhpybuwfxvartkzginemdcl".ToCharArray(),
"pldquhegkaomcnjrfxiysvtbwz".ToCharArray(),
};
private string newText(string inVal)
{
char[] ia = inVal.ToCharArray(); //in array
int l = ia.Length;
char[] oa = new char[l]; //out array
for (int i = 0; i < l; i++)
oa[i] = aRep[i][(int)ia[i]-97]; //lowercase starts at char97
return new string(oa);
}
Here is also the code I used to generate the rings:
//generate random letter rings
//char[] ascii = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
//for (int i = 0; i < 8; i++)
// new string(ascii.OrderBy (x => Guid.NewGuid() ).ToArray()).Dump();
I did some very simple testing and this seemed quite a bit faster than the original approach and more importantly (to me) it is way more readable and a lot easier to see your replacement letter sequences. I think there is some more optimization to be done, but this i think is a good start. Just call this with your text during 'on change'.

Advanced JSON serialization formatting options

I have some data in form of objects, inside a list object. Now I want to serialize this data to JSON. For this I'm (currently) using JSON.NET. My problem is that with
JsonConvert.SerializeObject(list, ...)
I seem to only have the option to either indent the whole thing, aka every key/value, or not indent at all.
{"Variable1":1,"Variable2":"2"},{"Variable1":1,"Variable2":"2"},...
or
{
"Variable1": 1,
"Variable2": "2"
},
{
"Variable1": 1,
"Variable2": "2"
},
...
I'd like to get this:
{ "Variable1": 1, "Variable2": "2" },
{ "Variable1": 1, "Variable2": "2" },
but without having to explicitly writing every key/value myself (JsonTextWriter or manually). I just want to pass the list and get the above. Is this somehow possible? At the moment I'm serializing every object separate, by going through the list, and running some replaces, regex replaces, and similar, to get the desired output, depending on the input list. Is there an easier way to do this, without writing your own serialization method?
hello i found something that my help you to start your special formatter
i translated it from javascript to c# from here: https://github.com/umbrae/jsonlintdotcom/blob/master/c/js/jsl.format.js
it add whitespaces to json string without them.
i think you can modify this formatter to your own needs.
private static string PrettyPrinter_repeat(string s, int count) {
string ns = "";
for(int i = 0; i < count; i++) ns += s;
return ns;
}
public static string PrettyPrinter(string json)
{
int i = 0,
il = 0;
string tab = " ",
newJson = "";
int indentLevel = 0;
bool inString = false;
char currentChar = default(char);
for (i = 0, il = json.Length; i < il; i += 1)
{
currentChar = json[i];
switch (currentChar) {
case '{':
case '[':
if (!inString) {
newJson += currentChar + "\n" + PrettyPrinter_repeat(tab, indentLevel + 1);
indentLevel += 1;
} else {
newJson += currentChar;
}
break;
case '}':
case ']':
if (!inString) {
indentLevel -= 1;
newJson += "\n" + PrettyPrinter_repeat(tab, indentLevel) + currentChar;
} else {
newJson += currentChar;
}
break;
case ',':
if (!inString) {
newJson += ",\n" + PrettyPrinter_repeat(tab, indentLevel);
} else {
newJson += currentChar;
}
break;
case ':':
if (!inString) {
newJson += ": ";
} else {
newJson += currentChar;
}
break;
case ' ':
case '\n':
case '\t':
if (inString) {
newJson += currentChar;
}
break;
case '"':
if (i > 0 && json[i - 1] != '\\') {
inString = !inString;
}
newJson += currentChar;
break;
default:
newJson += currentChar;
break;
}
}
return newJson;
}

Logic to decrease character values

I am working on a logic that decreases the value of an alphanumeric List<char>. For example, A10 becomes A9, BBA becomes BAZ, 123 becomes 122. And yes, if the value entered is the last one(like A or 0), then I should return -
An additional overhead is that there is a List<char> variable which is maintained by the user. It has characters which are to be skipped. For example, if the list contains A in it, the value GHB should become GGZ and not GHA.
The base of this logic is a very simple usage of decreasing the char but with these conditions, I am finding it very difficult.
My project is in Silverlight, the language is C#. Following is my code that I have been trying to do in the 3 methods:
List<char> lstGetDecrName(List<char> lstVal)//entry point of the value that returns decreased value
{
List<char> lstTmp = lstVal;
subCheckEmpty(ref lstTmp);
switch (lstTmp.Count)
{
case 0:
lstTmp.Add('-');
return lstTmp;
case 1:
if (lstTmp[0] == '-')
{
return lstTmp;
}
break;
case 2:
if (lstTmp[1] == '0')
{
if (lstTmp[0] == '1')
{
lstTmp.Clear();
lstTmp.Add('9');
return lstTmp;
}
if (lstTmp[0] == 'A')
{
lstTmp.Clear();
lstTmp.Add('-');
return lstTmp;
}
}
if (lstTmp[1] == 'A')
{
if (lstTmp[0] == 'A')
{
lstTmp.Clear();
lstTmp.Add('Z');
return lstTmp;
}
}
break;
}
return lstGetDecrValue(lstTmp,lstVal);
}
List<char> lstGetDecrValue(List<char> lstTmp,List<char> lstVal)
{
List<char> lstValue = new List<char>();
switch (lstTmp.Last())
{
case 'A':
lstValue = lstGetDecrTemp('Z', lstTmp, lstVal);
break;
case 'a':
lstValue = lstGetDecrTemp('z', lstTmp, lstVal);
break;
case '0':
lstValue = lstGetDecrTemp('9', lstTmp, lstVal);
break;
default:
char tmp = (char)(lstTmp.Last() - 1);
lstTmp.RemoveAt(lstTmp.Count - 1);
lstTmp.Add(tmp);
lstValue = lstTmp;
break;
}
return lstValue;
}
List<char> lstGetDecrTemp(char chrTemp, List<char> lstTmp, List<char> lstVal)//shifting places eg unit to ten,etc.
{
if (lstTmp.Count == 1)
{
lstTmp.Clear();
lstTmp.Add('-');
return lstTmp;
}
lstTmp.RemoveAt(lstTmp.Count - 1);
lstVal = lstGetDecrName(lstTmp);
lstVal.Insert(lstVal.Count, chrTemp);
return lstVal;
}
I seriously need help for this. Please help me out crack through this.
The problem you are trying to solve is actually how to decrement discreet sections of a sequence of characters, each with it's own counting system, where each section is separated by a change between Alpha and Numeric. The rest of the problem is easy once you identify this.
The skipping of unwanted characters is simply a matter of repeating the decrement if you get an unwanted character in the result.
One difficultly is the ambiguous definition of the sequences. e.g. what to do when you get down to say A00, what is next? "A" or "-". For the sake of argument I am assuming a practical implementation based loosely on Excel cell names (i.e. each section operates independently of the others).
The code below does 95% of what you wanted, however there is a bug in the exclusions code. e.g. "ABB" becomes "AAY". I feel the exclusions need to be applied at a higher level (e.g. repeat decrement until no character is in the exclusions list), but I don't have time to finish it now. Also it is resulting in a blank string when it counts down to nothing, rather than the "-" you wanted, but that is trivial to add at the end of the process.
Part 1 (divide the problem into sections):
public static string DecreaseName( string name, string exclusions )
{
if (string.IsNullOrEmpty(name))
{
return name;
}
// Split the problem into sections (reverse order)
List<StringBuilder> sections = new List<StringBuilder>();
StringBuilder result = new StringBuilder(name.Length);
bool isNumeric = char.IsNumber(name[0]);
StringBuilder sb = new StringBuilder();
sections.Add(sb);
foreach (char c in name)
{
// If we change between alpha and number, start new string.
if (char.IsNumber(c) != isNumeric)
{
isNumeric = char.IsNumber(c);
sb = new StringBuilder();
sections.Insert(0, sb);
}
sb.Append(c);
}
// Now process each section
bool cascadeToNext = true;
foreach (StringBuilder section in sections)
{
if (cascadeToNext)
{
result.Insert(0, DecrementString(section, exclusions, out cascadeToNext));
}
else
{
result.Insert(0, section);
}
}
return result.ToString().Replace(" ", "");
}
Part2 (decrement a given string):
private static string DecrementString(StringBuilder section, string exclusions, out bool cascadeToNext)
{
bool exclusionsExist = false;
do
{
exclusionsExist = false;
cascadeToNext = true;
// Process characters in reverse
for (int i = section.Length - 1; i >= 0 && cascadeToNext; i--)
{
char c = section[i];
switch (c)
{
case 'A':
c = (i > 0) ? 'Z' : ' ';
cascadeToNext = (i > 0);
break;
case 'a':
c = (i > 0) ? 'z' : ' ';
cascadeToNext = (i > 0);
break;
case '0':
c = (i > 0) ? '9' : ' ';
cascadeToNext = (i > 0);
break;
case ' ':
cascadeToNext = false;
break;
default:
c = (char)(((int)c) - 1);
if (i == 0 && c == '0')
{
c = ' ';
}
cascadeToNext = false;
break;
}
section[i] = c;
if (exclusions.Contains(c.ToString()))
{
exclusionsExist = true;
}
}
} while (exclusionsExist);
return section.ToString();
}
The dividing can of course be done more efficiently, just passing start and end indexes to the DecrementString, but this is easier to write & follow and not much slower in practical terms.
do a check if its a number if so then do a minus math of the number, if its a string then change it to char codes and then the char code minus 1
I couldn't stop thinking about this yesterday, so here's an idea. Note, this is just pseudo-code, and not tested, but I think the idea is valid and should work (with a few modifications).
The main point is to define your "alphabet" directly, and specify which characters in it are illegal and should be skipped, then use a list or array of positions in this alphabet to define the word you start with.
I can't spend any more time on this right now, but please let me know if you decide to use it and get it to work!
string[] alphabet = {a, b, c, d, e};
string[] illegal = {c, d};
public string ReduceString(string s){
// Create a list of the alphabet-positions for each letter:
int[] positionList = s.getCharsAsPosNrsInAlphabet();
int[] reducedPositionList = ReduceChar(positionList, positionList.length);
string result = "";
foreach(int pos in reducedPositionList){
result += alphabet[pos];
}
return result;
}
public string ReduceChar(string[] positionList, posToReduce){
int reducedCharPosition = ReduceToNextLegalChar(positionList[posToReduce]);
// put reduced char back in place:
positionList[posToReduce] = reducedCharPosition;
if(reducedCharPosition < 0){
if(posToReduce <= 0){
// Reached the end, reduced everything, return empty array!:
return new string[]();
}
// move to back of alphabet again (ie, like the 9 in "11 - 2 = 09"):
reducedCharPosition += alphabet.length;
// Recur and reduce next position (ie, like the 0 in "11 - 2 = 09"):
return ReduceChar(positionList, posToReduce-1);
}
return positionList;
}
public int ReduceToNextLegalChar(int pos){
int nextPos = pos--;
return (isLegalChar(nextPos) ? nextPos : ReduceToNextLegalChar(nextPos));
}
public boolean IsLegalChar(int pos){
return (! illegal.contains(alphabet[pos]));
}
enter code here
Without writing all your code for you, here's a suggestion as to how you can break this down:
char DecrementAlphaNumericChar(char input, out bool hadToWrap)
{
if (input == 'A')
{
hadToWrap = true;
return 'Z';
}
else if (input == '0')
{
hadToWrap = true;
return '9';
}
else if ((input > 'A' && input <= 'Z') || (input > '0' && input <= '9'))
{
hadToWrap = false;
return (char)((int)input - 1);
}
throw new ArgumentException(
"Characters must be digits or capital letters",
"input");
}
char DecrementAvoidingProhibited(
char input, List<char> prohibited, out bool hadToWrap)
{
var potential = DecrementAlphaNumericChar(input, out hadToWrap);
while (prohibited.Contains(potential))
{
bool temp;
potential = DecrementAlphaNumericChar(potential, out temp);
if (potential == input)
{
throw new ArgumentException(
"A whole class of characters was prohibited",
"prohibited");
}
hadToWrap |= temp;
}
return potential;
}
string DecrementString(string input, List<char> prohibited)
{
char[] chrs = input.ToCharArray();
for (int i = chrs.Length - 1; i >= 0; i--)
{
bool wrapped;
chrs[i] = DecrementAvoidingProhibited(
chrs[i], prohibited, out wrapped);
if (!wrapped)
return new string(chrs);
}
return "-";
}
The only issue here is that it will reduce e.g. A10 to A09 not A9. I actually prefer this myself, but it should be simple to write a final pass that removes the extra zeroes.
For a little more performance, replace the List<char>s with Hashset<char>s, they should allow a faster Contains lookup.
I found solution to my own answer with some other workarounds.
The calling function:
MyFunction()
{
//stuff I do before
strValue = lstGetDecrName(strValue.ToList());//decrease value here
if (strValue.Contains('-'))
{
strValue = "-";
}
//stuff I do after
}
In all there are 4 functions. 2 Main functions and 2 helper functions.
List<char> lstGetDecrName(List<char> lstVal)//entry point, returns decreased value
{
if (lstVal.Contains('-'))
{
return "-".ToList();
}
List<char> lstTmp = lstVal;
subCheckEmpty(ref lstTmp);
switch (lstTmp.Count)
{
case 0:
lstTmp.Add('-');
return lstTmp;
case 1:
if (lstTmp[0] == '-')
{
return lstTmp;
}
break;
case 2:
if (lstTmp[1] == '0')
{
if (lstTmp[0] == '1')
{
lstTmp.Clear();
lstTmp.Add('9');
return lstTmp;
}
if (lstTmp[0] == 'A')
{
lstTmp.Clear();
lstTmp.Add('-');
return lstTmp;
}
}
if (lstTmp[1] == 'A')
{
if (lstTmp[0] == 'A')
{
lstTmp.Clear();
lstTmp.Add('Z');
return lstTmp;
}
}
break;
}
List<char> lstValue = new List<char>();
switch (lstTmp.Last())
{
case 'A':
lstValue = lstGetDecrTemp('Z', lstTmp, lstVal);
break;
case 'a':
lstValue = lstGetDecrTemp('z', lstTmp, lstVal);
break;
case '0':
lstValue = lstGetDecrTemp('9', lstTmp, lstVal);
break;
default:
char tmp = (char)(lstTmp.Last() - 1);
lstTmp.RemoveAt(lstTmp.Count - 1);
lstTmp.Add(tmp);
subCheckEmpty(ref lstTmp);
lstValue = lstTmp;
break;
}
lstGetDecrSkipValue(lstValue);
return lstValue;
}
List<char> lstGetDecrSkipValue(List<char> lstValue)
{
bool blnSkip = false;
foreach (char tmpChar in lstValue)
{
if (lstChars.Contains(tmpChar))
{
blnSkip = true;
break;
}
}
if (blnSkip)
{
lstValue = lstGetDecrName(lstValue);
}
return lstValue;
}
void subCheckEmpty(ref List<char> lstTmp)
{
bool blnFirst = true;
int i = -1;
foreach (char tmpChar in lstTmp)
{
if (char.IsDigit(tmpChar) && blnFirst)
{
i = tmpChar == '0' ? lstTmp.IndexOf(tmpChar) : -1;
if (tmpChar == '0')
{
i = lstTmp.IndexOf(tmpChar);
}
blnFirst = false;
}
}
if (!blnFirst && i != -1)
{
lstTmp.RemoveAt(i);
subCheckEmpty(ref lstTmp);
}
}
List<char> lstGetDecrTemp(char chrTemp, List<char> lstTmp, List<char> lstVal)//shifting places eg unit to ten,etc.
{
if (lstTmp.Count == 1)
{
lstTmp.Clear();
lstTmp.Add('-');
return lstTmp;
}
lstTmp.RemoveAt(lstTmp.Count - 1);
lstVal = lstGetDecrName(lstTmp);
lstVal.Insert(lstVal.Count, chrTemp);
subCheckEmpty(ref lstVal);
return lstVal;
}

Categories

Resources