Related
I have a string as below
"a1/type/xyz/parts"
The part where 'xyz' exists is dynamic and varies accordingly at any size. I want to compare just the two strings are equal discarding the 'xyz' portion exactly.
For example I have string as below
"a1/type/abcd/parts"
Then my comparison has to be successful
I tried with regular expression as below. Though my knowledge on regular expressions is limited and it did not work. Probably something wrong in the way I used.
var regex = #"^[a-zA-Z]{2}/\[a-zA-Z]{16}/\[0-9a-zA-Z]/\[a-z]{5}/$";
var result = Regex.Match("mystring", regex).Success;
Another idea is to get substring of first and last part omitting the unwanted portion and comparing it.
The comparison should be successful by discarding certain portion of the string with effective code.
Comparison successful cases
string1: "a1/type/21412ghh/parts"
string2: "a1/type/eeeee122ghh/parts"
Comparison failure cases:
string1: "a1/type/21412ghh/parts"
string2: "a2/type/eeeee122ghh/parts/mm"
In short "a1/type/abcd/parts" in this part of string the non-bold part is static always.
Honestly, you could do this using regex, and pull apart the string. But you have a specified delimiter, just use String.Split:
bool AreEqualAccordingToMyRules(string input1, string input2)
{
var split1 = input1.Split('/');
var split2 = input2.Split('/');
return split1.Length == split2.Length // strings must have equal number of sections
&& split1[0] == split2[0] // section 1 must match
&& split1[1] == split2[1] // section 2 must match
&& split1[3] == split2[3] // section 4 must match
}
You can try Split (to get parts) and Linq (to exclude 3d one)
using System.Linq;
...
string string1 = "a1/type/xyz/parts";
string string2 = "a1/type/abcd/parts";
bool result = string1
.Split('/') // string1 parts
.Where((v, i) => i != 2) // all except 3d one
.SequenceEqual(string2 // must be equal to
.Split('/') // string2 parts
.Where((v, i) => i != 2)); // except 3d one
Here's a small programm using string functions to compare the parts before and after the middle part:
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(CutOutMiddle("a1/type/21412ghh/parts"));
Console.WriteLine("True: " + CompareNoMiddle("a1/type/21412ghh/parts", "a1/type/21412ghasdasdh/parts"));
Console.WriteLine("False: " + CompareNoMiddle("a1/type/21412ghh/parts", "a2/type/21412ghh/parts/someval"));
Console.WriteLine("False: " + CompareNoMiddle("a1/type/21412ghh/parts", "a1/type/21412ghasdasdh/parts/someappendix"));
}
private static bool CompareNoMiddle(string s1, string s2)
{
var s1CutOut = CutOutMiddle(s1);
var s2CutOut = CutOutMiddle(s2);
return s1CutOut == s2CutOut;
}
private static string CutOutMiddle(string val)
{
var fistSlash = val.IndexOf('/', 0);
var secondSlash = val.IndexOf('/', fistSlash+1);
var thirdSlash = val.IndexOf('/', secondSlash+1);
var firstPart = val.Substring(0, secondSlash);
var secondPart = val.Substring(thirdSlash, val.Length - thirdSlash);
return firstPart + secondPart;
}
}
returns
a1/type/parts
True: True
False: False
False: False
This solution should cover your case, as said by others, if you have a delimiter use it. In the function below you could change int skip for string ignore or something similar and within the comparison loop if(arrayStringOne[i] == ignore) continue;.
public bool Compare(string valueOne, string valueTwo, int skip) {
var delimiterOccuranceOne = valueOne.Count(f => f == '/');
var delimiterOccuranceTwo = valueTwo.Count(f => f == '/');
if(delimiterOccuranceOne == delimiterOccuranceTwo) {
var arrayStringOne = valueOne.Split('/');
var arrayStringTwo = valueTwo.Split('/');
for(int i=0; i < arrayStringOne.Length; ++i) {
if(i == skip) continue; // or instead of an index you could use a string
if(arrayStringOne[i] != arrayStringTwo[i]) {
return false;
}
}
return true;
}
return false;
}
Compare("a1/type/abcd/parts", "a1/type/xyz/parts", 2);
I have some strings containing code for emoji icons, like :grinning:, :kissing_heart:, or :bouquet:. I'd like to process them to remove the emoji codes.
For example, given:
Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:
I want to get this:
Hello , how are you? Are you fine?
I know I can use this code:
richTextBox2.Text = richTextBox1.Text.Replace(":kissing_heart:", "").Replace(":bouquet:", "").Replace(":grinning:", "").ToString();
However, there are 856 different emoji icons I have to remove (which, using this method, would take 856 calls to Replace()). Is there any other way to accomplish this?
You can use Regex to match the word between :anything:. Using Replace with function you can make other validation.
string pattern = #":(.*?):";
string input = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet: Are you super fan, for example. :words not to replace:";
string output = Regex.Replace(input, pattern, (m) =>
{
if (m.ToString().Split(' ').Count() > 1) // more than 1 word and other validations that will help preventing parsing the user text
{
return m.ToString();
}
return String.Empty;
}); // "Hello , how are you? Are you fine? Are you super fan, for example. :words not to replace:"
If you don't want to use Replace that make use of a lambda expression, you can use \w, as #yorye-nathan mentioned, to match only words.
string pattern = #":(\w*):";
string input = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet: Are you super fan, for example. :words not to replace:";
string output = Regex.Replace(input, pattern, String.Empty); // "Hello , how are you? Are you fine? Are you super fan, for example. :words not to replace:"
string Text = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:";
i would solve it that way
List<string> Emoj = new List<string>() { ":kissing_heart:", ":bouquet:", ":grinning:" };
Emoj.ForEach(x => Text = Text.Replace(x, string.Empty));
UPDATE - refering to Detail's Comment
Another approach: replace only existing Emojs
List<string> Emoj = new List<string>() { ":kissing_heart:", ":bouquet:", ":grinning:" };
var Matches = Regex.Matches(Text, #":(\w*):").Cast<Match>().Select(x => x.Value);
Emoj.Intersect(Matches).ToList().ForEach(x => Text = Text.Replace(x, string.Empty));
But i'm not sure if it's that big difference for such short chat-strings and it's more important to have code that's easy to read/maintain. OP's question was about reducing redundancy Text.Replace().Text.Replace() and not about the most efficient solution.
I would use a combination of some of the techniques already suggested. Firstly, I'd store the 800+ emoji strings in a database and then load them up at runtime. Use a HashSet to store these in memory, so that we have a O(1) lookup time (very fast). Use Regex to pull out all potential pattern matches from the input and then compare each to our hashed emoji, removing the valid ones and leaving any non-emoji patterns the user has entered themselves...
public class Program
{
//hashset for in memory representation of emoji,
//lookups are O(1), so very fast
private HashSet<string> _emoji = null;
public Program(IEnumerable<string> emojiFromDb)
{
//load emoji from datastore (db/file,etc)
//into memory at startup
_emoji = new HashSet<string>(emojiFromDb);
}
public string RemoveEmoji(string input)
{
//pattern to search for
string pattern = #":(\w*):";
string output = input;
//use regex to find all potential patterns in the input
MatchCollection matches = Regex.Matches(input, pattern);
//only do this if we actually find the
//pattern in the input string...
if (matches.Count > 0)
{
//refine this to a distinct list of unique patterns
IEnumerable<string> distinct =
matches.Cast<Match>().Select(m => m.Value).Distinct();
//then check each one against the hashset, only removing
//registered emoji. This allows non-emoji versions
//of the pattern to survive...
foreach (string match in distinct)
if (_emoji.Contains(match))
output = output.Replace(match, string.Empty);
}
return output;
}
}
public class MainClass
{
static void Main(string[] args)
{
var program = new Program(new string[] { ":grinning:", ":kissing_heart:", ":bouquet:" });
string output = program.RemoveEmoji("Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:");
Console.WriteLine(output);
}
}
Which results in:
Hello :imadethis:, how are you? Are you fine? This is:a:strange:thing :to type:,
but valid :nonetheless:
You do not have to replace all 856 emoji's. You only have to replace those that appear in the string. So have a look at:
Finding a substring using C# with a twist
Basically you extract all tokens ie the strings between : and : and then replace those with string.Empty()
If you are concerned that the search will return strings that are not emojis such as :some other text: then you could have a hash table lookup to make sure that replacing said found token is appropriate to do.
Finally got around to write something up. I'm combining a couple previously mentioned ideas, with the fact we should only loop over the string once. Based on those requirement, this sound like the perfect job for Linq.
You should probably cache the HashSet. Other than that, this has O(n) performance and only goes over the list once. Would be interesting to benchmark, but this could very well be the most efficient solution.
The approach is pretty straight forwards.
First load all Emoij in a HashSet so we can quickly look them up.
Split the string with input.Split(':') at the :.
Decide if we keep the current element.
If the last element was a match, keep the current element.
If the last element was no match, check if the current element matches.
If it does, ignore it. (This effectively removes the substring from the output).
If it doesn't, append : back and keep it.
Rebuild our string with a StringBuilder.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
static class Program
{
static void Main(string[] args)
{
ISet<string> emojiList = new HashSet<string>(new[] { "kissing_heart", "bouquet", "grinning" });
Console.WriteLine("Hello:grinning: , ho:w: a::re you?:kissing_heart:kissing_heart: Are you fine?:bouquet:".RemoveEmoji(':', emojiList));
Console.ReadLine();
}
public static string RemoveEmoji(this string input, char delimiter, ISet<string> emojiList)
{
StringBuilder sb = new StringBuilder();
input.Split(delimiter).Aggregate(true, (prev, curr) =>
{
if (prev)
{
sb.Append(curr);
return false;
}
if (emojiList.Contains(curr))
{
return true;
}
sb.Append(delimiter);
sb.Append(curr);
return false;
});
return sb.ToString();
}
}
}
Edit: I did something cool using the Rx library, but then realized Aggregate is the IEnumerable counterpart of Scan in Rx, thus simplifying the code even more.
If efficiency is a concern and to avoid processing "false positives", consider rewriting the string using a StringBuilder while skipping the special emoji tokens:
static HashSet<string> emojis = new HashSet<string>()
{
"grinning",
"kissing_heart",
"bouquet"
};
static string RemoveEmojis(string input)
{
StringBuilder sb = new StringBuilder();
int length = input.Length;
int startIndex = 0;
int colonIndex = input.IndexOf(':');
while (colonIndex >= 0 && startIndex < length)
{
//Keep normal text
int substringLength = colonIndex - startIndex;
if (substringLength > 0)
sb.Append(input.Substring(startIndex, substringLength));
//Advance the feed and get the next colon
startIndex = colonIndex + 1;
colonIndex = input.IndexOf(':', startIndex);
if (colonIndex < 0) //No more colons, so no more emojis
{
//Don't forget that first colon we found
sb.Append(':');
//Add the rest of the text
sb.Append(input.Substring(startIndex));
break;
}
else //Possible emoji, let's check
{
string token = input.Substring(startIndex, colonIndex - startIndex);
if (emojis.Contains(token)) //It's a match, so we skip this text
{
//Advance the feed
startIndex = colonIndex + 1;
colonIndex = input.IndexOf(':', startIndex);
}
else //No match, so we keep the normal text
{
//Don't forget the colon
sb.Append(':');
//Instead of doing another substring next loop, let's just use the one we already have
sb.Append(token);
startIndex = colonIndex;
}
}
}
return sb.ToString();
}
static void Main(string[] args)
{
List<string> inputs = new List<string>()
{
"Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:",
"Tricky test:123:grinning:",
"Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:"
};
foreach (string input in inputs)
{
Console.WriteLine("In <- " + input);
Console.WriteLine("Out -> " + RemoveEmojis(input));
Console.WriteLine();
}
Console.WriteLine("\r\n\r\nPress enter to exit...");
Console.ReadLine();
}
Outputs:
In <- Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:
Out -> Hello , how are you? Are you fine?
In <- Tricky test:123:grinning:
Out -> Tricky test:123
In <- Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:
Out -> Hello :imadethis:, how are you? Are you fine? This is:a:strange:thing :to type:, but valid :nonetheless:
Use this code I put up below I think using this function your problem will be solved.
string s = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:";
string rmv = ""; string remove = "";
int i = 0; int k = 0;
A:
rmv = "";
for (i = k; i < s.Length; i++)
{
if (Convert.ToString(s[i]) == ":")
{
for (int j = i + 1; j < s.Length; j++)
{
if (Convert.ToString(s[j]) != ":")
{
rmv += s[j];
}
else
{
remove += rmv + ",";
i = j;
k = j + 1;
goto A;
}
}
}
}
string[] str = remove.Split(',');
for (int x = 0; x < str.Length-1; x++)
{
s = s.Replace(Convert.ToString(":" + str[x] + ":"), "");
}
Console.WriteLine(s);
Console.ReadKey();
I'd use extension method like this:
public static class Helper
{
public static string MyReplace(this string dirty, char separator)
{
string newText = "";
bool replace = false;
for (int i = 0; i < dirty.Length; i++)
{
if(dirty[i] == separator) { replace = !replace ; continue;}
if(replace ) continue;
newText += dirty[i];
}
return newText;
}
}
Usage:
richTextBox2.Text = richTextBox2.Text.MyReplace(':');
This method show be better in terms of performance compare to one with Regex
I would split the text with the ':' and then build the string excluding the found emoji names.
const char marker = ':';
var textSections = text.Split(marker);
var emojiRemovedText = string.Empty;
var notMatchedCount = 0;
textSections.ToList().ForEach(section =>
{
if (emojiNames.Contains(section))
{
notMatchedCount = 0;
}
else
{
if (notMatchedCount++ > 0)
{
emojiRemovedText += marker.ToString();
}
emojiRemovedText += section;
}
});
In C#, how do I compare the characters in two strings.
For example, let's say I have these two strings
"bc3231dsc" and "bc3462dsc"
How do I programically figure out the the strings
both start with "bc3" and end with "dsc"?
So the given would be two variables:
var1 = "bc3231dsc";
var2 = "bc3462dsc";
After comparing each characters from var1 to var2, I would want the output to be:
leftMatch = "bc3";
center1 = "231";
center2 = "462";
rightMatch = "dsc";
Conditions:
1. The strings will always be a length of 9 character.
2. The strings are not case sensitive.
The string class has 2 methods (StartsWith and Endwith) that you can use.
After reading your question and the already given answers i think there are some constraints are missing, which are maybe obvious to you, but not to the community. But maybe we can do a little guess work:
You'll have a bunch of string pairs that should be compared.
The two strings in each pair are of the same length or you are only interested by comparing the characters read simultaneously from left to right.
Get some kind of enumeration that tells me where each block starts and how long it is.
Due to the fact, that a string is only a enumeration of chars you could use LINQ here to get an idea of the matching characters like this:
private IEnumerable<bool> CommonChars(string first, string second)
{
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
var charsToCompare = first.Zip(second, (LeftChar, RightChar) => new { LeftChar, RightChar });
var matchingChars = charsToCompare.Select(pair => pair.LeftChar == pair.RightChar);
return matchingChars;
}
With this we can proceed and now find out how long each block of consecutive true and false flags are with this method:
private IEnumerable<Tuple<int, int>> Pack(IEnumerable<bool> source)
{
if (source == null)
throw new ArgumentNullException("source");
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
bool current = iterator.Current;
int index = 0;
int length = 1;
while (iterator.MoveNext())
{
if(current != iterator.Current)
{
yield return Tuple.Create(index, length);
index += length;
length = 0;
}
current = iterator.Current;
length++;
}
yield return Tuple.Create(index, length);
}
}
Currently i don't know if there is an already existing LINQ function that provides the same functionality. As far as i have already read it should be possible with SelectMany() (cause in theory you can accomplish any LINQ task with this method), but as an adhoc implementation the above was easier (for me).
These functions could then be used in a way something like this:
var firstString = "bc3231dsc";
var secondString = "bc3462dsc";
var commonChars = CommonChars(firstString, secondString);
var packs = Pack(commonChars);
foreach (var item in packs)
{
Console.WriteLine("Left side: " + firstString.Substring(item.Item1, item.Item2));
Console.WriteLine("Right side: " + secondString.Substring(item.Item1, item.Item2));
Console.WriteLine();
}
Which would you then give this output:
Left side: bc3
Right side: bc3
Left side: 231
Right side: 462
Left side: dsc
Right side: dsc
The biggest drawback is in someway the usage of Tuple cause it leads to the ugly property names Item1 and Item2 which are far away from being instantly readable. But if it is really wanted you could introduce your own simple class holding two integers and has some rock-solid property names. Also currently the information is lost about if each block is shared by both strings or if they are different. But once again it should be fairly simply to get this information also into the tuple or your own class.
static void Main(string[] args)
{
string test1 = "bc3231dsc";
string tes2 = "bc3462dsc";
string firstmatch = GetMatch(test1, tes2, false);
string lasttmatch = GetMatch(test1, tes2, true);
string center1 = test1.Substring(firstmatch.Length, test1.Length -(firstmatch.Length + lasttmatch.Length)) ;
string center2 = test2.Substring(firstmatch.Length, test1.Length -(firstmatch.Length + lasttmatch.Length)) ;
}
public static string GetMatch(string fist, string second, bool isReverse)
{
if (isReverse)
{
fist = ReverseString(fist);
second = ReverseString(second);
}
StringBuilder builder = new StringBuilder();
char[] ar1 = fist.ToArray();
for (int i = 0; i < ar1.Length; i++)
{
if (fist.Length > i + 1 && ar1[i].Equals(second[i]))
{
builder.Append(ar1[i]);
}
else
{
break;
}
}
if (isReverse)
{
return ReverseString(builder.ToString());
}
return builder.ToString();
}
public static string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
Pseudo code of what you need..
int stringpos = 0
string resultstart = ""
while not end of string (either of the two)
{
if string1.substr(stringpos) == string1.substr(stringpos)
resultstart =resultstart + string1.substr(stringpos)
else
exit while
}
resultstart has you start string.. you can do the same going backwards...
Another solution you can use is Regular Expressions.
Regex re = new Regex("^bc3.*?dsc$");
String first = "bc3231dsc";
if(re.IsMatch(first)) {
//Act accordingly...
}
This gives you more flexibility when matching. The pattern above matches any string that starts in bc3 and ends in dsc with anything between except a linefeed. By changing .*? to \d, you could specify that you only want digits between the two fields. From there, the possibilities are endless.
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
class Sample {
static public void Main(){
string s1 = "bc3231dsc";
string s2 = "bc3462dsc";
List<string> common_str = commonStrings(s1,s2);
foreach ( var s in common_str)
Console.WriteLine(s);
}
static public List<string> commonStrings(string s1, string s2){
int len = s1.Length;
char [] match_chars = new char[len];
for(var i = 0; i < len ; ++i)
match_chars[i] = (Char.ToLower(s1[i])==Char.ToLower(s2[i]))? '#' : '_';
string pat = new String(match_chars);
Regex regex = new Regex("(#+)", RegexOptions.Compiled);
List<string> result = new List<string>();
foreach (Match match in regex.Matches(pat))
result.Add(s1.Substring(match.Index, match.Length));
return result;
}
}
for UPDATE CONDITION
using System;
class Sample {
static public void Main(){
string s1 = "bc3231dsc";
string s2 = "bc3462dsc";
int len = 9;//s1.Length;//cond.1)
int l_pos = 0;
int r_pos = len;
for(int i=0;i<len && Char.ToLower(s1[i])==Char.ToLower(s2[i]);++i){
++l_pos;
}
for(int i=len-1;i>0 && Char.ToLower(s1[i])==Char.ToLower(s2[i]);--i){
--r_pos;
}
string leftMatch = s1.Substring(0,l_pos);
string center1 = s1.Substring(l_pos, r_pos - l_pos);
string center2 = s2.Substring(l_pos, r_pos - l_pos);
string rightMatch = s1.Substring(r_pos);
Console.Write(
"leftMatch = \"{0}\"\n" +
"center1 = \"{1}\"\n" +
"center2 = \"{2}\"\n" +
"rightMatch = \"{3}\"\n",leftMatch, center1, center2, rightMatch);
}
}
string sentence = "We know it contains 'camel' word.";
// Camel can be in different cases:
string s1 = "CAMEL";
string s2 = "CaMEL";
string s3 = "CAMeL";
// ...
string s4 = "Camel";
// ...
string s5 = "camel";
How to replace 'camel' in sentence with 'horse' despite of string.Replace doesn't support ignoreCase on left string?
Use a regular expression:
var regex = new Regex( "camel", RegexOptions.IgnoreCase );
var newSentence = regex.Replace( sentence, "horse" );
Of course, this will also match words containing camel, but it's not clear if you want that or not.
If you need exact matches you can use a custom MatchEvaluator.
public static class Evaluators
{
public static string Wrap( Match m, string original, string format )
{
// doesn't match the entire string, otherwise it is a match
if (m.Length != original.Length)
{
// has a preceding letter or digit (i.e., not a real match).
if (m.Index != 0 && char.IsLetterOrDigit( original[m.Index - 1] ))
{
return m.Value;
}
// has a trailing letter or digit (i.e., not a real match).
if (m.Index + m.Length != original.Length && char.IsLetterOrDigit( original[m.Index + m.Length] ))
{
return m.Value;
}
}
// it is a match, apply the format
return string.Format( format, m.Value );
}
}
Used with the previous example to wrap the match in a span as:
var regex = new Regex( highlightedWord, RegexOptions.IgnoreCase );
foreach (var sentence in sentences)
{
var evaluator = new MatchEvaluator( match => Evaluators.Wrap( match, sentence, "<span class='red'>{0}</span>" ) );
Console.WriteLine( regex.Replace( sentence, evaluator ) );
}
Add an extension method for string to do the trick:
Usage:
string yourString = "TEXTTOREPLACE";
yourString.Replace("texttoreplace", "Look, I Got Replaced!", StringComparison.OrdinalIgnoreCase);
Code:
using System;
using System.Collections.Generic;
using System.IO;
public static class Extensions
{
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
// Determine if we found a match
bool MatchFound = index >= 0;
if (MatchFound)
{
// Remove the old text
source = source.Remove(index, oldString.Length);
// Add the replacemenet text
source = source.Insert(index, newString);
}
// recurse for multiple instances of the name
if (source.IndexOf(oldString, comp) != -1)
{
source = Replace(source, oldString, newString, comp);
}
return source;
}
}
Here's an extension method taking a StringComparison, using string.IndexOf:
[Pure]
public static string Replace(this string source, string oldValue, string newValue, StringComparison comparisonType)
{
if (source.Length == 0 || oldValue.Length == 0)
return source;
var result = new System.Text.StringBuilder();
int startingPos = 0;
int nextMatch;
while ((nextMatch = source.IndexOf(oldValue, startingPos, comparisonType)) > -1)
{
result.Append(source, startingPos, nextMatch - startingPos);
result.Append(newValue);
startingPos = nextMatch + oldValue.Length;
}
result.Append(source, startingPos, source.Length - startingPos);
return result.ToString();
}
Btw, here's also a similar Contains-method also taking a StringComparison:
[Pure]
public static bool Contains(this string source, string value, StringComparison comparisonType)
{
return source.IndexOf(value, comparisonType) >= 0;
}
Some tests:
[TestFixture]
public class ExternalTests
{
private static string[] TestReplace_args =
{
"ab/B/c/ac",
"HELLO World/Hello/Goodbye/Goodbye World",
"Hello World/world/there!/Hello there!",
"hello WoRlD/world/there!/hello there!",
"///",
"ab///ab",
"/ab/cd/",
"a|b|c|d|e|f/|//abcdef",
"a|b|c|d|e|f|/|/:/a:b:c:d:e:f:",
};
[Test, TestCaseSource("TestReplace_args")]
public void TestReplace(string teststring)
{
var split = teststring.Split("/");
var source = split[0];
var oldValue = split[1];
var newValue = split[2];
var result = split[3];
Assert.That(source.Replace(oldValue, newValue, StringComparison.OrdinalIgnoreCase), Is.EqualTo(result));
}
}
Here is my extension method, which combines Tom Beech's, with the recursiveness of sntbob's, and a cleaner fix to the bug that ksun pointed out.
Code:
public static string Replace(this string source, string oldString,
string newString, StringComparison comparison)
{
int index = source.IndexOf(oldString, comparison);
while (index > -1)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
index = source.IndexOf(oldString, index + newString.Length, comparison);
}
return source;
}
Usage:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase));
Result:
bbananabananaa
And, if you still want the recursive nature to be optional:
Code:
public static string Replace(this string source, string oldString,
string newString, StringComparison comparison,
bool recursive = true)
{
int index = source.IndexOf(oldString, comparison);
while (index > -1)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
if (!recursive)
{
return source;
}
index = source.IndexOf(oldString, index + newString.Length, comparison);
}
return source;
}
Usage:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase, false));
Result:
bbananaana
Utiltize StringComparison because of its handy OrdinalIgnoreCase
string sentence = "We know it contains 'camel' word.";
string wordToFind = "camel";
string replacementWord = "horse";
int index = sentence.IndexOf(wordToFind , StringComparison.OrdinalIgnoreCase)
// Did we match the word regardless of case
bool match = index >= 0;
// perform the replace on the matched word
if(match) {
sentence = sentence.Remove(index, wordToFind.Length)
sentence = sentence.Insert(index, replacementWord)
}
Sure would be nice if the C# String class had an ignoreCase() method like Java.
You could also use String.IndexOf
http://msdn.microsoft.com/en-us/library/system.string.indexof.aspx
You may get slightly better performance doing it this way than with RegExpressions (I abhor them because they're not intuitive and easy to screw up, although this simple .Net function call abstracts the actual messy RegEx, and doesn't provide much room for error), but that's probably not a concern for you; computers are REALLY fast these days, right? :) The overload for IndexOf that takes a StringComparison object allows you to optionally ignore case, and because IndexOf returns the first occurrence from at a specified position, you'll have to code a loop to process a string having multiple occurrences.
public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replace0nce)
{
StringComparison sc = StringComparison.OrdinalIgnoreCase;
if (matchCase)
sc = StringComparison.Ordinal;
int pos;
while ((pos = srcText.IndexOf(toFind, sc)) > -1)
{
srcText = srcText.Remove(pos, toFind.Length);
srcText = srcText.Insert(pos, toReplace);
if (replace0nce)
break;
}
return srcText;
}
It may not be as efficient as some of the other answers, but I kind of like the CustomReplace function written by sntbob.
However, there is a flaw in it. If the text replacement is recursive it will cause an infinite loop. For example, CustomReplace("I eat bananas!","an","banana",false,false) would cause an infinite loop and the string would continue growing larger.
For example, after the 4th iteration the string would be "I eat bbbbbananaanaanaanaanas!"
If you want to only replace the two instances of "an" inside "banana" then you'll have to take another approach. I modified sntbob's code to account for this case. I admit that it's much more convoluted, but it handles recursive replacements.
public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replaceOnce)
{
StringComparison sc = StringComparison.OrdinalIgnoreCase;
if (matchCase)
sc = StringComparison.Ordinal;
int pos;
int previousProcessedLength = 0;
string alreadyProcessedTxt = "";
string remainingToProcessTxt = srcText;
while ((pos = remainingToProcessTxt.IndexOf(toFind, sc)) > -1)
{
previousProcessedLength = alreadyProcessedTxt.Length;
//Append processed text up until the end of the found string and perform replacement
alreadyProcessedTxt += remainingToProcessTxt.Substring(0, pos + toFind.Length);
alreadyProcessedTxt = alreadyProcessedTxt.Remove(previousProcessedLength + pos, toFind.Length);
alreadyProcessedTxt = alreadyProcessedTxt.Insert(previousProcessedLength + pos, toReplace);
//Remove processed text from remaining
remainingToProcessTxt = remainingToProcessTxt.Substring(pos + toFind.Length);
if (replaceOnce)
break;
}
return alreadyProcessedTxt + remainingToProcessTxt;
}
Why not just import the Microsoft.VisualBasic namespace and use the VB Strings.Replace method?
https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.replace(v=vs.110).aspx
eg
var newString = Strings.Replace(SourceString, FindTextValue, ReplacementTextValue, 1, -1, Constants.vbTextCompare);
vbTextCompare forces a case-insensitive replacement. Job done.
Okay, it's not 'pure' C#, but it gets you to where you want to go with much less complexity and messing around.
Here's one more alternative that uses StringComparison as an extension method. on a StringBuilder object. I've read some articles indicating that a StringBuilder might be a little more efficient with memory than using strings. You can easily alter this to work with strings if that's what you need.
/// <summary>
/// Extension method to find/replace replaces text in a StringBuilder object
/// </summary>
/// <param name="original">Source StringBuilder object</param>
/// <param name="oldString">String to search for</param>
/// <param name="newString">String to replace each occurrance of oldString</param>
/// <param name="stringComparison">String comparison to use</param>
/// <returns>Original Stringbuilder with replacements made</returns>
public static StringBuilder Replace(this StringBuilder original,
string oldString, string newString, StringComparison stringComparison)
{
//If anything is null, or oldString is blank, exit with original value
if ( newString == null || original == null || string.IsNullOrEmpty(oldString))
return original;
//Convert to a string and get starting position using
//IndexOf which allows us to use StringComparison.
int pos = original.ToString().IndexOf(oldString, 0, stringComparison);
//Loop through until we find and replace all matches
while ( pos >= 0 )
{
//Remove the old string and insert the new one.
original.Remove(pos, oldString.Length).Insert(pos, newString);
//Get the next match starting 1 character after last replacement (to avoid a possible infinite loop)
pos = original.ToString().IndexOf(oldString, pos + newString.Length + 1, stringComparison);
}
return original;
}
Is there a fast way (without having to explicitly looping through each character in a string) and either stripping or keeping it. In Visual FoxPro, there is a function CHRTRAN() that does it great. Its on a 1:1 character replacement, but if no character in the alternate position, its stripped from the final string. Ex
CHRTRAN( "This will be a test", "it", "X" )
will return
"ThXs wXll be a es"
Notice the original "i" is converted to "X", and lower case "t" is stripped out.
I looked at the replace for similar intent, but did not see an option to replace with nothing.
I'm looking to make some generic routines to validate multiple origins of data that have different types of input restrictions. Some of the data may be coming from external sources, so its not just textbox entry validation I need to test for.
Thanks
All you need is a couple of calls to String.Replace().
string s = "This will be a test";
s = s.Replace("i", "X");
s = s.Replace("t", "");
Note that Replace() returns a new string. It does not alter the string itself.
Does this what you want?
"This will be a test".Replace("i", "X").Replace("t", String.Empty)
Here is a simple implementation of the CHRTRAN function - it does not work if the string contains \0 and is quite messy. You could write a nicer one using loops, but I just wanted to try it using LINQ.
public static String ChrTran(String input, String source, String destination)
{
return source.Aggregate(
input,
(current, symbol) => current.Replace(
symbol,
destination.ElementAtOrDefault(source.IndexOf(symbol))),
preResult => preResult.Replace("\0", String.Empty));
}
And the you can use it.
// Returns "ThXs wXll be a es"
String output = ChrTran("This will be a test", "it", "X");
Just to have a clean solution - the same without LINQ and working for the \0 cases, too, and it is almost in place because of using a StringBuilder but won't modify the input, of course.
public static String ChrTran(String input, String source, String destination)
{
StringBuilder result = new StringBuilder(input);
Int32 minLength = Math.Min(source.Length, destination.Length);
for (Int32 i = 0; i < minLength; i++)
{
result.Replace(source[i], destination[i]);
}
for (Int32 i = minLength; i < searchPattern.Length; i++)
{
result.Replace(source[i].ToString(), String.Empty);
}
return result.ToString();
}
Null reference handling is missing.
Inspired by tvanfosson's solution, I gave LINQ a second shot.
public static String ChrTran(String input, String source, String destination)
{
return new String(input.
Where(symbol =>
!source.Contains(symbol) ||
source.IndexOf(symbol) < destination.Length).
Select(symbol =>
source.Contains(symbol)
? destination[source.IndexOf(symbol)]
: symbol).
ToArray());
}
Here was my final function and works perfectly as expected.
public static String ChrTran(String ToBeCleaned,
String ChangeThese,
String IntoThese)
{
String CurRepl = String.Empty;
for (int lnI = 0; lnI < ChangeThese.Length; lnI++)
{
if (lnI < IntoThese.Length)
CurRepl = IntoThese.Substring(lnI, 1);
else
CurRepl = String.Empty;
ToBeCleaned = ToBeCleaned.Replace(ChangeThese.Substring(lnI, 1), CurRepl);
}
return ToBeCleaned;
}
This is a case where I think using LINQ overcomplicates the matter. This is simple and to the point:
private static string Translate(string input, string from, string to)
{
StringBuilder sb = new StringBuilder();
foreach (char ch in input)
{
int i = from.IndexOf(ch);
if (from.IndexOf(ch) < 0)
{
sb.Append(ch);
}
else
{
if (i >= 0 && i < to.Length)
{
sb.Append(to[i]);
}
}
}
return sb.ToString();
}
To "replace with nothing", just replace with an empty string. This will give you:
String str = "This will be a test";
str = str.Replace("i", "X");
str = str.Replace("t","");
A more general version as a string extension. Like the others this does not do a translation in place since strings are immutable in C#, but instead returns a new string with the replacements as specified.
public static class StringExtensions
{
public static string Translate( this string source, string from, string to )
{
if (string.IsNullOrEmpty( source ) || string.IsNullOrEmpty( from ))
{
return source;
}
return string.Join( "", source.ToCharArray()
.Select( c => Translate( c, from, to ) )
.Where( c => c != null )
.ToArray() );
}
private static string Translate( char c, string from, string to )
{
int i = from != null ? from.IndexOf( c ) : -1;
if (i >= 0)
{
return (to != null && to.Length > i)
? to[i].ToString()
: null;
}
else
{
return c.ToString();
}
}
}