See it.
Let's recursively define what “print a line in reverse order” is. It's simple
- separate the initial character (because it must be printed last)
- print the rest of the line in reverse order
- print a separated initial character.
For example, the line "ABCD"
:
- initial character is
'A'
- print the remainder
"BCD"
in reverse order, DCB
will be printed - we print
'A'
, as a result it is printed DCBA
.
str.Substring(1, str.Length-1)
is exactly the tail of a string, starting from the character with index 1 (the initial character of the string has index 0), and 1 less than the length of the string.
Note that the Shildt example is bad in many ways.
- The use of recursion is not justified here, since long lines (more than 1000 characters) are not uncommon, and this can lead to stack overflow.
- The task has a simple iterative solution, the use of recursion is much slower and more expensive in terms of resources.
- The code uses the
RevStr
class, which does not have “reverse line” semantics. This is just a helper class, and it must be static, since the instance does not carry an independent meaning. (And his name reflects nothing.) - The string is copied many times, which is inefficient. It is easier to pass the initial index of an existing row for processing.
- Uses an improper overload of
string.Substring
, which forces you to calculate the length manually. There is a simpler overload (from a given index to the end of the line). - The code is still bad, because the
RevStr
class RevStr
too many responsibilities: it expands the string and prints it out. As a result, flexibility is lost: we cannot invert a line with this class and use it further without output. - Mixing model semantics (flipping a line) and presentation semantics (output to the console) in one method is bad.
A better code would be (this option takes into account points 3-7):
static class StringHelper { public string Reverse(string s) { var sb = new StringBuilder(); ReverseImpl(sb, 0, s); return sb.ToString(); } private void ReverseImpl(StringBuilder sb, int startIdx, string s) { if (startIdx >= s.Length) return; ReverseImpl(sb, startIdx + 1, s); sb.Append(s[startIdx]); } } class StringReverseDemo { static void Main() { string s = "Это тест"; , Console.WriteLine("Исходная строка: " + s); Console.WriteLine("Перевернутая строка: " + StringHelper.Reverse(s)); } }
A variant in which tail recursion is possible, which means that it partially solves the problem of item 2:
static class StringHelper { public string Reverse(string s) { var sb = new StringBuilder(); ReverseImpl(sb, s.Length - 1, s); return sb.ToString(); } private void ReverseImpl(StringBuilder sb, int endIdx, string s) { if (startIdx < 0) return; sb.Append(s[endIdx]); ReverseImpl(sb, endIdx - 1, s); } }
The same code, but the recursion is folded into an iteration, thereby solving the problems of paras. 1 and 2:
static class StringHelper { public string Reverse(string s) { var sb = new StringBuilder(); for (var endIdx = s.Length - 1; endIdx >= 0; endIdx--) sb.Append(s[endIdx]); return sb.ToString(); } }
Now, instead of all logic, you can simply convert the data view, and use the quick library function:
static class StringHelper { public string Reverse(string s) { var array = s.ToCharArray(); Array.Reverse(array); return new string(array); } }
However, the latter option is also not entirely accurate, as it does not take into account combining Unicode accents. Jon Skeet wrote an excellent article about this (and much more): OMG Ponies !!! To solve the problem, we need to rearrange not the characters , but clusters of graphemes :
static class StringHelper { private IEnumerable<string> GetGraphemeClusters(string s) { var iter = StringInfo.GetTextElementEnumerator(s); while (iter.MoveNext()) yield return iter.GetTextElement(); } public string Reverse(string s) { var array = GetGraphemeClusters().ToArray(); Array.Reverse(array); return string.Concat(array); } }
See how far the solution in the book is far from correct?