Trimming down a string in C# code-behind - c#

I have this line in my code-behind:
lblAboutMe.Text = (DT1["UserBody"].ToString());
No problems. But now, we want to only show the beginning of the paragraph and then an ellipsis. So, instead of being:
Please read it all as I hate wasting time with people that don't. If
you have an issue with Muslim's please move on as I do not. I used to
practice Islam and while I no longer do I still respect it and hate
the ignorance people show by following the media or stigma instead of
experiencing it or talking with Muslim's to educate themselves about
it. Referring to the no drug policy later in this profile, yes,
pot/marijuana counts as a drug and is a no with me so please move on.
I know what follows makes me seem cold but I am really quite warm and
loving, very devoted to the right one, i am just tired of being played
and taken for granted/ advantage of. People lie soooo much and ignore
so much of what I say I do not want. I have been told many times on
here that what I seek is too much.
We want to take just the first, say, 100 characters and follow it with an ellipsis. So, something like:
Please read it all as I hate wasting time with people that don't. If
you have an issue with Muslim's please move on as I do not. I used to
practice Islam and while I no longer do I still respect it and hate
the ignorance people show by following the media or stigma instead of
experiencing it or talking with Muslim's to educate themselves ...
How can we do this in code-behind? I have a feeling it's pretty easy (because it would be easy in Access), but I'm still new to this language.

Use Length to determine your string length, then use Substring to take some of it (100 chars) if it is too long:
string aboutme = DT1["UserBody"] != null ? DT1["UserBody"].ToString() : ""; //just in case DT1["UserBody"] is null
lblAboutMe.Text = aboutme.Length > 100 ? aboutme.Substring(0,100) + "..." : aboutme;

Do it with String.Substring(startIndex, lenght):
lblAboutMe.Text = DT1["UserBody"].ToString().Substring(0, 100) + " ...";
MSDN - Substring
If you want full words try following code. It determines null values and if it's larger than 100 chars. Than it makes shure a space is at the end:
int maxLength = 100;
string body = DT1["UserBody"] != null ? DT1["UserBody"].ToString() : "";
if (!string.IsNullOrEmpty(body))
{
if(body.Length > maxLength)
{
body = body.Substring(0, maxLength);
// if you want to have full words
if (body.Contains(" "))
{
while (body[body.Length - 1] != ' ')
{
body = body.Substring(0, body.Length - 1);
if(body.Length == 2)
{
break;
}
}
}
lblAboutMe.Text = body + "...";
}
else
{
lblAboutMe.Text = body;
}
}

Please also check for Null or empty string
string aboutme = Convert.ToString(DT1["UserBody"]);
if (!string.IsNullOrEmpty(aboutme))
{
lblAboutMe.Text = aboutme.Length > 100 ? aboutme.Substring(0, 100) +"..." : aboutme;
}

Basically, you use Substring but be aware of short texts with less than 100 characters
string test = /* your full string */;
string result = test.Substring(0, Math.Min(test.Length, 100)) + " ...";
If you want to cut at spaces, use IndexOf or if you want to consider all kinds of whitespace, you can do something along the lines of the following:
string result = test.Substring(0, Math.Min(test.Length, 100));
if (test.Length > 100)
{
result += new string(test.Substring(100).TakeWhile(x => !char.IsWhiteSpace(x)).ToArray()) + " ...";
}

Related

Incremental counting and saving all values in one string

I'm having trouble thinking of a logical way to achieve this. I have a method which sends a web request with a for loop that is counting up from 1 to x, the request counts up until it finds a specific response and then sends the URL + number to another method.
After this, saying we got the number 5, I need to create a string which displays as "1,2,3,4,5" but cannot seem to find a way to create the entire string, everything I try is simply replacing the string and only keeping the last number.
string unionMod = string.Empty;
for (int i = 1; i <= count; i++)
{
unionMod =+ count + ",";
}
I assumed I'd be able to simply add each value onto the end of the string but the output is just "5," with it being the last number. I have looked around but I can't seem to even think of what I would search in order to get the answer, I have a hard-coded solution but ideally, I'd like to not have a 30+ string with each possible value and just have it created when needed.
Any pointers?
P.S: Any coding examples are appreciated but I've probably just forgotten something obvious so any directions you can give are much appreciated, I should sleep but I'm on one of those all-night coding grinds.
Thank you!
First of all your problem is the +=. You should avoid concatenating strings because it allocates a new string. Instead you should use a StringBuilder.
Your Example: https://dotnetfiddle.net/Widget/qQIqWx
My Example: https://dotnetfiddle.net/Widget/sx7cxq
public static void Main()
{
var counter = 5;
var sb = new StringBuilder();
for(var i = 1; i <= counter; ++i) {
sb.Append(i);
if (i != counter) {
sb.Append(",");
}
}
Console.WriteLine(sb);
}
As it's been pointed out, you should use += instead of =+. The latter means "take count and append a comma to it", which is the incorrect result you experienced.
You could also simplify your code like this:
int count = 10;
string unionMod = String.Join(",", Enumerable.Range(1, count));
Enumerable.Range generates a sequence of integers between its two parameters and String.Join joins them up with the given separator character.

How to split a string into efficient way c#

I have a string like this:
-82.9494547,36.2913021,0
-83.0784938,36.2347521,0
-82.9537782,36.079235,0
I need to have output like this:
-82.9494547 36.2913021, -83.0784938 36.2347521, -82.9537782,36.079235
I have tried this following to code to achieve the desired output:
string[] coordinatesVal = coordinateTxt.Trim().Split(new string[] { ",0" }, StringSplitOptions.None);
for (int i = 0; i < coordinatesVal.Length - 1; i++)
{
coordinatesVal[i] = coordinatesVal[i].Trim();
coordinatesVal[i] = coordinatesVal[i].Replace(',', ' ');
numbers.Append(coordinatesVal[i]);
if (i != coordinatesVal.Length - 1)
{
coordinatesVal.Append(", ");
}
}
But this process does not seem to me the professional solution. Can anyone please suggest more efficient way of doing this?
Your code is okay. You could dismiss temporary results and chain method calls
var numbers = new StringBuilder();
string[] coordinatesVal = coordinateTxt
.Trim()
.Split(new string[] { ",0" }, StringSplitOptions.None);
for (int i = 0; i < coordinatesVal.Length - 1; i++) {
numbers
.Append(coordinatesVal[i].Trim().Replace(',', ' '))
.Append(", ");
}
numbers.Length -= 2;
Note that the last statement assumes that there is at least one coordinate pair available. If the coordinates can be empty, you would have to enclose the loop and this last statement in if (coordinatesVal.Length > 0 ) { ... }. This is still more efficient than having an if inside the loop.
You ask about efficiency, but you don't specify whether you mean code efficiency (execution speed) or programmer efficiency (how much time you have to spend on it).
One key part of professional programming is to judge which one of these is more important in any given situation.
The other answers do a good job of covering programmer efficiency, so I'm taking a stab at code efficiency. I'm doing this at home for fun, but for professional work I would need a good reason before putting in the effort to even spend time comparing the speeds of the methods given in the other answers, let alone try to improve on them.
Having said that, waiting around for the program to finish doing the conversion of millions of coordinate pairs would give me such a reason.
One of the speed pitfalls of C# string handling is the way String.Replace() and String.Trim() return a whole new copy of the string. This involves allocating memory, copying the characters, and eventually cleaning up the garbage generated. Do that a few million times, and it starts to add up. With that in mind, I attempted to avoid as many allocations and copies as possible.
enum CurrentField
{
FirstNum,
SecondNum,
UnwantedZero
};
static string ConvertStateMachine(string input)
{
// Pre-allocate enough space in the string builder.
var numbers = new StringBuilder(input.Length);
var state = CurrentField.FirstNum;
int i = 0;
while (i < input.Length)
{
char c = input[i++];
switch (state)
{
// Copying the first number to the output, next will be another number
case CurrentField.FirstNum:
if (c == ',')
{
// Separate the two numbers by space instead of comma, then move on
numbers.Append(' ');
state = CurrentField.SecondNum;
}
else if (!(c == ' ' || c == '\n'))
{
// Ignore whitespace, output anything else
numbers.Append(c);
}
break;
// Copying the second number to the output, next will be the ,0\n that we don't need
case CurrentField.SecondNum:
if (c == ',')
{
numbers.Append(", ");
state = CurrentField.UnwantedZero;
}
else if (!(c == ' ' || c == '\n'))
{
// Ignore whitespace, output anything else
numbers.Append(c);
}
break;
case CurrentField.UnwantedZero:
// Output nothing, just track when the line is finished and we start all over again.
if (c == '\n')
{
state = CurrentField.FirstNum;
}
break;
}
}
return numbers.ToString();
}
This uses a state machine to treat incoming characters differently depending on whether they are part of the first number, second number, or the rest of the line, and output characters accordingly. Each character is only copied once into the output, then I believe once more when the output is converted to a string at the end. This second conversion could probably be avoided by using a char[] for the output.
The bottleneck in this code seems to be the number of calls to StringBuilder.Append(). If more speed were required, I would first attempt to keep track of how many characters were to be copied directly into the output, then use .Append(string value, int startIndex, int count) to send an entire number across in one call.
I put a few example solutions into a test harness, and ran them on a string containing 300,000 coordinate-pair lines, averaged over 50 runs. The results on my PC were:
String Split, Replace each line (see Olivier's answer, though I pre-allocated the space in the StringBuilder):
6542 ms / 13493147 ticks, 130.84ms / 269862.9 ticks per conversion
Replace & Trim entire string (see Heriberto's second version):
3352 ms / 6914604 ticks, 67.04 ms / 138292.1 ticks per conversion
- Note: Original test was done with 900000 coord pairs, but this entire-string version suffered an out of memory exception so I had to rein it in a bit.
Split and Join (see Ɓukasz's answer):
8780 ms / 18110672 ticks, 175.6 ms / 362213.4 ticks per conversion
Character state machine (see above):
1685 ms / 3475506 ticks, 33.7 ms / 69510.12 ticks per conversion
So, the question of which version is most efficient comes down to: what are your requirements?
Your solution is fine. Maybe you could write it a bit more elegant like this:
string[] coordinatesVal = coordinateTxt.Trim().Split(new string[] { ",0" },
StringSplitOptions.RemoveEmptyEntries);
string result = string.Empty;
foreach (string line in coordinatesVal)
{
string[] numbers = line.Trim().Split(',');
result += numbers[0] + " " + numbers[1] + ", ";
}
result = result.Remove(result.Count()-2, 2);
Note the StringSplitOptions.RemoveEmptyEntries parameter of Split method so you don't have to deal with empty lines into foreach block.
Or you can do extremely short one-liner. Harder to debug, but in simple cases does the work.
string result =
string.Join(", ",
coordinateTxt.Trim().Split(new string[] { ",0" }, StringSplitOptions.RemoveEmptyEntries).
Select(i => i.Replace(",", " ")));
heres another way without defining your own loops and replace methods, or using LINQ.
string coordinateTxt = #" -82.9494547,36.2913021,0
-83.0784938,36.2347521,0
-82.9537782,36.079235,0";
string[] coordinatesVal = coordinateTxt.Replace(",", "*").Trim().Split(new string[] { "*0", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
string result = string.Join(",", coordinatesVal).Replace("*", " ");
Console.WriteLine(result);
or even
string coordinateTxt = #" -82.9494540,36.2913021,0
-83.0784938,36.2347521,0
-82.9537782,36.079235,0";
string result = coordinateTxt.Replace(Environment.NewLine, "").Replace($",", " ").Replace(" 0", ", ").Trim(new char[]{ ',',' ' });
Console.WriteLine(result);

Faster Way to Color All Occurences in a RichTextBox in C#

I have a RichTextBox, and there are about more than 1000 occurrences of a specified search string.
I use the following function to color all the occurrences:
public void ColorAll(string s)
{
rtbxContent.BeginUpdate();
int start = 0, current = 0;
RichTextBoxFinds options = RichTextBoxFinds.MatchCase;
start = rtbxContent.Find(s, start, options);
while (start >= 0)
{
rtbxContent.SelectionStart = start;
rtbxContent.SelectionLength = s.Length;
rtbxContent.SelectionColor = Color.Red;
rtbxContent.SelectionBackColor = Color.Yellow;
current = start + s.Length;
if (current < rtbxContent.TextLength)
start = rtbxContent.Find(s, current, options);
else
break;
}
rtbxContent.EndUpdate();
}
But I found it's very slow.
However, if I color all the occurrences of another word, which has less number of occurrences in the same text, I found it's very fast.
So I guess the slowness is from (these two line might get UI refresh involved):
rtbxContent.SelectionColor = Color.Red;
rtbxContent.SelectionBackColor = Color.Yellow;
Is there a faster way of doing the same job, such as, I do the coloring in the memory, and then I display the result at one-go?
Do I make myself clear?
Thanks.
The amount of time it takes is directly proportional to the number of occurances.
It is probably the Find that is using the most time. You could replace this line:
start = rtbxContent.Find(s, start + s.Length, options);
with this:
start = rtbxContent.Find(s, current, options);
Since you have computed current to equal start + s.Length
You could also store s.Length is a variable so you do not need to count all the characters in a string each time. The same goes for rtbxContent.TextLength.
There is a faster way.
Use regex to find the matches then highlight in the richtextbox
if (this.tBoxFind.Text.Length > 0)
{
try
{
this.richTBox.SuspendLayout();
this.Cursor = Cursors.WaitCursor;
string s = this.richTBox.Text;
System.Text.RegularExpressions.MatchCollection mColl = System.Text.RegularExpressions.Regex.Matches(s, this.tBoxFind.Text);
foreach (System.Text.RegularExpressions.Match g in mColl)
{
this.richTBox.SelectionColor = Color.White;
this.richTBox.SelectionBackColor = Color.Blue;
this.richTBox.Select(g.Index, g.Length);
}
}
finally
{
this.richTBox.ResumeLayout();
this.Cursor = Cursors.Default;
}
}
The string search is linear. If you find Find method to be slow, maybe you can use third party tool to do the searching for you. All you need is index of the pattern in a string.
Maybe this will help you. You should time the difference and use the faster one.
You're on the right track that Winforms' slow RichTextBox implementation is to blame. You also did well to use the BeginUpdate and EndUpdate methods (I'm guessing you took those from here?). But alas, that isn't enough.
A couple of solutions:
1: Try writing the RTF directly to the textbox. This is a fairly messy, complicated format, but luckily, I have created an answer here which will do the trick.
2: This highly rated external project also looks well worth a look: http://www.codeproject.com/Articles/161871/Fast-Colored-TextBox-for-syntax-highlighting

Is there a quicker way to process a sequence of element than using a loop?

Here I am storing the elements of a datagrid in a string builder using a for loop, but it takes too much time when there is a large number of rows. Is there another way to copy the data in to a string builder in less time?
for (int a = 0; a < grdMass.RowCount; a++)
{
if (a == 0)
{
_MSISDN.AppendLine("'" + grdMass.Rows[a].Cells[0].Value.ToString() + "'");
}
else
{
_MSISDN.AppendLine(",'" + grdMass.Rows[a].Cells[0].Value.ToString() + "'");
}
}
There is no way to improve this code given the information you have provided. This is a simply for loop that appends strings to a StringBuilder - there isn't a whole lot going on here that can be optimized.
This may be one of those cases where something takes a long time simply because you are processing a lot of data. Perhaps there is a way to cache this data so you don't have to generate it as often. Is there anything else you can tell us that would help us find a better way to do this?
Side note: It is very important that you validate your suspicions as to the particular section of code that is causing the slowness. Do this by profiling your code so that you don't spend time trying to fix a problem that exists elsewhere.
As others have said, the StringBuilder is about as fast as you're going to get, so assuming this is the only bit of code that could be causing your slow down, there's probably not much you can do... but you could slightly optimise it by removing the small amount of string concatenation you are doing. I.e:
for (int a = 0; a < grdMass.RowCount; a++)
{
if (a == 0)
{
_MSISDN.Append("'");
}
else
{
_MSISDN.Append(",'");
}
_MSISDN.Append(grdMass.Rows[a].Cells[0].Value);
_MSISDN.AppendLine("'");
}
Edit: You could also clean up the if statement (although I highly doubt it's having a noticable effect) like so:
//First row
if (grdMass.RowCount > 0)
{
_MSISDN.Append("'");
_MSISDN.Append(grdMass.Rows[0].Cells[0].Value);
_MSISDN.AppendLine("'");
}
//Second row onwards
for (int a = 1; a < grdMass.RowCount; a++)
{
_MSISDN.Append(",'");
_MSISDN.Append(grdMass.Rows[a].Cells[0].Value);
_MSISDN.AppendLine("'");
}
I'm suspecting that it's not the string building that takes a long time, perhaps it's accessing the grid elements that is slow.
You could rewrite your code like this:
var cellValues = grdMass.Rows
.Select(r => "'" + r.Cells[0].Value.ToString() + "'")
.ToArray();
return String.Join(",", cellValues);
Now you can verify which part takes the most time. Is it building the cellValues array, or is it the String.Join call?
StringBuilder is pretty much as fast as it gets for building up strings -- and that is pretty goshdarned fast. If StringBuilder is too slow, you are probably trying to process too much data in one go. Are you sure it is really the string building which is slow and not some other part of the processing?
One tip that will speed up StringBuilder for very large strings: set the capacity up front. That is, call the StringBuilder(int) constructor instead of the default constructor, passing an estimate of the number of characters you plan to write. It will still expand if you underestimate -- this just saves the initial "well, 1K wasn't enough, time to allocate another 2K... 4K... etc." But this will make only a small difference, and only if your strings are very long.
This would be better....
if (grdMass.RowCount > 0)
{
_MSISDN.AppendLine("'" + grdMass.Rows[0].Cells[0].Value.ToString() + "'");
for (int a = 1; a < grdMass.RowCount; a++)
{
_MSISDN.AppendLine(",'" + grdMass.Rows[a].Cells[0].Value.ToString() + "'");
}
}

Errata in Extreme Programming Adventures in C#?

I'm trying to work my way through Ron Jeffries's Extreme Programming Adventures in C#. I am stuck, however, in Chapter 3 because the code does not, and cannot, do what the author says it does.
Basically, the text says that I should be able to write some text in a word-wrap enabled text box. If I then move the cursor to an intermediate line and hit enter, the code should re-display the lines before the cursor, add a couple of lines and a set of HTML paragraph tags, then append the rest of the lines. The code doesn't match the text because it uses the textbox.lines property. Well, no matter how many word-wrapped lines there are in a text box, there's only ONE line in the Lines property until you hit a carriage return. So, the statement that the code should, "Copy the rest of the lines into the buffer" appears wrong to me.
I'd appreciate anybody having experience with the book telling me what I'm reading, or doing, wrong!
Thanks.
EoRaptor
Try emailing Ron Jeffries directly. I have the book - somewhere, but I don't remember it not working. His email address is ronjeffries at acm dot org and put [Ron] in the subject line.
(And for those wondering - his email info was right from his website Welcome page)
I've also just started this book and had exactly the same problem although the code you have included looks further along than where I am.
The 'subscript out of range' occurred for 2 reasons, first as Ron explains he was just testing and so returned a hard-coded value of 3 before he wrote the CursorLine() function, which means you I think at least 4? lines of text which as you say need to be pasted in, or maybe set the text to this value before running, also as you say they need to have carriage returns to make txtbox.Lines return an array of strings.
The second reason occurs even after CursorLine() has been implemented but only happens if the text box is empty as txtbox.Lines returns string[0] but I think Ron is implementing a 'User Story' which says that when text has been entered and user presses enter, so not sure if he fixes this later, but will probably find out!
The author's do state that they are learning C# and will show the development wart's and all, which is one of the reasons I have chosen to study this book as I think it is encouraging me to develop projects. I also try to do the code first before looking at his solutions to see if I'm thinking the same way, but maybe I know C# a little better than I give myself credit for, or I'm completly crap, but I've noticed a few things, first he says that overriding OnKeyDown() doesn't work, but I think he must have got confused and tried to do in Form, instead of deriving from TextBox and overriding there.
This was my code when reading the 'User Story':
int curPos = txtbox.SelectionStart;
string Wrd = Environment.NewLine + "<P></P>" + Environment.NewLine;
txtbox.SelectedText = Wrd;
int pl = Environment.NewLine.Length + 3; // "<P>" length is 3
// Put text cursor inbetween <P> tags
txtbox.SelectionStart = curPos + pl;
It works differently to Ron's code, but was just my interpretation of the 'User Story' and not sure how should act if text is selected or wether to split line if text cursor in the middle etc.
Also in 'My Adventures' in Extreme Programming Adventures in C#
txtbox.GetLineFromCharIndex(txtbox.SelectionStart)
gets the cursor line position and doesn't matter if no carriage returns or resized,
as far as I can tell, I done little test with:
txtbox.GetLineFromCharIndex(txtbox.TextLength)
which returns the total amount of lines, which will vary if you resize the text box.
Using C# I always look for solutions which already exsist and people may slate me for this but I think MS have created a great language with great components which do what you expect them to do, so don't have to re-create the wheel each time.
Although like I say it's early days in this book and perhaps these simple solutions aren't extensible enough and maybe Ron's taking that into account, although he did mention just get it working then worry about that later is more the XP way.
Warren.
print("using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace NotepadOne {
public class TextModel {
private String[] lines;
private int selectionStart;
private int cursorPosition;
public TextModel() {
}
public String[] Lines {
get {
return lines;
}
set {
lines = value;
}
}
public int SelectionStart {
get {
return selectionStart;
}
set {
selectionStart = value;
}
}
public int CursorPosition {
get {
return cursorPosition;
}
set {
cursorPosition = value;
}
}
public void InsertControlPText() {
lines[lines.Length - 1] += "ControlP";
}
public void InsertParagraphTags() {
int cursorLine = CursorLine();
String[] newlines = new String[lines.Length + 2];
for (int i = 0; i <= cursorLine; i++) {
newlines[i] = lines[i];
}
newlines[cursorLine + 1] = "";
newlines[cursorLine + 2] = "<P></P>";
for (int i = cursorLine + 1; i < lines.Length; i++) {
newlines[i + 2] = lines[i];
}
lines = newlines;
selectionStart = NewSelectionStart(cursorLine + 2);
}
private int CursorLine() {
int length = 0;
int lineNr = 0;
foreach (String s in lines) {
if (length <= SelectionStart && SelectionStart <= length + s.Length + 2) {
break;
length += s.Length + Environment.NewLine.Length;
lineNr++;
}
lineNr++;
}
return lineNr;
}
private int NewSelectionStart(int cursorLine) {
int length = 0;
for (int i = 0; i < cursorLine; i++) {
length += lines[i].Length + Environment.NewLine.Length;
}
return length + 3;
}
}
}
");
The InsertParagraphTags method is called by pressing the enter key in the textbox.
BTW, the break here is that there is a subscript out of range error if you try to hit enter at the end of the text. I'm sure I could figure out how to get around this but then my code won't look like his code; which is what I'm trying to learn.
Randy

Categories

Resources