Comparing rapidly-changing string values to find frequent occurrences - c#

My problem should be quite simple. I have a random generated string that changes multiple times per second. I want to return 1 if it appears x times consecutively.
My current code:
string s; //this is the generated string
int checker = 0;
string[] s_list = null;
if( cheaker == 0)
{
s_list[0] = s;
}
else if( cheaker == 1)
{
s_list[1] = s;
}
checker++;
if(s_list[0] == s_list[1]) return 1;
My problem is that I want to be able to change the amount of x times if appears and like this it will generate tons of code if the x is too big.
Do you think putting the current string into an array of string and compare them is the best way? There should be a better implementation.

To make the code generic for any given X, you should keep last X strings and check whether all they are equal, e.g.:
List<string> list = new List<string>();
if (list.Count >= X)
{
list.RemoveAt(0);
}
list.Add(newString);
return list.Count >= X && list.Any(s => s == list[0]);

I assume s_list is an array where you store all the generations of the string
string s;
string[] s_list;
// Your logic that would generate s and store it in s_list
// ...
// ...
int required_amount = 10; // whatever X amount you want
int current_sequence = 0;
// It's important to start at 1, and not 0,
// as you compare to the previous entry each time
for(int i = 1; i < s_list.Lenght; i++ )
{
if( s_list[i] == s_list[i-1] )
{
current_sequence++;
if(current_sequence >= required_amount)
{
return 1;
}
}
else
{
current_sequence = 0;
}
}

Related

find the first element of an array that is not consecutive using web forms

E.g. If we have an array [1,2,3,4,6,7,8] then 1 then 2 then 3 then 4 are all consecutive but 6 is not, so that's the first non-consecutive number.
If the whole array is consecutive then return null .
The array will always have at least 2 elements 1 and all elements will be numbers. The numbers will also all be unique and in ascending order. The numbers could be positive or negative and the first non-consecutive could be either too. please help me finish this code i am new in programming. My code:
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _2katas
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var input = this.txtInput.Text;
var numarray = input.Split(',');
int firstValue = Convert.ToInt32(numarray[0]);
for (var i = 0; i < numarray.Length; i++)
{
if (Convert.ToInt32(numarray[i]) - i != firstValue)
{
lblPrint.Text = "";
}
else
{
lblPrint.Text = "";
}
if (this.rdbConsecutive.Checked == true)
{
lblKataRunning.Text = "Consecutive";
}
else if (this.rdbStripCleaning.Checked == true)
{
lblKataRunning.Text = "Strip Cleaning";
}
}
}
}
}
Let's extract a method:
Find the first element of an array that is not consecutive ...
If the whole array is consecutive then return null
We can implement it like this:
private static string FirstInconsecutive(string data) {
var array = data.Split(',');
if (array.Length <= 0)
return null; //TODO: what shall we return on empty array?
if (!int.TryParse(array[0], out int delta))
return array[0];
for (int i = 1; i < array.Length; ++i)
if (!int.TryParse(array[i], out int value) || value - i != delta)
return array[i];
return null;
}
Usage:
string result = FirstInconsecutive(txtInput.Text);
Please note int.TryParse which helps to return the right answer "ABC" on an input like "1, 2, 3, ABC, 4, 6, 7, 8" (user input txtInput.Text can contain any string)
A linq solution just for the fun of it:
static int? FindFirstNonConsecutive(IEnumerable<int> arr)
{
var nonConsecutiveInfo =
arr.Select((i, index) => new {Index = index, Delta = i - index})
.FirstOrDefault(t => t.Delta != arr.First());
return nonConsecutiveInfo?.Delta + nonConsecutiveInfo?.Index;
}
Note that this will only work finding non consecutive numbers in ascending order as per requirements.
Two numbers are not consecutive if the left ones + 1 <> the right one.
Check with something like this, note that you have to change your boundary checks:
for (var i = 0; i < numarray.Length - 1; i++)
{
if (Convert.ToInt32(numarray[i]) + 1 != Convert.ToInt32(numarray[i+1]))
Update your condition as below for loop and it will work. I would suggest you to have separate function so that it could be reusable if needed elsewhere in code.
Here start your loop from i = 1 and compare numarray[i-1] + 1 != numarray[i] values.
You can convert your sting[] to int[] with var numarray = input.Split(',').Select(x => Convert.ToInt32(x)).ToArray(); and use it with IsConsecutive(numarray) as per button1_Click code.
You can get first non-consecutive value with minor modification in return type and return statement as shown in GetFirstNonConsecutiveValue().
public bool IsConsecutive(int[] numarray)
{
for (int i = 1; i < numarray.Length; i++)
{
if (numarray[i-1] + 1 != numarray[i])
{
return false;
}
}
return true;
}
public int? GetFirstNonConsecutiveValue(int[] numarray)
{
for (int i = 1; i < numarray.Length; i++)
{
if (numarray[i-1] + 1 != numarray[i])
{
return numarray[i];
}
}
return null;
}
private void button1_Click(object sender, EventArgs e)
{
var input = this.txtInput.Text;
var numarray = input.Split(',').Select(x => Convert.ToInt32(x)).ToArray();
var nonConsecutiveValue = GetFirstNonConsecutiveValue(numarray);
if (nonConsecutiveValue != null)
{
// nonConsecutiveValue is first non consecutive value.
}
else
{
// sequence is consecutive.
}
}
One way to go.
string rawData = "1,2,3,4,6,7,8,9";
IEnumerable<int> data = rawData.Split(',').Select(v => Convert.ToInt32(v));
IEnumerable<int> all = Enumerable.Range(data.Min(), data.Max() - data.Min() + 1);
IEnumerable<int> diff = all.Except(data);
if (diff.Count() == 0)
{
return null;
}
return data.ElementAt(all.ToList().IndexOf(diff.First()))
NB Not thoroughly tested.
Just test diff for being empty to get the numbers are consecutive

For-loop looping too often because it's not exiting properly (performance)

I currently have an array with a size of one million, and a method that parses new objects (each one with a key and value) or overwrites an object in case a new object has an already existing key. The thing is that the loop for the overwriting part is taking an extremely long time with an array this big because it is not exiting properly. For every element it parses it checks every index, so for a thousand elements to parse it would do a billion checks, which is obviously not desired behaviour.
private const int ARRAY_SIZE = 1000000;
private Paar[] pArr = new Paar[ARRAY_SIZE];
public void Put(string key, object value)
{
bool wasReplaced = false;
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null && pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
wasReplaced = true;
}
}
if (wasReplaced == false)
{
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] == null)
{
pArr[i] = new Paar(key, value);
break;
}
else if (i >= pArr.Length)
{
throw new Exception("All slots full");
}
}
}
}
Edit: Note that I am using two loops to prevent the overwriting function from being able to parse an object with a duplicate key and new value into an empty index if there happens to be one before the duplicate key's index (for example if I set one or more random indexes to null).
string keyparse = "Key";
string valueparse = "Value";
Random rnd = new Random();
int wdh = 1000;
for (int i = 0; i < wdh; i++)
{
myMap.Put(keyparse + rnd.Next(1, 10000), valueparse + rnd.Next(1, 10000));
}
I tried to make the first part of the function add 1 to an int and break if the int reaches the number of elements parsed, but it does not seem to function properly, and neither does it affect the time taken by the function.
bool wasReplaced = false;
int count = 0;
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null)
{
count += 1;
if (pArr[i] != null && pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
count += 1;
break;
}
else if (count == 1000)
{
break;
}
}
}
I don't really know why this wouldn't work or how else I could approach this, so I'm kind of stuck here... Any help is greatly appreciated!
Use a Dictionary - but you write it's for practice ...
In that case:
Speedup 1: (Assuming your key is unique!)
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null && pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
// wasReplaced = true;
return;
}
}
Speedup 2: When traversing in the first for-loop, save the first position of an empty space. Then you can use that instantly and only have to iterate the array once.
int empty = -1;
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null )
{
if (pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
return;
}
}
else if( empty < 0 )
{
empty = i;
}
}
if( pArr >= 0 )
pArr[empty] = new Paar(key,value);
else // Array has been fully traversed without match and without empty space
throw new Exception("All slots full");
Speedup 3: You could keep track of the highest index used, so you can break the for-loop early.
Mind that these measures are only to speed up that part. I did not take into account many other considerations and possible techniques like hashing, thread-safety, etc.
There are several things may done here-
A minor improvement will be break your loop when you set wasReplaced = true
if (pArr[i] != null && pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
wasReplaced = true;
break;
}
But it is not a good solution
Another solution is you may use TPL (Task Parallel Library), to use multithread and multicore. There is a ParallelForEach() loop there.
Another solution is use Dictionary<string, string> to store your values, instead of using an array. Dictionary<> uses hash map, you will get a better performance.
If you still want to use your own implementation with array, then try to use hashing or heap algorithm to store your data. By using hashing or heap you can store/get/update data by O(log(n)) time complexity.
private List<Paar> pArr = new list<Paar>();
public void Put(string key, object value)
{
(from p in pArr
where p!= null && p.key!=key select p).ToList()
.ForEach(x => x.key== key, x.Value==value);
}
Try with Linq Query
As written in a comment, I suggest you to use Dictionary instead of Paar class, so you can do something like:
int ARRAY_SIZE= 100000;
Dictionary<string, object> pArr = new Dictionary<string, object>()
public void Put(string key, string object)
{
if(pArr.ContainsKey(key))
pArr[key] = value;
else
{
if(pArr.Count() >= ARRAY_SIZE)
throw new Exception("All slots full");
pArr.Add(key, value)
}
}
If you need to use Paar class, you can use the .ToDictionary(x => x.key, x => x.value) to work with it.
Given that you don't want to use a dictionary and there may be a match anywhere within the array, then the best you can do is search the whole array until you find a match, and capture the index of the first null item along the way (for an insertion point if a match is not found):
public static void Put(string key, object value)
{
int insertionIndex = -1;
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null)
{
if (pArr[i].Key == key)
{
insertionIndex = i;
break;
}
}
else if (insertionIndex < 0)
{
insertionIndex = i;
}
}
if (insertionIndex < 0)
{
throw new Exception("All slots full");
}
else
{
pArr[insertionIndex] = new Paar(key, value);
}
}
I solved this by declaring an instance variable and a local variable which are then to be compared. I also fixed a logical error I made (now the second loop counts properly and the first loop doesn't count anymore if it overwrites something but instead returns).
The method still functions the same but now runs much more efficiently (took 7 seconds for 5kk parses compared to the previous 12 seconds for 1k parses!).
int _objectCount = 0;
public void Put(string key, object value)
{
int localCount = 0;
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] != null)
{
localCount++;
if (pArr[i].key == key)
{
pArr[i] = new Paar(key, value);
return;
}
}
if (localCount == _objectCount)
{
return;
}
}
for (int i = 0; i < pArr.Length; i++)
{
if (pArr[i] == null)
{
pArr[i] = new Paar(key, value);
_objectCount++;
return;
}
else if (i >= pArr.Length)
{
throw new Exception("All slots full");
}
}
}

A List of Strings, Item Request Array issue

List<string> arguments = new List<string>(Environment.GetCommandLineArgs().Skip(1).Take(4));
int variant = consoleOptions.HandleInput(arguments);
public int HandleInput(List<string> input)
{
int variant = 0;
//for (int i = 0; i < input.Count; i++)
//{
// if (input[i].Contains("-s"))
// {
// variant = 1;
// }
//}
if (input[0].Contains("-s"))
{
variant = 1;
if (!String.IsNullOrWhiteSpace(input[1]) && !String.IsNullOrWhiteSpace(input[2]))
{
variant = 2;
}
if (!String.IsNullOrWhiteSpace(input[3]))
{
variant = 3;
}
}
return variant;
}
I'm starting my application from Commandline.
Then I get a List of Strings (max 4).
What I want to do now is:
if the first String in List is "-s" then variant = 1
if the second and third string isn't Empty then variant = 2
if the fourth string isn't Empty then variant = 3
I tried some things (Code above), but the problem is, if I only get the first String (one item),
The Compiler crashes on other place (checking Second string, cause Index not accessible)..
What would be the best method?
The Take(4) doesn't guarantee that you'll have 4 elements, it just means you won't have more than 4. So you have to check the list length.
if (input.Count >= 1 && input[0].Contains("-s"))
{
return 1;
}
if (input.Count >= 3)
{
return 2;
}
if (input.Count >= 4)
{
return 3;
}
return 0; //what do you return if none of the conditions are met?
you try some thing like this.
if (input[0].Contains("-s"))
{
variant = 1;
}
if (!String.IsNullOrWhiteSpace(input[1]) && !String.IsNullOrWhiteSpace(input[2]))
{
variant = 2;
}
if (!String.IsNullOrWhiteSpace(input[3]))
{
variant = 3;
}
return variant;

Dice Question (Full House and Straight recognition)

I'm making a dice game. There are 5 dice in which I have all the values for and I need to determine if they make up a full house (3 of one and 2 of another), small straight (1-4, 2-6 or 3-6) or a large straight (1-5, 2-6).
Perhaps the best way to approach this seems to be to use regular expressions.
Does anyone know how I would go about representing these rules in regex?
Or if you can provide a better solution, I'd appreciate that.
Examples:
Full house = 44422 or 11166 or 12212 etc.
Small Straight = 12342 or 54532 etc.
Large Straight = 12345 or 52643 etc.
Edit
Changed wording to highlight that this is my inexperienced opinion.
I know how to achieve this using code, but it seems like such a long winded solution, I'm wondering if there's a more simplistic approach.
I would order all the numbers decreasing and then do some linear criteria matching on each value as you go along it whether it be in an int[] or a string.
Don't know about c#, but in a scripting language I'd take the regexp route. For each side, calculate how many times it occurs in the combination and join the results together. For example, for the combination 12342 the counter string will be 121100. Then match the counter string against these patterns:
/5/ = Five of a kind
/4/ = Four of a kind
/20*3|30*2/ = Full house
/1{5}/ = Large Straight
/[12]{4}/ = Small Straight
/3/ = Three of a kind
/2[013]*2/ = Two pair
/2/ = One pair
You could always do a LINQ aggregate query and count the number of same cards. It would be something similar to (can't test it):
var query = from card in hand
group card by card into groupedCards
select new { Card = groupedCards.Key, Count = groupedCards.Count() };
This way you would easily know if you are dealing with a possible straight (or nothing at all), a pair, a triple, etc.
I am no LINQ expert and I can not test this bit of code at the moment, so I am not sure if it will compile as it is, but it might help you or give you an idea on how to approach the problem at hand.
For example:
if count query = 5 : We are dealing with an empty hand, a flush or a straight => Run particular flush/straight logic.
if count query = 4 : We are dealing with a single pair.
if count query = 3 : We are dealing with a double pair or a triple => if max count =3 then triple
if count query = 2 : We are dealing with a full house / poker. If max count = 4 then poker
I won't comment on how you seek the results, but rather on how you store the result for later lookup.
Since you have only 46656 possible combinations and one byte can store the resulting hand strength, this problem is much easier than a poker hand problem.
You can have a lookup table, consisting of hands as indexes and associated with results (some hands can have multiple results) of that hand. Each byte can store all hand types as a binary representation (hopefully, if not use a short).
Each number you get (eg. 66655 - full house) is a number in base six (1-6), convert it into a base 10 number to get the index in the lookup table.
It will require about 46656 bytes (+ CPU alignment), and can fit into CPU L2 cache. Speed would be enourmous, since the only operation you would need to do is convert number base, and the binary OR operation to extract a hand strenght.
What you will miss is the real strength of a hand. Eg. 66655 is better than 66644. You can easily figure that out - you will need a bigger type to store result into :)
I decided to try myself, and I ended up not using regular expressions -- I thought maybe with the simplicity of the searches required, regular expressions would add more complexity than they save. I used similar logic to another answer though: count the quantity of each number and base all the scoring on that:
enum RollScoreType
{
HighDie,
Pair,
TwoPair,
ThreeOfAKind,
SmallStright,
PairSmallStriaght,
FullHouse,
LargeStraight,
FourOfAKind,
FiveOfAKind
}
struct RollScore
{
public RollScoreType type;
public byte highestDie;
public byte nextHighestDie;
public RollScore(RollScoreType type, byte highest, byte next)
{
this.type = type;
this.highestDie = highest;
this.nextHighestDie = next;
}
public override string ToString()
{
return string.Format("{0} {1} {2}", type, highestDie, nextHighestDie);
}
}
static RollScore GetDiceScore(string input)
{
char[] dice = input.ToCharArray();
byte[] diceCount = new byte[6];
for (int i = 0; i < dice.Length; i++)
diceCount[int.Parse(dice[i].ToString())-1]++;
if (Array.IndexOf(diceCount, (byte)5) >= 0)
return new RollScore(RollScoreType.FiveOfAKind, (byte)(Array.IndexOf(diceCount, (byte)5) + 1), 0);
else if (Array.IndexOf(diceCount, (byte)4) >= 0)
return new RollScore(RollScoreType.FourOfAKind, (byte)(Array.IndexOf(diceCount, (byte)4) + 1), (byte)(Array.IndexOf(diceCount, (byte)1) + 1));
else if (Array.IndexOf(diceCount, (byte)3) >= 0)
{
byte three = (byte)(Array.IndexOf(diceCount, (byte)3) + 1);
if (Array.IndexOf(diceCount, (byte)2) >= 0)
{
byte pair = (byte)(Array.IndexOf(diceCount, (byte)2) + 1);
return new RollScore(RollScoreType.FullHouse, Math.Max(pair, three), Math.Min(pair, three));
}
else
return new RollScore(RollScoreType.ThreeOfAKind, three, (byte)(Array.LastIndexOf(diceCount, (byte)1) + 1));
}
else if (Array.IndexOf(diceCount, (byte)2) >= 0)
{
byte pair = (byte)(Array.IndexOf(diceCount, (byte)2) + 1);
byte highPair = (byte)(Array.LastIndexOf(diceCount, (byte)2) + 1);
if (highPair != pair)
return new RollScore(RollScoreType.TwoPair, highPair, pair);
else
{
byte lowMissingDie = (byte)Array.IndexOf(diceCount, (byte)0);
byte highMissingDie = (byte)Array.LastIndexOf(diceCount, (byte)0);
switch (lowMissingDie)
{
case 0:
if (highMissingDie == 5)
return new RollScore(RollScoreType.PairSmallStriaght, 5, 4);
if (highMissingDie == 1)
return new RollScore(RollScoreType.PairSmallStriaght, 6, 5);
break;
case 4:
return new RollScore(RollScoreType.PairSmallStriaght, 4, 3);
}
return new RollScore(RollScoreType.Pair, pair, (byte)(Array.LastIndexOf(diceCount, (byte)1) + 1));
}
}
byte missingDie = (byte)Array.IndexOf(diceCount, (byte)0);
switch(missingDie)
{
case 0:
return new RollScore(RollScoreType.LargeStraight, 6, 5);
case 1:
return new RollScore(RollScoreType.SmallStright, 6, 5);
case 4:
return new RollScore(RollScoreType.SmallStright, 4, 3);
case 5:
return new RollScore(RollScoreType.LargeStraight, 5, 4);
default:
return new RollScore(RollScoreType.HighDie, 6, (byte)(Array.LastIndexOf(diceCount, (byte)1, 3) + 1));
}
}
I discovered, to my surprise, that the probability of a small straight and a large straight are equal in 5-die rolls. Is that true!?
EDIT: Fixed; I see that when I include small straights that include a pair, the probability of a small straight goes up significantly.
When I think about it, a pair and a small straight should probably use the pair as the highest die and the highest number in the straight as the next highest (in order to [properly compare two rolls that are both a pair with a small straight). If so, I'd replace the block of code for handling PairSmallStraight with this:
switch (lowMissingDie)
{
case 0:
if (highMissingDie == 5)
return new RollScore(RollScoreType.PairSmallStriaght, pair, 5);
if (highMissingDie == 1)
return new RollScore(RollScoreType.PairSmallStriaght, pair, 6);
break;
case 4:
return new RollScore(RollScoreType.PairSmallStriaght, pair, 4);
}
You could try to put your values in to a list. This would allow you to quickly sort your values. And if you add the values that would give you the hand. 111AA = 29 and 222KK = 30. Just an idea.
Here is my Code:
public static int CalculateTotalOfSingles (int pipNumber)
{
//
var myScore = 0;
foreach (var myDie in Roll5Player.MyHand.Dice)
{
{ if (myDie.Pips == pipNumber)
myScore+= pipNumber;
}
}
//
return myScore;
}
public static int CalculateDicePips ()
{
//
var myScore = 0;
foreach (var myDie in Roll5Player.MyHand.Dice)
{
{myScore += myDie.Pips;
}
}
//
return myScore;
}
//
//
//
public static int CalculateTotalOfAllSingles (int pipNumber)
{
//
var myScore = 0;
for (int i = 1; i <= 6; i++)
{
myScore += pipNumber;
}
//
return myScore;
}
public static bool CalculateIsNOfaKind (int count)
{
//
for (var index = 1; index <= 6; index++)
{
var cntr = 0;
foreach (var myDie in Roll5Player.MyHand.Dice)
{
if (myDie.Pips == index)
cntr++;
}
//
if (cntr == count)
{
return true;
;
}
}
//
return false;
}
public static int CalculateNOfaKind (int count )
{
//
var myScore = 0;
for (var index = 1; index <= 6; index++)
{
var cntr = 0;
foreach (var myDie in Roll5Player.MyHand.Dice)
{
if (myDie.Pips == index)
cntr++;
}
//
if (cntr >= count)
{ myScore = CalculateDicePips();
return myScore;
;
}
}
//
return myScore;
}
///
public static int CaluclateFullHouse ( )
{
//
var myScore = 0;
var cntr = new int[6];
for (var index = 1; index <= 6; index++)
{
foreach (var myDie in Roll5Player.MyHand.Dice)
{
if (myDie.Pips == index)
cntr[index-1]++;
}
}
//
var boolCondA = false;
var boolCondB = false;
foreach (var i in cntr)
{
if (i == 3)
{boolCondA = true;
break;
}
}
if (boolCondA)
{
foreach (var i in cntr)
{
if (i == 2)
{boolCondB = true;
break;
}
}
}
//
if (boolCondB )
myScore = CalculateDicePips();
//
//
//
return myScore;
}
public static int CaluclateStraights (int straightCount, int score)
{
//
var tempPip = 0;
var myScore = 0;
var isFirstIteration = true;
var cntr = 0;
int[] sortedDice = new int[5];
var sortedDiceLise = new List<int>();
foreach (var myDie in Roll5Player.MyHand.Dice)
{
sortedDiceLise.Add(myDie.Pips);
}
sortedDiceLise.Sort();
foreach (var myDie in sortedDiceLise)
{
//
//
if (!isFirstIteration)
{
if (myDie == tempPip + 1)
cntr++;
}
//
isFirstIteration = false;
tempPip = myDie;
}
if (cntr == straightCount - 1)
{myScore = score;
}
//
//
//
return myScore;
}
public static int CalculateYahtzee ()
{
//
for (var index = 1; index <= 6; index++)
{
var cntr = 0;
foreach (var myDie in Roll5Player.MyHand.Dice)
{
if (myDie.Pips == index)
cntr++;
}
//
if (cntr == 5)
{
return 50;
;
}
}
//
return 0;
}

How do you perform string replacement on just a subsection of a string?

I'd like an efficient method that would work something like this
EDIT: Sorry I didn't put what I'd tried before. I updated the example now.
// Method signature, Only replaces first instance or how many are specified in max
public int MyReplace(ref string source,string org, string replace, int start, int max)
{
int ret = 0;
int len = replace.Length;
int olen = org.Length;
for(int i = 0; i < max; i++)
{
// Find the next instance of the search string
int x = source.IndexOf(org, ret + olen);
if(x > ret)
ret = x;
else
break;
// Insert the replacement
source = source.Insert(x, replace);
// And remove the original
source = source.Remove(x + len, olen); // removes original string
}
return ret;
}
string source = "The cat can fly but only if he is the cat in the hat";
int i = MyReplace(ref source,"cat", "giraffe", 8, 1);
// Results in the string "The cat can fly but only if he is the giraffe in the hat"
// i contains the index of the first letter of "giraffe" in the new string
The only reason I'm asking is because my implementation I'd imagine getting slow with 1,000s of replaces.
How about:
public static int MyReplace(ref string source,
string org, string replace, int start, int max)
{
if (start < 0) throw new System.ArgumentOutOfRangeException("start");
if (max <= 0) return 0;
start = source.IndexOf(org, start);
if (start < 0) return 0;
StringBuilder sb = new StringBuilder(source, 0, start, source.Length);
int found = 0;
while (max-- > 0) {
int index = source.IndexOf(org, start);
if (index < 0) break;
sb.Append(source, start, index - start).Append(replace);
start = index + org.Length;
found++;
}
sb.Append(source, start, source.Length - start);
source = sb.ToString();
return found;
}
it uses StringBuilder to avoid lots of intermediate strings; I haven't tested it rigorously, but it seems to work. It also tries to avoid an extra string when there are no matches.
To start, try something like this:
int count = 0;
Regex.Replace(source, Regex.Escape(literal), (match) =>
{
return (count++ > something) ? "new value" : match.Value;
});
To replace only the first match:
private string ReplaceFirst(string source, string oldString, string newString)
{
var index = source.IndexOf(oldString);
var begin = source.Substring(0, index);
var end = source.Substring(index + oldString.Length);
return begin + newString + end;
}
You have a bug in that you will miss the item to replace if it is in the beginning.
change these lines;
int ret = start; // instead of zero, or you ignore the start parameter
// Find the next instance of the search string
// Do not skip olen for the first search!
int x = i == 0 ? source.IndexOf(org, ret) : source.IndexOf(org, ret + olen);
Also your routine does 300 thousand replaces a second on my machine. Are you sure this will be a bottleneck?
And just found that your code also has an issue if you replace larger texts by smaller texts.
This code is 100% faster if you have four replaces and around 10% faster with one replacement (faster when compared with the posted original code). It uses the specified start parameter and works when replacing larger texts by smaller texts.
Mark Gravells solution is (no offense ;-) 60% slower as the original code and it also returns another value.
// Method signature, Only replaces first instance or how many are specified in max
public static int MyReplace(ref string source, string org, string replace, int start, int max)
{
var ret = 0;
int x = start;
int reps = 0;
int l = source.Length;
int lastIdx = 0;
string repstring = "";
while (x < l)
{
if ((source[x] == org[0]) && (reps < max) && (x >= start))
{
bool match = true;
for (int y = 1; y < org.Length; y++)
{
if (source[x + y] != org[y])
{
match = false;
break;
}
}
if (match)
{
repstring += source.Substring(lastIdx, x - lastIdx) + replace;
ret = x;
x += org.Length - 1;
reps++;
lastIdx = x + 1;
// Done?
if (reps == max)
{
source = repstring + source.Substring(lastIdx);
return ret;
}
}
}
x++;
}
if (ret > 0)
{
source = repstring + source.Substring(lastIdx);
}
return ret;
}

Categories