How to know position(linenumber) of a streamreader in a textfile? - c#
an example (that might not be real life, but to make my point) :
public void StreamInfo(StreamReader p)
{
string info = string.Format(
"The supplied streamreaer read : {0}\n at line {1}",
p.ReadLine(),
p.GetLinePosition()-1);
}
GetLinePosition here is an imaginary extension method of streamreader.
Is this possible?
Of course I could keep count myself but that's not the question.
I came across this post while looking for a solution to a similar problem where I needed to seek the StreamReader to particular lines. I ended up creating two extension methods to get and set the position on a StreamReader. It doesn't actually provide a line number count, but in practice, I just grab the position before each ReadLine() and if the line is of interest, then I keep the start position for setting later to get back to the line like so:
var index = streamReader.GetPosition();
var line1 = streamReader.ReadLine();
streamReader.SetPosition(index);
var line2 = streamReader.ReadLine();
Assert.AreEqual(line1, line2);
and the important part:
public static class StreamReaderExtensions
{
readonly static FieldInfo charPosField = typeof(StreamReader).GetField("charPos", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
public static long GetPosition(this StreamReader reader)
{
// shift position back from BaseStream.Position by the number of bytes read
// into internal buffer.
int byteLen = (int)byteLenField.GetValue(reader);
var position = reader.BaseStream.Position - byteLen;
// if we have consumed chars from the buffer we need to calculate how many
// bytes they represent in the current encoding and add that to the position.
int charPos = (int)charPosField.GetValue(reader);
if (charPos > 0)
{
var charBuffer = (char[])charBufferField.GetValue(reader);
var encoding = reader.CurrentEncoding;
var bytesConsumed = encoding.GetBytes(charBuffer, 0, charPos).Length;
position += bytesConsumed;
}
return position;
}
public static void SetPosition(this StreamReader reader, long position)
{
reader.DiscardBufferedData();
reader.BaseStream.Seek(position, SeekOrigin.Begin);
}
}
This works quite well for me and depending on your tolerance for using reflection It thinks it is a fairly simple solution.
Caveats:
While I have done some simple testing using various Systems.Text.Encoding options, pretty much all of the data I consume with this are simple text files (ASCII).
I only ever use the StreamReader.ReadLine() method and while a brief review of the source for StreamReader seems to indicate this will still work when using the other read methods, I have not really tested that scenario.
No, not really possible. The concept of a "line number" is based upon the actual data that's already been read, not just the position. For instance, if you were to Seek() the reader to an arbitrary position, it's not actuall going to read that data, so it wouldn't be able to determine the line number.
The only way to do this is to keep track of it yourself.
It is extremely easy to provide a line-counting wrapper for any TextReader:
public class PositioningReader : TextReader {
private TextReader _inner;
public PositioningReader(TextReader inner) {
_inner = inner;
}
public override void Close() {
_inner.Close();
}
public override int Peek() {
return _inner.Peek();
}
public override int Read() {
var c = _inner.Read();
if (c >= 0)
AdvancePosition((Char)c);
return c;
}
private int _linePos = 0;
public int LinePos { get { return _linePos; } }
private int _charPos = 0;
public int CharPos { get { return _charPos; } }
private int _matched = 0;
private void AdvancePosition(Char c) {
if (Environment.NewLine[_matched] == c) {
_matched++;
if (_matched == Environment.NewLine.Length) {
_linePos++;
_charPos = 0;
_matched = 0;
}
}
else {
_matched = 0;
_charPos++;
}
}
}
Drawbacks (for the sake of brevity):
Does not check constructor argument for null
Does not recognize alternate ways to terminate the lines. Will be inconsistent with ReadLine() behavior when reading files separated by raw \r or \n.
Does not override "block"-level methods like Read(char[], int, int), ReadBlock, ReadLine, ReadToEnd. TextReader implementation works correctly since it routes everything else to Read(); however, better performance could be achieved by
overriding those methods via routing calls to _inner. instead of base.
passing the characters read to the AdvancePosition. See the sample ReadBlock implementation:
public override int ReadBlock(char[] buffer, int index, int count) {
var readCount = _inner.ReadBlock(buffer, index, count);
for (int i = 0; i < readCount; i++)
AdvancePosition(buffer[index + i]);
return readCount;
}
No.
Consider that it's possible to seek to any poisition using the underlying stream object (which could be at any point in any line).
Now consider what that would do to any count kept by the StreamReader.
Should the StreamReader go and figure out which line it's now on?
Should it just keep a number of lines read, regardless of position within the file?
There are more questions than just these that would make this a nightmare to implement, imho.
Here is a guy that implemented a StreamReader with ReadLine() method that registers file position.
http://www.daniweb.com/forums/thread35078.html
I guess one should inherit from StreamReader, and then add the extra method to the special class along with some properties (_lineLength + _bytesRead):
// Reads a line. A line is defined as a sequence of characters followed by
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
// immediately followed by a line feed. The resulting string does not
// contain the terminating carriage return and/or line feed. The returned
// value is null if the end of the input stream has been reached.
//
/// <include file='doc\myStreamReader.uex' path='docs/doc[#for="myStreamReader.ReadLine"]/*' />
public override String ReadLine()
{
_lineLength = 0;
//if (stream == null)
// __Error.ReaderClosed();
if (charPos == charLen)
{
if (ReadBuffer() == 0) return null;
}
StringBuilder sb = null;
do
{
int i = charPos;
do
{
char ch = charBuffer[i];
int EolChars = 0;
if (ch == '\r' || ch == '\n')
{
EolChars = 1;
String s;
if (sb != null)
{
sb.Append(charBuffer, charPos, i - charPos);
s = sb.ToString();
}
else
{
s = new String(charBuffer, charPos, i - charPos);
}
charPos = i + 1;
if (ch == '\r' && (charPos < charLen || ReadBuffer() > 0))
{
if (charBuffer[charPos] == '\n')
{
charPos++;
EolChars = 2;
}
}
_lineLength = s.Length + EolChars;
_bytesRead = _bytesRead + _lineLength;
return s;
}
i++;
} while (i < charLen);
i = charLen - charPos;
if (sb == null) sb = new StringBuilder(i + 80);
sb.Append(charBuffer, charPos, i);
} while (ReadBuffer() > 0);
string ss = sb.ToString();
_lineLength = ss.Length;
_bytesRead = _bytesRead + _lineLength;
return ss;
}
Think there is a minor bug in the code as the length of the string is used to calculate file position instead of using the actual bytes read (Lacking support for UTF8 and UTF16 encoded files).
I came here looking for something simple. If you're just using ReadLine() and don't care about using Seek() or anything, just make a simple subclass of StreamReader
class CountingReader : StreamReader {
private int _lineNumber = 0;
public int LineNumber { get { return _lineNumber; } }
public CountingReader(Stream stream) : base(stream) { }
public override string ReadLine() {
_lineNumber++;
return base.ReadLine();
}
}
and then you make it the normal way, say from a FileInfo object named file
CountingReader reader = new CountingReader(file.OpenRead())
and you just read the reader.LineNumber property.
The points already made with respect to the BaseStream are valid and important. However, there are situations in which you want to read a text and know where in the text you are. It can still be useful to write that up as a class to make it easy to reuse.
I tried to write such a class now. It seems to work correctly, but it's rather slow. It should be fine when performance isn't crucial (it isn't that slow, see below).
I use the same logic to track position in the text regardless if you read a char at a time, one buffer at a time, or one line at a time. While I'm sure this can be made to perform rather better by abandoning this, it made it much easier to implement... and, I hope, to follow the code.
I did a very basic performance comparison of the ReadLine method (which I believe is the weakest point of this implementation) to StreamReader, and the difference is almost an order of magnitude. I got 22 MB/s using my class StreamReaderEx, but nearly 9 times as much using StreamReader directly (on my SSD-equipped laptop). While it could be interesting, I don't know how to make a proper reading test; maybe using 2 identical files, each larger than the disk buffer, and reading them alternately..? At least my simple test produces consistent results when I run it several times, and regardless of which class reads the test file first.
The NewLine symbol defaults to Environment.NewLine but can be set to any string of length 1 or 2. The reader considers only this symbol as a newline, which may be a drawback. At least I know Visual Studio has prompted me a fair number of times that a file I open "has inconsistent newlines".
Please note that I haven't included the Guard class; this is a simple utility class and it should be obvoius from the context how to replace it. You can even remove it, but you'd lose some argument checking and thus the resulting code would be farther from "correct". For example, Guard.NotNull(s, "s") simply checks that is s is not null, throwing an ArgumentNullException (with argument name "s", hence the second parameter) should it be the case.
Enough babble, here's the code:
public class StreamReaderEx : StreamReader
{
// NewLine characters (magic value -1: "not used").
int newLine1, newLine2;
// The last character read was the first character of the NewLine symbol AND we are using a two-character symbol.
bool insideNewLine;
// StringBuilder used for ReadLine implementation.
StringBuilder lineBuilder = new StringBuilder();
public StreamReaderEx(string path, string newLine = "\r\n") : base(path)
{
init(newLine);
}
public StreamReaderEx(Stream s, string newLine = "\r\n") : base(s)
{
init(newLine);
}
public string NewLine
{
get { return "" + (char)newLine1 + (char)newLine2; }
private set
{
Guard.NotNull(value, "value");
Guard.Range(value.Length, 1, 2, "Only 1 to 2 character NewLine symbols are supported.");
newLine1 = value[0];
newLine2 = (value.Length == 2 ? value[1] : -1);
}
}
public int LineNumber { get; private set; }
public int LinePosition { get; private set; }
public override int Read()
{
int next = base.Read();
trackTextPosition(next);
return next;
}
public override int Read(char[] buffer, int index, int count)
{
int n = base.Read(buffer, index, count);
for (int i = 0; i
Related
Is there a way to read raw content from XmlReader?
I have a very large XML file so I am using XmlReader in C#. Problem is some of the content contains XML-like markers that should not be processed by XmlReader. <Narf name="DOH">Mark's test of <newline> like stuff</Narf> This is legacy data, so it cannot be refactored... (of course) I have tried ReadInnerXml but get the whole node. I have tried ReadElementContentAsString but get an exception saying 'newline' is not closed. // Does not deal with markup in the content (Both lines) ms.mText = reader.ReadElementContentAsString(); XElement el = XNode.ReadFrom(reader) as XElement; ms.mText = el.ToString(); What I want is ms.mText to equal "Mark's test of <newline> like stuff" and not an exception. System.Xml.XmlException was unhandled HResult=-2146232000 LineNumber=56 LinePosition=63 Message=The 'newline' start tag on line 56 position 42 does not match the end tag of 'Narf'. Line 56, position 63. Source=System.Xml The duplicate flagged question did not solve the problem because it requires changing the input to remove the problem before using the data. As stated above, this is legacy data.
I figured it out based on responses here! Not elegant, but works... public class TextWedge : TextReader { private StreamReader mSr = null; private string mBuffer = ""; public TextWedge(string filename) { mSr = File.OpenText(filename); // buffer 50 for (int i =0; i<50; i++) { mBuffer += (char) (mSr.Read()); } } public override int Peek() { return mSr.Peek() + mBuffer.Length; } public override int Read() { int iRet = -1; if (mBuffer.Length > 0) { iRet = mBuffer[0]; int ic = mSr.Read(); char c = (char)ic; mBuffer = mBuffer.Remove(0, 1); if (ic != -1) { mBuffer += c; // Run through the battery of non-xml tags mBuffer = mBuffer.Replace("<newline>", "[br]"); } } return iRet; } }
How can I limit the length of a line in NLog?
We have 2 targets in our default NLog config: File and Console. In order for the Console to stay readable we would like to define a maximum length of a line shown in the Console before wrapping to the next line. Now I have looked quite a bit at the different layouts for NLog but could only find the pad wrapper with a fixed-length option. But this truncates the line instead of wrapping it. The only way I can think of is via regexp and introduction of the ${newline} layout. Any other ideas?
You could write your own wrapper, along the lines of: [LayoutRenderer("wrap-line")] [AmbientProperty("WrapLine")] [ThreadAgnostic] public sealed class WrapLineRendererWrapper : WrapperLayoutRendererBase { public WrapLineRendererWrapper () { Position = 80; } [DefaultValue(80)] public int Position { get; set; } protected override string Transform(string text) { return string.Join(Environment.NewLine, MakeChunks(text, Position)); } private static IEnumerable<string> MakeChunks(string str, int chunkLength) { if (String.IsNullOrEmpty(str)) throw new ArgumentException(); if (chunkLength < 1) throw new ArgumentException(); for (int i = 0; i < str.Length; i += chunkLength) { if (chunkLength + i > str.Length) chunkLength = str.Length - i; yield return str.Substring(i, chunkLength); } } } How to use it : https://github.com/NLog/NLog/wiki/Extending%20NLog#how-to-write-a-custom-layout-renderer Based on this one : https://github.com/NLog/NLog/blob/master/src/NLog/LayoutRenderers/Wrappers/ReplaceNewLinesLayoutRendererWrapper.cs And this answer : https://stackoverflow.com/a/8944374/971
How can I optimize this function? c# scan text file for strings
I'm writing a program to scan a text file for blocks of strings (lines) and output the blocks to a file when found In my process class, the function proc() is taking an unusually long time to process a 6MB file. On a previous program I wrote where I scan the text for only one specific type of string it took 5 seconds to process the same file. Now I rewrote it to scan for the presence of different strings. it is taking over 8 minutes which is a significant difference. Does any one have any ideas how to optimize this function? This is my RegEx System.Text.RegularExpressions.Regex RegExp { get { return new System.Text.RegularExpressions.Regex(#"(?s)(?-m)MSH.+?(?=[\r\n]([^A-Z0-9]|.{1,2}[^A-Z0-9])|$)", System.Text.RegularExpressions.RegexOptions.Compiled); } } . public static class TypeFactory { public static List<IMessageType> GetTypeList() { List<IMessageType> types = new List<IMessageType>(); types.AddRange(from assembly in AppDomain.CurrentDomain.GetAssemblies() from t in assembly.GetTypes() where t.IsClass && t.GetInterfaces().Contains(typeof(IMessageType)) select Activator.CreateInstance(t) as IMessageType); return types; } } public class process { public void proc() { IOHandler.Read reader = new IOHandler.Read(new string[1] { #"C:\TEMP\DeIdentified\DId_RSLTXMIT.LOG" }); List<IMessageType> types = MessageType.TypeFactory.GetTypeList(); //TEST1 IOHandler.Write.writeReport(System.DateTime.Now.ToString(), "TEST", "v3test.txt", true); foreach (string file in reader.FileList) { using (FileStream readStream = new FileStream(file, FileMode.Open, FileAccess.Read)) { int charVal = 0; Int64 position = 0; StringBuilder fileFragment = new StringBuilder(); string message = string.Empty; string current = string.Empty; string previous = string.Empty; int currentLength = 0; int previousLength = 0; bool found = false; do { //string line = reader.ReturnLine(readStream, out charVal, ref position); string line = reader.ReturnLine(readStream, out charVal); for (int i = 0; i < types.Count; i++) { if (Regex.IsMatch(line, types[i].BeginIndicator)) //found first line of a message type { found = true; message += line; do { previousLength = types[i].RegExp.Match(message).Length; //keep adding lines until match length stops growing //message += reader.ReturnLine(readStream, out charVal, ref position); message += reader.ReturnLine(readStream, out charVal); currentLength = types[i].RegExp.Match(message).Length; if (currentLength == previousLength) { //stop - message complete IOHandler.Write.writeReport(message, "TEST", "v3test.txt", true); //reset message = string.Empty; currentLength = 0; previousLength = 0; break; } } while (charVal != -1); break; } } } while (charVal != -1); //END OF FILE CONDITION if (charVal == -1) { } } } IOHandler.Write.writeReport(System.DateTime.Now.ToString(), "TEST", "v3test.txt", true); } } . EDIT: I ran profiling wizard in VS2012 and I found most time was spent on RegEx.Match function
Here are some thoughts: RegEx matching is not the most efficient way to do a substring search, and you are performing the match check once per "type" of match. Have a look at efficient substring matching algorithms such as Boyer-Moore if you need to match literal substrings rather than patterns. If you must use RegEx, consider using compiled expressions. Use a BufferedStream to improve IO performance. Probably marginal for a 6MB file, but it only costs a line of code. Use a profiler to be sure exactly where time is being spent.
High level ideas: Use Regex.Matches to find all matches at once instead of one by one. Probably the main performance hit Pre-build the search pattern to include multiple messages at once. You can use Regex OR.
C# Roll back Streamreader 1 character
For a C# project i am using streamreader, i need to go back 1 character (basicly like undoing) and i need it to change so when you get the next character it is the same one as when you rolled back For example Hello There we do H E L L O [whitespace] T H E R <-- we undo the R so.. R <-- undid R E that is a rough idea
When you don't know if you want the value, instead of Read(), use Peek() - then you can check the value without advancing the stream. Another approach (that I use in some of my code) is to encapsulate the reader (or in my case, a Stream) in a class that has an internal buffer that lets you push values back. The buffer is always used first, making it easy to push values (or even: adjusted values) back into the stream without having to rewind it (which doesn't work for a number of streams).
A clean solution would be to derive a class from StreamReader and override the Read() function. For your requirements a simple private int lastChar would suffice to implement a Pushback() method. A more general solution would use a Stack<char> to allow unlimited pushbacks. //untested, incomplete class MyReader : StreamReader { public MyReader(Stream strm) : base(strm) { } private int lastChar = -1; public override int Read() { int ch; if (lastChar >= 0) { ch = lastChar; lastChar = -1; } else { ch = base.Read(); // could be -1 } return ch; } public void PushBack(char ch) // char, don't allow Pushback(-1) { if (lastChar >= 0) throw new InvalidOperation("PushBack of more than 1 char"); lastChar = ch; } }
Minus one from the position: var bytes = Encoding.ASCII.GetBytes("String"); Stream stream = new MemoryStream(bytes); Console.WriteLine((char)stream.ReadByte()); //S Console.WriteLine((char)stream.ReadByte()); //t stream.Position -= 1; Console.WriteLine((char)stream.ReadByte()); //t Console.WriteLine((char)stream.ReadByte()); //r Console.WriteLine((char)stream.ReadByte()); //i Console.WriteLine((char)stream.ReadByte()); //n Console.WriteLine((char)stream.ReadByte()); //g
Is there an easy way to return a string repeated X number of times?
I'm trying to insert a certain number of indentations before a string based on an items depth and I'm wondering if there is a way to return a string repeated X times. Example: string indent = "---"; Console.WriteLine(indent.Repeat(0)); //would print nothing. Console.WriteLine(indent.Repeat(1)); //would print "---". Console.WriteLine(indent.Repeat(2)); //would print "------". Console.WriteLine(indent.Repeat(3)); //would print "---------".
If you only intend to repeat the same character you can use the string constructor that accepts a char and the number of times to repeat it new String(char c, int count). For example, to repeat a dash five times: string result = new String('-', 5); Output: -----
If you're using .NET 4.0, you could use string.Concat together with Enumerable.Repeat. int N = 5; // or whatever Console.WriteLine(string.Concat(Enumerable.Repeat(indent, N))); Otherwise I'd go with something like Adam's answer. The reason I generally wouldn't advise using Andrey's answer is simply that the ToArray() call introduces superfluous overhead that is avoided with the StringBuilder approach suggested by Adam. That said, at least it works without requiring .NET 4.0; and it's quick and easy (and isn't going to kill you if efficiency isn't too much of a concern).
most performant solution for string string result = new StringBuilder().Insert(0, "---", 5).ToString();
public static class StringExtensions { public static string Repeat(this string input, int count) { if (string.IsNullOrEmpty(input) || count <= 1) return input; var builder = new StringBuilder(input.Length * count); for(var i = 0; i < count; i++) builder.Append(input); return builder.ToString(); } }
For many scenarios, this is probably the neatest solution: public static class StringExtensions { public static string Repeat(this string s, int n) => new StringBuilder(s.Length * n).Insert(0, s, n).ToString(); } Usage is then: text = "Hello World! ".Repeat(5); This builds on other answers (particularly #c0rd's). As well as simplicity, it has the following features, which not all the other techniques discussed share: Repetition of a string of any length, not just a character (as requested by the OP). Efficient use of StringBuilder through storage preallocation.
Strings and chars [version 1] string.Join("", Enumerable.Repeat("text" , 2 )); //result: texttext Strings and chars [version 2]: String.Concat(Enumerable.Repeat("text", 2)); //result: texttext Strings and chars [version 3] new StringBuilder().Insert(0, "text", 2).ToString(); //result: texttext Chars only: new string('5', 3); //result: 555 Extension way: (works FASTER - better for WEB) public static class RepeatExtensions { public static string Repeat(this string str, int times) { var a = new StringBuilder(); //Append is faster than Insert ( () => a.Append(str) ).RepeatAction(times) ; return a.ToString(); } public static void RepeatAction(this Action action, int count) { for (int i = 0; i < count; i++) { action(); } } } usage: var a = "Hello".Repeat(3); //result: HelloHelloHello
Use String.PadLeft, if your desired string contains only a single char. public static string Indent(int count, char pad) { return String.Empty.PadLeft(count, pad); } Credit due here
You can repeat your string (in case it's not a single char) and concat the result, like this: String.Concat(Enumerable.Repeat("---", 5))
I would go for Dan Tao's answer, but if you're not using .NET 4.0 you can do something like that: public static string Repeat(this string str, int count) { return Enumerable.Repeat(str, count) .Aggregate( new StringBuilder(str.Length * count), (sb, s) => sb.Append(s)) .ToString(); }
string indent = "---"; string n = string.Concat(Enumerable.Repeat(indent, 1).ToArray()); string n = string.Concat(Enumerable.Repeat(indent, 2).ToArray()); string n = string.Concat(Enumerable.Repeat(indent, 3).ToArray());
Adding the Extension Method I am using all over my projects: public static string Repeat(this string text, int count) { if (!String.IsNullOrEmpty(text)) { return String.Concat(Enumerable.Repeat(text, count)); } return ""; } Hope someone can take use of it...
I like the answer given. Along the same lines though is what I've used in the past: "".PadLeft(3*Indent,'-') This will fulfill creating an indent but technically the question was to repeat a string. If the string indent is something like >-< then this as well as the accepted answer would not work. In this case, c0rd's solution using StringBuilder looks good, though the overhead of StringBuilder may in fact not make it the most performant. One option is to build an array of strings, fill it with indent strings, then concat that. To whit: int Indent = 2; string[] sarray = new string[6]; //assuming max of 6 levels of indent, 0 based for (int iter = 0; iter < 6; iter++) { //using c0rd's stringbuilder concept, insert ABC as the indent characters to demonstrate any string can be used sarray[iter] = new StringBuilder().Insert(0, "ABC", iter).ToString(); } Console.WriteLine(sarray[Indent] +"blah"); //now pretend to output some indented line We all love a clever solution but sometimes simple is best.
Surprised nobody went old-school. I am not making any claims about this code, but just for fun: public static string Repeat(this string #this, int count) { var dest = new char[#this.Length * count]; for (int i = 0; i < dest.Length; i += 1) { dest[i] = #this[i % #this.Length]; } return new string(dest); }
Print a line with repetition. Console.Write(new string('=', 30) + "\n"); ==============================
For general use, solutions involving the StringBuilder class are best for repeating multi-character strings. It's optimized to handle the combination of large numbers of strings in a way that simple concatenation can't and that would be difficult or impossible to do more efficiently by hand. The StringBuilder solutions shown here use O(N) iterations to complete, a flat rate proportional to the number of times it is repeated. However, for very large number of repeats, or where high levels of efficiency must be squeezed out of it, a better approach is to do something similar to StringBuilder's basic functionality but to produce additional copies from the destination, rather than from the original string, as below. public static string Repeat_CharArray_LogN(this string str, int times) { int limit = (int)Math.Log(times, 2); char[] buffer = new char[str.Length * times]; int width = str.Length; Array.Copy(str.ToCharArray(), buffer, width); for (int index = 0; index < limit; index++) { Array.Copy(buffer, 0, buffer, width, width); width *= 2; } Array.Copy(buffer, 0, buffer, width, str.Length * times - width); return new string(buffer); } This doubles the length of the source/destination string with each iteration, which saves the overhead of resetting counters each time it would go through the original string, instead smoothly reading through and copying the now much longer string, something that modern processors can do much more efficiently. It uses a base-2 logarithm to find how many times it needs to double the length of the string and then proceeds to do so that many times. Since the remainder to be copied is now less than the total length it is copying from, it can then simply copy a subset of what it has already generated. I have used the Array.Copy() method over the use of StringBuilder, as a copying of the content of the StringBuilder into itself would have the overhead of producing a new string with that content with each iteration. Array.Copy() avoids this, while still operating with an extremely high rate of efficiency. This solution takes O(1 + log N) iterations to complete, a rate that increases logarithmically with the number of repeats (doubling the number of repeats equals one additional iteration), a substantial savings over the other methods, which increase proportionally.
Another approach is to consider string as IEnumerable<char> and have a generic extension method which will multiply the items in a collection by the specified factor. public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, int times) { source = source.ToArray(); return Enumerable.Range(0, times).SelectMany(_ => source); } So in your case: string indent = "---"; var f = string.Concat(indent.Repeat(0)); //.NET 4 required //or var g = new string(indent.Repeat(5).ToArray());
Not sure how this would perform, but it's an easy piece of code. (I have probably made it appear more complicated than it is.) int indentCount = 3; string indent = "---"; string stringToBeIndented = "Blah"; // Need dummy char NOT in stringToBeIndented - vertical tab, anyone? char dummy = '\v'; stringToBeIndented.PadLeft(stringToBeIndented.Length + indentCount, dummy).Replace(dummy.ToString(), indent); Alternatively, if you know the maximum number of levels you can expect, you could just declare an array and index into it. You would probably want to make this array static or a constant. string[] indents = new string[4] { "", indent, indent.Replace("-", "--"), indent.Replace("-", "---"), indent.Replace("-", "----") }; output = indents[indentCount] + stringToBeIndented;
I don't have enough rep to comment on Adam's answer, but the best way to do it imo is like this: public static string RepeatString(string content, int numTimes) { if(!string.IsNullOrEmpty(content) && numTimes > 0) { StringBuilder builder = new StringBuilder(content.Length * numTimes); for(int i = 0; i < numTimes; i++) builder.Append(content); return builder.ToString(); } return string.Empty; } You must check to see if numTimes is greater then zero, otherwise you will get an exception.
Using the new string.Create function, we can pre-allocate the right size and copy a single string in a loop using Span<char>. I suspect this is likely to be the fastest method, as there is no extra allocation at all: the string is precisely allocated. public static string Repeat(this string source, int times) { return string.Create(source.Length * times, source, RepeatFromString); } private static void RepeatFromString(Span<char> result, string source) { ReadOnlySpan<char> sourceSpan = source.AsSpan(); for (var i = 0; i < result.Length; i += sourceSpan.Length) sourceSpan.CopyTo(result.Slice(i, sourceSpan.Length)); } dotnetfiddle
I didn't see this solution. I find it simpler for where I currently am in software development: public static void PrintFigure(int shapeSize) { string figure = "\\/"; for (int loopTwo = 1; loopTwo <= shapeSize - 1; loopTwo++) { Console.Write($"{figure}"); } }
You can create an ExtensionMethod to do that! public static class StringExtension { public static string Repeat(this string str, int count) { string ret = ""; for (var x = 0; x < count; x++) { ret += str; } return ret; } } Or using #Dan Tao solution: public static class StringExtension { public static string Repeat(this string str, int count) { if (count == 0) return ""; return string.Concat(Enumerable.Repeat(indent, N)) } }