Index out of bounds of array but only sometimes [duplicate] - c#
Suppose I had a string:
string str = "1111222233334444";
How can I break this string into chunks of some size?
e.g., breaking this into sizes of 4 would return strings:
"1111"
"2222"
"3333"
"4444"
static IEnumerable<string> Split(string str, int chunkSize)
{
return Enumerable.Range(0, str.Length / chunkSize)
.Select(i => str.Substring(i * chunkSize, chunkSize));
}
Please note that additional code might be required to gracefully handle edge cases (null or empty input string, chunkSize == 0, input string length not divisible by chunkSize, etc.). The original question doesn't specify any requirements for these edge cases and in real life the requirements might vary so they are out of scope of this answer.
In a combination of dove+Konstatin's answers...
static IEnumerable<string> WholeChunks(string str, int chunkSize) {
for (int i = 0; i < str.Length; i += chunkSize)
yield return str.Substring(i, chunkSize);
}
This will work for all strings that can be split into a whole number of chunks, and will throw an exception otherwise.
If you want to support strings of any length you could use the following code:
static IEnumerable<string> ChunksUpto(string str, int maxChunkSize) {
for (int i = 0; i < str.Length; i += maxChunkSize)
yield return str.Substring(i, Math.Min(maxChunkSize, str.Length-i));
}
However, the the OP explicitly stated he does not need this; it's somewhat longer and harder to read, slightly slower. In the spirit of KISS and YAGNI, I'd go with the first option: it's probably the most efficient implementation possible, and it's very short, readable, and, importantly, throws an exception for nonconforming input.
Why not loops? Here's something that would do it quite well:
string str = "111122223333444455";
int chunkSize = 4;
int stringLength = str.Length;
for (int i = 0; i < stringLength ; i += chunkSize)
{
if (i + chunkSize > stringLength) chunkSize = stringLength - i;
Console.WriteLine(str.Substring(i, chunkSize));
}
Console.ReadLine();
I don't know how you'd deal with case where the string is not factor of 4, but not saying you're idea is not possible, just wondering the motivation for it if a simple for loop does it very well? Obviously the above could be cleaned and even put in as an extension method.
Or as mentioned in comments, you know it's /4 then
str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize)
{Console.WriteLine(str.Substring(i, chunkSize));}
This is based on #dove solution but implemented as an extension method.
Benefits:
Extension method
Covers corner cases
Splits string with any chars: numbers, letters, other symbols
Code
public static class EnumerableEx
{
public static IEnumerable<string> SplitBy(this 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);
}
}
}
Usage
var result = "bobjoecat".SplitBy(3); // bob, joe, cat
Unit tests removed for brevity (see previous revision)
Using regular expressions and Linq:
List<string> groups = (from Match m in Regex.Matches(str, #"\d{4}")
select m.Value).ToList();
I find this to be more readable, but it's just a personal opinion. It can also be a one-liner : ).
How's this for a one-liner?
List<string> result = new List<string>(Regex.Split(target, #"(?<=\G.{4})", RegexOptions.Singleline));
With this regex it doesn't matter if the last chunk is less than four characters, because it only ever looks at the characters behind it.
I'm sure this isn't the most efficient solution, but I just had to toss it out there.
Starting with .NET 6, we can also use the Chunk method:
var result = str
.Chunk(4)
.Select(x => new string(x))
.ToList();
I recently had to write something that accomplishes this at work, so I thought I would post my solution to this problem. As an added bonus, the functionality of this solution provides a way to split the string in the opposite direction and it does correctly handle unicode characters as previously mentioned by Marvin Pinto above. So, here it is:
using System;
using Extensions;
namespace TestCSharp
{
class Program
{
static void Main(string[] args)
{
string asciiStr = "This is a string.";
string unicodeStr = "これは文字列です。";
string[] array1 = asciiStr.Split(4);
string[] array2 = asciiStr.Split(-4);
string[] array3 = asciiStr.Split(7);
string[] array4 = asciiStr.Split(-7);
string[] array5 = unicodeStr.Split(5);
string[] array6 = unicodeStr.Split(-5);
}
}
}
namespace Extensions
{
public static class StringExtensions
{
/// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
/// <param name="s">This string object.</param>
/// <param name="length">Size of each substring.
/// <para>CASE: length > 0 , RESULT: String is split from left to right.</para>
/// <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
/// <para>CASE: length < 0 , RESULT: String is split from right to left.</para>
/// </param>
/// <returns>String array that has been split into substrings of equal length.</returns>
/// <example>
/// <code>
/// string s = "1234567890";
/// string[] a = s.Split(4); // a == { "1234", "5678", "90" }
/// </code>
/// </example>
public static string[] Split(this string s, int length)
{
System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);
int lengthAbs = Math.Abs(length);
if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
return new string[] { str.ToString() };
string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs: (str.LengthInTextElements / lengthAbs) + 1)];
if (length > 0)
for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
else // if (length < 0)
for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));
return array;
}
}
}
Also, here is an image link to the results of running this code: http://i.imgur.com/16Iih.png
It's not pretty and it's not fast, but it works, it's a one-liner and it's LINQy:
List<string> a = text.Select((c, i) => new { Char = c, Index = i }).GroupBy(o => o.Index / 4).Select(g => new String(g.Select(o => o.Char).ToArray())).ToList();
This should be much faster and more efficient than using LINQ or other approaches used here.
public static IEnumerable<string> Splice(this string s, int spliceLength)
{
if (s == null)
throw new ArgumentNullException("s");
if (spliceLength < 1)
throw new ArgumentOutOfRangeException("spliceLength");
if (s.Length == 0)
yield break;
var start = 0;
for (var end = spliceLength; end < s.Length; end += spliceLength)
{
yield return s.Substring(start, spliceLength);
start = end;
}
yield return s.Substring(start);
}
You can use morelinq by Jon Skeet. Use Batch like:
string str = "1111222233334444";
int chunkSize = 4;
var chunks = str.Batch(chunkSize).Select(r => new String(r.ToArray()));
This will return 4 chunks for the string "1111222233334444". If the string length is less than or equal to the chunk size Batch will return the string as the only element of IEnumerable<string>
For output:
foreach (var chunk in chunks)
{
Console.WriteLine(chunk);
}
and it will give:
1111
2222
3333
4444
Personally I prefer my solution :-)
It handles:
String lengths that are a multiple of the chunk size.
String lengths that are NOT a multiple of the chunk size.
String lengths that are smaller than the chunk size.
NULL and empty strings (throws an exception).
Chunk sizes smaller than 1 (throws an exception).
It is implemented as a extension method, and it calculates the number of chunks is going to generate beforehand. It checks the last chunk because in case the text length is not a multiple it needs to be shorter. Clean, short, easy to understand... and works!
public static string[] Split(this string value, int chunkSize)
{
if (string.IsNullOrEmpty(value)) throw new ArgumentException("The string cannot be null.");
if (chunkSize < 1) throw new ArgumentException("The chunk size should be equal or greater than one.");
int remainder;
int divResult = Math.DivRem(value.Length, chunkSize, out remainder);
int numberOfChunks = remainder > 0 ? divResult + 1 : divResult;
var result = new string[numberOfChunks];
int i = 0;
while (i < numberOfChunks - 1)
{
result[i] = value.Substring(i * chunkSize, chunkSize);
i++;
}
int lastChunkSize = remainder > 0 ? remainder : chunkSize;
result[i] = value.Substring(i * chunkSize, lastChunkSize);
return result;
}
Simple and short:
// this means match a space or not a space (anything) up to 4 characters
var lines = Regex.Matches(str, #"[\s\S]{0,4}").Cast<Match>().Select(x => x.Value);
I know question is years old, but here is a Rx implementation. It handles the length % chunkSize != 0 problem out of the box:
public static IEnumerable<string> Chunkify(this string input, int size)
{
if(size < 1)
throw new ArgumentException("size must be greater than 0");
return input.ToCharArray()
.ToObservable()
.Buffer(size)
.Select(x => new string(x.ToArray()))
.ToEnumerable();
}
public static IEnumerable<IEnumerable<T>> SplitEvery<T>(this IEnumerable<T> values, int n)
{
var ls = values.Take(n);
var rs = values.Skip(n);
return ls.Any() ?
Cons(ls, SplitEvery(rs, n)) :
Enumerable.Empty<IEnumerable<T>>();
}
public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> xs)
{
yield return x;
foreach (var xi in xs)
yield return xi;
}
Best , Easiest and Generic Answer :).
string originalString = "1111222233334444";
List<string> test = new List<string>();
int chunkSize = 4; // change 4 with the size of strings you want.
for (int i = 0; i < originalString.Length; i = i + chunkSize)
{
if (originalString.Length - i >= chunkSize)
test.Add(originalString.Substring(i, chunkSize));
else
test.Add(originalString.Substring(i,((originalString.Length - i))));
}
static IEnumerable<string> Split(string str, int chunkSize)
{
IEnumerable<string> retVal = Enumerable.Range(0, str.Length / chunkSize)
.Select(i => str.Substring(i * chunkSize, chunkSize))
if (str.Length % chunkSize > 0)
retVal = retVal.Append(str.Substring(str.Length / chunkSize * chunkSize, str.Length % chunkSize));
return retVal;
}
It correctly handles input string length not divisible by chunkSize.
Please note that additional code might be required to gracefully handle edge cases (null or empty input string, chunkSize == 0).
static IEnumerable<string> Split(string str, double chunkSize)
{
return Enumerable.Range(0, (int) Math.Ceiling(str.Length/chunkSize))
.Select(i => new string(str
.Skip(i * (int)chunkSize)
.Take((int)chunkSize)
.ToArray()));
}
and another approach:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var x = "Hello World";
foreach(var i in x.ChunkString(2)) Console.WriteLine(i);
}
}
public static class Ext{
public static IEnumerable<string> ChunkString(this string val, int chunkSize){
return val.Select((x,i) => new {Index = i, Value = x})
.GroupBy(x => x.Index/chunkSize, x => x.Value)
.Select(x => string.Join("",x));
}
}
Six years later o_O
Just because
public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
{
var count = (int) Math.Ceiling(str.Length/(double) chunkSize);
Func<int, int> start = index => remainingInFront ? str.Length - (count - index)*chunkSize : index*chunkSize;
Func<int, int> end = index => Math.Min(str.Length - Math.Max(start(index), 0), Math.Min(start(index) + chunkSize - Math.Max(start(index), 0), chunkSize));
return Enumerable.Range(0, count).Select(i => str.Substring(Math.Max(start(i), 0),end(i)));
}
or
private static Func<bool, int, int, int, int, int> start = (remainingInFront, length, count, index, size) =>
remainingInFront ? length - (count - index) * size : index * size;
private static Func<bool, int, int, int, int, int, int> end = (remainingInFront, length, count, index, size, start) =>
Math.Min(length - Math.Max(start, 0), Math.Min(start + size - Math.Max(start, 0), size));
public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
{
var count = (int)Math.Ceiling(str.Length / (double)chunkSize);
return Enumerable.Range(0, count).Select(i => str.Substring(
Math.Max(start(remainingInFront, str.Length, count, i, chunkSize), 0),
end(remainingInFront, str.Length, count, i, chunkSize, start(remainingInFront, str.Length, count, i, chunkSize))
));
}
AFAIK all edge cases are handled.
Console.WriteLine(string.Join(" ", "abc".Split(2, false))); // ab c
Console.WriteLine(string.Join(" ", "abc".Split(2, true))); // a bc
Console.WriteLine(string.Join(" ", "a".Split(2, true))); // a
Console.WriteLine(string.Join(" ", "a".Split(2, false))); // a
List<string> SplitString(int chunk, string input)
{
List<string> list = new List<string>();
int cycles = input.Length / chunk;
if (input.Length % chunk != 0)
cycles++;
for (int i = 0; i < cycles; i++)
{
try
{
list.Add(input.Substring(i * chunk, chunk));
}
catch
{
list.Add(input.Substring(i * chunk));
}
}
return list;
}
I took this to another level. Chucking is an easy one liner, but in my case I needed whole words as well. Figured I would post it, just in case someone else needs something similar.
static IEnumerable<string> Split(string orgString, int chunkSize, bool wholeWords = true)
{
if (wholeWords)
{
List<string> result = new List<string>();
StringBuilder sb = new StringBuilder();
if (orgString.Length > chunkSize)
{
string[] newSplit = orgString.Split(' ');
foreach (string str in newSplit)
{
if (sb.Length != 0)
sb.Append(" ");
if (sb.Length + str.Length > chunkSize)
{
result.Add(sb.ToString());
sb.Clear();
}
sb.Append(str);
}
result.Add(sb.ToString());
}
else
result.Add(orgString);
return result;
}
else
return new List<string>(Regex.Split(orgString, #"(?<=\G.{" + chunkSize + "})", RegexOptions.Singleline));
}
Results based on below comment:
string msg = "336699AABBCCDDEEFF";
foreach (string newMsg in Split(msg, 2, false))
{
Console.WriteLine($">>{newMsg}<<");
}
Console.ReadKey();
Results:
>>33<<
>>66<<
>>99<<
>>AA<<
>>BB<<
>>CC<<
>>DD<<
>>EE<<
>>FF<<
>><<
Another way to pull it:
List<string> splitData = (List<string>)Split(msg, 2, false);
for (int i = 0; i < splitData.Count - 1; i++)
{
Console.WriteLine($">>{splitData[i]}<<");
}
Console.ReadKey();
New Results:
>>33<<
>>66<<
>>99<<
>>AA<<
>>BB<<
>>CC<<
>>DD<<
>>EE<<
>>FF<<
An important tip if the string that is being chunked needs to support all Unicode characters.
If the string is to support international characters like 𠀋, then split up the string using the System.Globalization.StringInfo class. Using StringInfo, you can split up the string based on number of text elements.
string internationalString = '𠀋';
The above string has a Length of 2, because the String.Length property returns the number of Char objects in this instance, not the number of Unicode characters.
Changed slightly to return parts whose size not equal to chunkSize
public static IEnumerable<string> Split(this string str, int chunkSize)
{
var splits = new List<string>();
if (str.Length < chunkSize) { chunkSize = str.Length; }
splits.AddRange(Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize)));
splits.Add(str.Length % chunkSize > 0 ? str.Substring((str.Length / chunkSize) * chunkSize, str.Length - ((str.Length / chunkSize) * chunkSize)) : string.Empty);
return (IEnumerable<string>)splits;
}
I think this is an straight forward answer:
public static IEnumerable<string> Split(this string str, int chunkSize)
{
if(string.IsNullOrEmpty(str) || chunkSize<1)
throw new ArgumentException("String can not be null or empty and chunk size should be greater than zero.");
var chunkCount = str.Length / chunkSize + (str.Length % chunkSize != 0 ? 1 : 0);
for (var i = 0; i < chunkCount; i++)
{
var startIndex = i * chunkSize;
if (startIndex + chunkSize >= str.Length)
yield return str.Substring(startIndex);
else
yield return str.Substring(startIndex, chunkSize);
}
}
And it covers edge cases.
static List<string> GetChunks(string value, int chunkLength)
{
var res = new List<string>();
int count = (value.Length / chunkLength) + (value.Length % chunkLength > 0 ? 1 : 0);
Enumerable.Range(0, count).ToList().ForEach(f => res.Add(value.Skip(f * chunkLength).Take(chunkLength).Select(z => z.ToString()).Aggregate((a,b) => a+b)));
return res;
}
demo
Here's my 2 cents:
IEnumerable<string> Split(string str, int chunkSize)
{
while (!string.IsNullOrWhiteSpace(str))
{
var chunk = str.Take(chunkSize).ToArray();
str = str.Substring(chunk.Length);
yield return new string(chunk);
}
}//Split
I've slightly build up on João's solution.
What I've done differently is in my method you can actually specify whether you want to return the array with remaining characters or whether you want to truncate them if the end characters do not match your required chunk length, I think it's pretty flexible and the code is fairly straight forward:
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace SplitFunction
{
class Program
{
static void Main(string[] args)
{
string text = "hello, how are you doing today?";
string[] chunks = SplitIntoChunks(text, 3,false);
if (chunks != null)
{
chunks.ToList().ForEach(e => Console.WriteLine(e));
}
Console.ReadKey();
}
private static string[] SplitIntoChunks(string text, int chunkSize, bool truncateRemaining)
{
string chunk = chunkSize.ToString();
string pattern = truncateRemaining ? ".{" + chunk + "}" : ".{1," + chunk + "}";
string[] chunks = null;
if (chunkSize > 0 && !String.IsNullOrEmpty(text))
chunks = (from Match m in Regex.Matches(text,pattern)select m.Value).ToArray();
return chunks;
}
}
}
public static List<string> SplitByMaxLength(this string str)
{
List<string> splitString = new List<string>();
for (int index = 0; index < str.Length; index += MaxLength)
{
splitString.Add(str.Substring(index, Math.Min(MaxLength, str.Length - index)));
}
return splitString;
}
I can't remember who gave me this, but it works great. I speed tested a number of ways to break Enumerable types into groups. The usage would just be like this...
List<string> Divided = Source3.Chunk(24).Select(Piece => string.Concat<char>(Piece)).ToList();
The extention code would look like this...
#region Chunk Logic
private class ChunkedEnumerable<T> : IEnumerable<T>
{
class ChildEnumerator : IEnumerator<T>
{
ChunkedEnumerable<T> parent;
int position;
bool done = false;
T current;
public ChildEnumerator(ChunkedEnumerable<T> parent)
{
this.parent = parent;
position = -1;
parent.wrapper.AddRef();
}
public T Current
{
get
{
if (position == -1 || done)
{
throw new InvalidOperationException();
}
return current;
}
}
public void Dispose()
{
if (!done)
{
done = true;
parent.wrapper.RemoveRef();
}
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
position++;
if (position + 1 > parent.chunkSize)
{
done = true;
}
if (!done)
{
done = !parent.wrapper.Get(position + parent.start, out current);
}
return !done;
}
public void Reset()
{
// per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
throw new NotSupportedException();
}
}
EnumeratorWrapper<T> wrapper;
int chunkSize;
int start;
public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
{
this.wrapper = wrapper;
this.chunkSize = chunkSize;
this.start = start;
}
public IEnumerator<T> GetEnumerator()
{
return new ChildEnumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private class EnumeratorWrapper<T>
{
public EnumeratorWrapper(IEnumerable<T> source)
{
SourceEumerable = source;
}
IEnumerable<T> SourceEumerable { get; set; }
Enumeration currentEnumeration;
class Enumeration
{
public IEnumerator<T> Source { get; set; }
public int Position { get; set; }
public bool AtEnd { get; set; }
}
public bool Get(int pos, out T item)
{
if (currentEnumeration != null && currentEnumeration.Position > pos)
{
currentEnumeration.Source.Dispose();
currentEnumeration = null;
}
if (currentEnumeration == null)
{
currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
}
item = default(T);
if (currentEnumeration.AtEnd)
{
return false;
}
while (currentEnumeration.Position < pos)
{
currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
currentEnumeration.Position++;
if (currentEnumeration.AtEnd)
{
return false;
}
}
item = currentEnumeration.Source.Current;
return true;
}
int refs = 0;
// needed for dispose semantics
public void AddRef()
{
refs++;
}
public void RemoveRef()
{
refs--;
if (refs == 0 && currentEnumeration != null)
{
var copy = currentEnumeration;
currentEnumeration = null;
copy.Source.Dispose();
}
}
}
/// <summary>Speed Checked. Works Great!</summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
if (chunksize < 1) throw new InvalidOperationException();
var wrapper = new EnumeratorWrapper<T>(source);
int currentPos = 0;
T ignore;
try
{
wrapper.AddRef();
while (wrapper.Get(currentPos, out ignore))
{
yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
currentPos += chunksize;
}
}
finally
{
wrapper.RemoveRef();
}
}
#endregion
class StringHelper
{
static void Main(string[] args)
{
string str = "Hi my name is vikas bansal and my email id is bansal.vks#gmail.com";
int offSet = 10;
List<string> chunks = chunkMyStr(str, offSet);
Console.Read();
}
static List<string> chunkMyStr(string str, int offSet)
{
List<string> resultChunks = new List<string>();
for (int i = 0; i < str.Length; i += offSet)
{
string temp = str.Substring(i, (str.Length - i) > offSet ? offSet : (str.Length - i));
Console.WriteLine(temp);
resultChunks.Add(temp);
}
return resultChunks;
}
}
Related
How can I reduce the complexity of these two methods?
I have some code like using System; using System.Collections.Generic; using System.IO; using System.Linq; class Solution { // returns true or false based on whether s1 and s2 are // an unordered anagrammatic pair // e.g. "aac","cac" --> false // "aac","aca" --> true // Complexity: O(n) static bool IsAnagrammaticPair(string s1, string s2) { if(s1.Length != s2.Length) return false; int[] counter1 = new int[26], counter2 = new int[26]; for(int i = 0; i < s1.Length; ++i) { counter1[(int)s1[i] - (int)'a'] += 1; counter2[(int)s2[i] - (int)'a'] += 1; } for(int i = 0; i < 26; ++i) if(counter1[i] != counter2[i]) return false; return true; } // gets all substrings of s (not including the empty string, // including s itself) // Complexity: O(n^2) static IEnumerable<string> GetSubstrings(string s) { return from i in Enumerable.Range(0, s.Length) from j in Enumerable.Range(0, s.Length - i + 1) where j >= 1 select s.Substring(i, j); } // gets the number of anagrammatical pairs of substrings in s // Complexity: O(n^2) static int NumAnagrammaticalPairs(string s) { var substrings = GetSubstrings(s).ToList(); var indices = Enumerable.Range(0, substrings.Count); return (from i in indices from j in indices where i < j && IsAnagrammaticPair(substrings[i], substrings[j]) select 1).Count(); } static void Main(String[] args) { int T = Int32.Parse(Console.ReadLine()); for(int t = 0; t < T; ++t) { string line = Console.ReadLine(); Console.WriteLine(NumAnagrammaticalPairs(line)); } } } which is not meeting the performance benchmarks of the problem. The two helper methods I have GetSubstrings and NumAnagrammaticalPairs I know are O(n^2) as I mentioned in the comments, however I don't see how I can reduce the number of operations involved in retrieving the answer. Any ideas?
You can use LINQ to check for anagarmatic pair. Not sure about performance though. public class Program { public static void Main() { string x="aac"; string y ="caa"; List<char> lstX = x.ToCharArray().OrderBy(m=>m).ToList<char>(); List<char> lstY =y.ToCharArray().OrderBy(m=>m).ToList<char>(); Console.WriteLine(IsAnagramaticPair(x,y)); } public static bool IsAnagramaticPair(string x ,string y) { List<char> lstX = x.ToCharArray().OrderBy(m=>m).ToList<char>(); List<char> lstY =y.ToCharArray().OrderBy(m=>m).ToList<char>(); return lstX.SequenceEqual(lstY); } }
There are two possibilities that come to mind. First, sort both strings and compare the results: static bool IsAnagrammaticPair(string s1, string s2) { var srt1 = s1.OrderBy(c => c); var srt2 = s2.OrderBy(c => c); return s1.SequenceEqual(s2); } If m and n are the lengths of the strings, then that is O(n log n + m log m). Another possibility is to make a histogram of characters from one string, and then compare the characters and associated counts from the other string. The strings have to be the same length for that to work, though: static bool IsAnagrammaticPair(string s1, string s2) { if (s1.Length != s2.Length) return false; var l1 = s1.ToLookup(c => c); return s2.GroupBy(c => c).All(g => l1.Contains(g.Key) && l1[g.Key].Count() == g.Count()); } That's O(n) for the first part, with O(n) extra space. And O(m) for the second part.
You can take subsets of the substrings so that each subset is composed of strings having the same length. After that you can or using a sort O(n log n) to make a direct comparison between the strings. And also use the sort to arrange each subset in the good order (so no ixj comparisons). static void Main( string[] args ) { int T = Int32.Parse( Console.ReadLine() ); for ( int t = 0 ; t < T ; ++t ) { string line = Console.ReadLine(); Console.WriteLine( NumAnagrammaticalPairs( line ) ); } } static int NumAnagrammaticalPairs( string s ) { int sum = 0; foreach ( var substrings in GetSubstringsByLength( s ) ) { // Count for each string how many others are identical int toSum = 0; for ( int i = 1 ; i < substrings.Count ; i++ ) if ( substrings[i - 1] == substrings[i] ) { toSum++; sum += toSum; } else { toSum = 0; } } return sum; } static IEnumerable<List<string>> GetSubstringsByLength( string s ) { for ( int length = 1 ; length < s.Length ; length++ ) yield return GetSubstringsOfLength( s, length ); } static List<string> GetSubstringsOfLength( string s, int length ) { var result = new List<string>(); for ( int i = 0 ; i <= s.Length - length ; i++ ) { var substring = s.Substring( i, length ); result.Add( new string( substring.OrderBy( c => c ).ToArray<char>() ) ); } result.Sort(); return result; }
How to convert Int To String in C# without using library functions?
What is the most effective code to convert int to string without using a native convert function. public static void Main(string[] args) { string y = Convert(990); Console.WriteLine(y); Console.ReadLine(); } public static string Convert(int x) { char[] Str = new char[20]; int i = 0; while (x != 0) { Str[i++] = x % 10 + '0'; x = x / 10; } return Str.ToString();// How do I handle this without the native function? } The above doesnt seem to work. Please advise.
Here's a solution without a String constructor or a .ToString() call. This one handles negative numbers. It's a bit too 'clever' for my taste, but it's an academic exercise anyway... void Main() { Console.WriteLine(Convert(-5432)); } String Convert(int i) { return String.Join("",Digits(i).Reverse()); } IEnumerable<char> Digits(int i) { bool neg = false; if(i==0) {yield return '0'; yield break;} if(i<0) { i = -i; neg = true;} while (i!=0) { char digit = (char)(i % 10 + '0'); i = i / 10; yield return digit; } if(neg) yield return '-'; yield break; }
It might be easier to construct your result using a StringBuilder rather than a char array: public static string Convert(int x) { StringBuilder sb = new StringBuilder(); while (x != 0) { sb.Insert(0, (char)(x % 10 + '0')); x = x / 10; } return sb.ToString(); } The Insert(0, x) allows you to insert the more significant digits at the front.
You are adding the least significant digits to the start of the string. You'll need to reverse the string, or add the digits in reverse order, or insert them rather than append them. And you'll need to do some casting. For example, here's one way to do it: Str[i++] = (char)(x % 10 + '0'); Finally, you cannot use ToString() like that. You want this: return new string(Str, 0, i); Although StringBuilder might be more suitable. And note that your code won't work properly for negative input values, or for zero. So, here's a version that handles all of that: public static string Convert(int x) { if (x == 0) { return "0"; } StringBuilder sb = new StringBuilder(); string prefix = ""; if (x < 0) { prefix = "-"; x = -x; } while (x != 0) { sb.Insert(0, (char)(x % 10 + '0')); x = x / 10; } return prefix + sb.ToString(); } This is pretty ugly though!
Is there a higher performance method for removing rare unwanted chars from a string?
EDIT Apologies if the original unedited question is misleading. This question is not asking how to remove Invalid XML Chars from a string, answers to that question would be better directed here. I'm not asking you to review my code. What I'm looking for in answers is, a function with the signature string <YourName>(string input, Func<char, bool> check); that will have performance similar or better than RemoveCharsBufferCopyBlackList. Ideally this function would be more generic and if possible simpler to read, but these requirements are secondary. I recently wrote a function to strip invalid XML chars from a string. In my application the strings can be modestly long and the invalid chars occur rarely. This excerise got me thinking. What ways can this be done in safe managed c# and, which would offer the best performance for my scenario. Here is my test program, I've subtituted the "valid XML predicate" for one the omits the char 'X'. class Program { static void Main() { var attempts = new List<Func<string, Func<char, bool>, string>> { RemoveCharsLinqWhiteList, RemoveCharsFindAllWhiteList, RemoveCharsBufferCopyBlackList } const string GoodString = "1234567890abcdefgabcedefg"; const string BadString = "1234567890abcdefgXabcedefg"; const int Iterations = 100000; var timer = new StopWatch(); var testSet = new List<string>(Iterations); for (var i = 0; i < Iterations; i++) { if (i % 1000 == 0) { testSet.Add(BadString); } else { testSet.Add(GoodString); } } foreach (var attempt in attempts) { //Check function works and JIT if (attempt.Invoke(BadString, IsNotUpperX) != GoodString) { throw new ApplicationException("Broken Function"); } if (attempt.Invoke(GoodString, IsNotUpperX) != GoodString) { throw new ApplicationException("Broken Function"); } timer.Reset(); timer.Start(); foreach (var t in testSet) { attempt.Invoke(t, IsNotUpperX); } timer.Stop(); Console.WriteLine( "{0} iterations of function \"{1}\" performed in {2}ms", Iterations, attempt.Method, timer.ElapsedMilliseconds); Console.WriteLine(); } Console.Readkey(); } private static bool IsNotUpperX(char value) { return value != 'X'; } private static string RemoveCharsLinqWhiteList(string input, Func<char, bool> check); { return new string(input.Where(check).ToArray()); } private static string RemoveCharsFindAllWhiteList(string input, Func<char, bool> check); { return new string(Array.FindAll(input.ToCharArray(), check.Invoke)); } private static string RemoveCharsBufferCopyBlackList(string input, Func<char, bool> check); { char[] inputArray = null; char[] outputBuffer = null; var blackCount = 0; var lastb = -1; var whitePos = 0; for (var b = 0; b , input.Length; b++) { if (!check.invoke(input[b])) { var whites = b - lastb - 1; if (whites > 0) { if (outputBuffer == null) { outputBuffer = new char[input.Length - blackCount]; } if (inputArray == null) { inputArray = input.ToCharArray(); } Buffer.BlockCopy( inputArray, (lastb + 1) * 2, outputBuffer, whitePos * 2, whites * 2); whitePos += whites; } lastb = b; blackCount++; } } if (blackCount == 0) { return input; } var remaining = inputArray.Length - 1 - lastb; if (remaining > 0) { Buffer.BlockCopy( inputArray, (lastb + 1) * 2, outputBuffer, whitePos * 2, remaining * 2); } return new string(outputBuffer, 0, inputArray.Length - blackCount); } } If you run the attempts you'll note that the performance improves as the functions get more specialised. Is there a faster and more generic way to perform this operation? Or if there is no generic option is there a way that is just faster? Please note that I am not actually interested in removing 'X' and in practice the predicate is more complicated.
You certainly don't want to use LINQ to Objects aka enumerators to do this if you require high performance. Also, don't invoke a delegate per char. Delegate invocations are costly compared to the actual operation you are doing. RemoveCharsBufferCopyBlackList looks good (except for the delegate call per character). I recommend that you inline the contents of the delegate hard-coded. Play around with different ways to write the condition. You may get better performance by first checking the current char against a range of known good chars (e.g. 0x20-0xFF) and if it matches let it through. This test will pass almost always so you can save the expensive checks against individual characters which are invalid in XML. Edit: I just remembered I solved this problem a while ago: static readonly string invalidXmlChars = Enumerable.Range(0, 0x20) .Where(i => !(i == '\u000A' || i == '\u000D' || i == '\u0009')) .Select(i => (char)i) .ConcatToString() + "\uFFFE\uFFFF"; public static string RemoveInvalidXmlChars(string str) { return RemoveInvalidXmlChars(str, false); } internal static string RemoveInvalidXmlChars(string str, bool forceRemoveSurrogates) { if (str == null) throw new ArgumentNullException("str"); if (!ContainsInvalidXmlChars(str, forceRemoveSurrogates)) return str; str = str.RemoveCharset(invalidXmlChars); if (forceRemoveSurrogates) { for (int i = 0; i < str.Length; i++) { if (IsSurrogate(str[i])) { str = str.Where(c => !IsSurrogate(c)).ConcatToString(); break; } } } return str; } static bool IsSurrogate(char c) { return c >= 0xD800 && c < 0xE000; } internal static bool ContainsInvalidXmlChars(string str) { return ContainsInvalidXmlChars(str, false); } public static bool ContainsInvalidXmlChars(string str, bool forceRemoveSurrogates) { if (str == null) throw new ArgumentNullException("str"); for (int i = 0; i < str.Length; i++) { if (str[i] < 0x20 && !(str[i] == '\u000A' || str[i] == '\u000D' || str[i] == '\u0009')) return true; if (str[i] >= 0xD800) { if (forceRemoveSurrogates && str[i] < 0xE000) return true; if ((str[i] == '\uFFFE' || str[i] == '\uFFFF')) return true; } } return false; } Notice, that RemoveInvalidXmlChars first invokes ContainsInvalidXmlChars to save the string allocation. Most strings do not contain invalid XML chars so we can be optimistic.
Natural Sort Order in C#
Anyone have a good resource or provide a sample of a natural order sort in C# for an FileInfo array? I am implementing the IComparer interface in my sorts.
The easiest thing to do is just P/Invoke the built-in function in Windows, and use it as the comparison function in your IComparer: [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern int StrCmpLogicalW(string psz1, string psz2); Michael Kaplan has some examples of how this function works here, and the changes that were made for Vista to make it work more intuitively. The plus side of this function is that it will have the same behaviour as the version of Windows it runs on, however this does mean that it differs between versions of Windows so you need to consider whether this is a problem for you. So a complete implementation would be something like: [SuppressUnmanagedCodeSecurity] internal static class SafeNativeMethods { [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] public static extern int StrCmpLogicalW(string psz1, string psz2); } public sealed class NaturalStringComparer : IComparer<string> { public int Compare(string a, string b) { return SafeNativeMethods.StrCmpLogicalW(a, b); } } public sealed class NaturalFileInfoNameComparer : IComparer<FileInfo> { public int Compare(FileInfo a, FileInfo b) { return SafeNativeMethods.StrCmpLogicalW(a.Name, b.Name); } }
Just thought I'd add to this (with the most concise solution I could find): public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector) { int max = source .SelectMany(i => Regex.Matches(selector(i), #"\d+").Cast<Match>().Select(m => (int?)m.Value.Length)) .Max() ?? 0; return source.OrderBy(i => Regex.Replace(selector(i), #"\d+", m => m.Value.PadLeft(max, '0'))); } The above pads any numbers in the string to the max length of all numbers in all strings and uses the resulting string to sort. The cast to (int?) is to allow for collections of strings without any numbers (.Max() on an empty enumerable throws an InvalidOperationException).
None of the existing implementations looked great so I wrote my own. The results are almost identical to the sorting used by modern versions of Windows Explorer (Windows 7/8). The only differences I've seen are 1) although Windows used to (e.g. XP) handle numbers of any length, it's now limited to 19 digits - mine is unlimited, 2) Windows gives inconsistent results with certain sets of Unicode digits - mine works fine (although it doesn't numerically compare digits from surrogate pairs; nor does Windows), and 3) mine can't distinguish different types of non-primary sort weights if they occur in different sections (e.g. "e-1é" vs "é1e-" - the sections before and after the number have diacritic and punctuation weight differences). public static int CompareNatural(string strA, string strB) { return CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase); } public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options) { CompareInfo cmp = culture.CompareInfo; int iA = 0; int iB = 0; int softResult = 0; int softResultWeight = 0; while (iA < strA.Length && iB < strB.Length) { bool isDigitA = Char.IsDigit(strA[iA]); bool isDigitB = Char.IsDigit(strB[iB]); if (isDigitA != isDigitB) { return cmp.Compare(strA, iA, strB, iB, options); } else if (!isDigitA && !isDigitB) { int jA = iA + 1; int jB = iB + 1; while (jA < strA.Length && !Char.IsDigit(strA[jA])) jA++; while (jB < strB.Length && !Char.IsDigit(strB[jB])) jB++; int cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options); if (cmpResult != 0) { // Certain strings may be considered different due to "soft" differences that are // ignored if more significant differences follow, e.g. a hyphen only affects the // comparison if no other differences follow string sectionA = strA.Substring(iA, jA - iA); string sectionB = strB.Substring(iB, jB - iB); if (cmp.Compare(sectionA + "1", sectionB + "2", options) == cmp.Compare(sectionA + "2", sectionB + "1", options)) { return cmp.Compare(strA, iA, strB, iB, options); } else if (softResultWeight < 1) { softResult = cmpResult; softResultWeight = 1; } } iA = jA; iB = jB; } else { char zeroA = (char)(strA[iA] - (int)Char.GetNumericValue(strA[iA])); char zeroB = (char)(strB[iB] - (int)Char.GetNumericValue(strB[iB])); int jA = iA; int jB = iB; while (jA < strA.Length && strA[jA] == zeroA) jA++; while (jB < strB.Length && strB[jB] == zeroB) jB++; int resultIfSameLength = 0; do { isDigitA = jA < strA.Length && Char.IsDigit(strA[jA]); isDigitB = jB < strB.Length && Char.IsDigit(strB[jB]); int numA = isDigitA ? (int)Char.GetNumericValue(strA[jA]) : 0; int numB = isDigitB ? (int)Char.GetNumericValue(strB[jB]) : 0; if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false; if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false; if (isDigitA && isDigitB) { if (numA != numB && resultIfSameLength == 0) { resultIfSameLength = numA < numB ? -1 : 1; } jA++; jB++; } } while (isDigitA && isDigitB); if (isDigitA != isDigitB) { // One number has more digits than the other (ignoring leading zeros) - the longer // number must be larger return isDigitA ? 1 : -1; } else if (resultIfSameLength != 0) { // Both numbers are the same length (ignoring leading zeros) and at least one of // the digits differed - the first difference determines the result return resultIfSameLength; } int lA = jA - iA; int lB = jB - iB; if (lA != lB) { // Both numbers are equivalent but one has more leading zeros return lA > lB ? -1 : 1; } else if (zeroA != zeroB && softResultWeight < 2) { softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options); softResultWeight = 2; } iA = jA; iB = jB; } } if (iA < strA.Length || iB < strB.Length) { return iA < strA.Length ? 1 : -1; } else if (softResult != 0) { return softResult; } return 0; } The signature matches the Comparison<string> delegate: string[] files = Directory.GetFiles(#"C:\"); Array.Sort(files, CompareNatural); Here's a wrapper class for use as IComparer<string>: public class CustomComparer<T> : IComparer<T> { private Comparison<T> _comparison; public CustomComparer(Comparison<T> comparison) { _comparison = comparison; } public int Compare(T x, T y) { return _comparison(x, y); } } Example: string[] files = Directory.EnumerateFiles(#"C:\") .OrderBy(f => f, new CustomComparer<string>(CompareNatural)) .ToArray(); Here's a good set of filenames I use for testing: Func<string, string> expand = (s) => { int o; while ((o = s.IndexOf('\\')) != -1) { int p = o + 1; int z = 1; while (s[p] == '0') { z++; p++; } int c = Int32.Parse(s.Substring(p, z)); s = s.Substring(0, o) + new string(s[o - 1], c) + s.Substring(p + z); } return s; }; string encodedFileNames = "KDEqLW4xMiotbjEzKjAwMDFcMDY2KjAwMlwwMTcqMDA5XDAxNyowMlwwMTcqMDlcMDE3KjEhKjEtISox" + "LWEqMS4yNT8xLjI1KjEuNT8xLjUqMSoxXDAxNyoxXDAxOCoxXDAxOSoxXDA2NioxXDA2NyoxYSoyXDAx" + "NyoyXDAxOCo5XDAxNyo5XDAxOCo5XDA2Nio9MSphMDAxdGVzdDAxKmEwMDF0ZXN0aW5nYTBcMzEqYTAw" + "Mj9hMDAyIGE/YTAwMiBhKmEwMDIqYTAwMmE/YTAwMmEqYTAxdGVzdGluZ2EwMDEqYTAxdnNmcyphMSph" + "MWEqYTF6KmEyKmIwMDAzcTYqYjAwM3E0KmIwM3E1KmMtZSpjZCpjZipmIDEqZipnP2cgMT9oLW4qaG8t" + "bipJKmljZS1jcmVhbT9pY2VjcmVhbT9pY2VjcmVhbS0/ajBcNDE/ajAwMWE/ajAxP2shKmsnKmstKmsx" + "KmthKmxpc3QqbTAwMDNhMDA1YSptMDAzYTAwMDVhKm0wMDNhMDA1Km0wMDNhMDA1YSpuMTIqbjEzKm8t" + "bjAxMypvLW4xMipvLW40P28tbjQhP28tbjR6P28tbjlhLWI1Km8tbjlhYjUqb24wMTMqb24xMipvbjQ/" + "b240IT9vbjR6P29uOWEtYjUqb245YWI1Km/CrW4wMTMqb8KtbjEyKnAwMCpwMDEqcDAxwr0hKnAwMcK9" + "KnAwMcK9YSpwMDHCvcK+KnAwMipwMMK9KnEtbjAxMypxLW4xMipxbjAxMypxbjEyKnItMDAhKnItMDAh" + "NSpyLTAwIe+8lSpyLTAwYSpyLe+8kFwxIS01KnIt77yQXDEhLe+8lSpyLe+8kFwxISpyLe+8kFwxITUq" + "ci3vvJBcMSHvvJUqci3vvJBcMWEqci3vvJBcMyE1KnIwMCEqcjAwLTUqcjAwLjUqcjAwNSpyMDBhKnIw" + "NSpyMDYqcjQqcjUqctmg2aYqctmkKnLZpSpy27Dbtipy27Qqctu1KnLfgN+GKnLfhCpy34UqcuClpuCl" + "rCpy4KWqKnLgpasqcuCnpuCnrCpy4KeqKnLgp6sqcuCppuCprCpy4KmqKnLgqasqcuCrpuCrrCpy4Kuq" + "KnLgq6sqcuCtpuCtrCpy4K2qKnLgrasqcuCvpuCvrCpy4K+qKnLgr6sqcuCxpuCxrCpy4LGqKnLgsasq" + "cuCzpuCzrCpy4LOqKnLgs6sqcuC1puC1rCpy4LWqKnLgtasqcuC5kOC5lipy4LmUKnLguZUqcuC7kOC7" + "lipy4LuUKnLgu5UqcuC8oOC8pipy4LykKnLgvKUqcuGBgOGBhipy4YGEKnLhgYUqcuGCkOGClipy4YKU" + "KnLhgpUqcuGfoOGfpipy4Z+kKnLhn6UqcuGgkOGglipy4aCUKnLhoJUqcuGlhuGljCpy4aWKKnLhpYsq" + "cuGnkOGnlipy4aeUKnLhp5UqcuGtkOGtlipy4a2UKnLhrZUqcuGusOGutipy4a60KnLhrrUqcuGxgOGx" + "hipy4bGEKnLhsYUqcuGxkOGxlipy4bGUKnLhsZUqcuqYoFwx6pilKnLqmKDqmKUqcuqYoOqYpipy6pik" + "KnLqmKUqcuqjkOqjlipy6qOUKnLqo5UqcuqkgOqkhipy6qSEKnLqpIUqcuqpkOqplipy6qmUKnLqqZUq" + "cvCQkqAqcvCQkqUqcvCdn5gqcvCdn50qcu+8kFwxISpy77yQXDEt77yVKnLvvJBcMS7vvJUqcu+8kFwx" + "YSpy77yQXDHqmKUqcu+8kFwx77yO77yVKnLvvJBcMe+8lSpy77yQ77yVKnLvvJDvvJYqcu+8lCpy77yV" + "KnNpKnPEsSp0ZXN02aIqdGVzdNmi2aAqdGVzdNmjKnVBZS0qdWFlKnViZS0qdUJlKnVjZS0xw6kqdWNl" + "McOpLSp1Y2Uxw6kqdWPDqS0xZSp1Y8OpMWUtKnVjw6kxZSp3ZWlhMSp3ZWlhMip3ZWlzczEqd2Vpc3My" + "KndlaXoxKndlaXoyKndlacOfMSp3ZWnDnzIqeSBhMyp5IGE0KnknYTMqeSdhNCp5K2EzKnkrYTQqeS1h" + "Myp5LWE0KnlhMyp5YTQqej96IDA1MD96IDIxP3ohMjE/ejIwP3oyMj96YTIxP3rCqTIxP1sxKl8xKsKt" + "bjEyKsKtbjEzKsSwKg=="; string[] fileNames = Encoding.UTF8.GetString(Convert.FromBase64String(encodedFileNames)) .Replace("*", ".txt?").Split(new[] { "?" }, StringSplitOptions.RemoveEmptyEntries) .Select(n => expand(n)).ToArray();
Matthews Horsleys answer is the fastest method which doesn't change behaviour depending on which version of windows your program is running on. However, it can be even faster by creating the regex once, and using RegexOptions.Compiled. I also added the option of inserting a string comparer so you can ignore case if needed, and improved readability a bit. public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> items, Func<T, string> selector, StringComparer stringComparer = null) { var regex = new Regex(#"\d+", RegexOptions.Compiled); int maxDigits = items .SelectMany(i => regex.Matches(selector(i)).Cast<Match>().Select(digitChunk => (int?)digitChunk.Value.Length)) .Max() ?? 0; return items.OrderBy(i => regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), stringComparer ?? StringComparer.CurrentCulture); } Use by var sortedEmployees = employees.OrderByNatural(emp => emp.Name); This takes 450ms to sort 100,000 strings compared to 300ms for the default .net string comparison - pretty fast!
Pure C# solution for linq orderby: http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html public class NaturalSortComparer<T> : IComparer<string>, IDisposable { private bool isAscending; public NaturalSortComparer(bool inAscendingOrder = true) { this.isAscending = inAscendingOrder; } #region IComparer<string> Members public int Compare(string x, string y) { throw new NotImplementedException(); } #endregion #region IComparer<string> Members int IComparer<string>.Compare(string x, string y) { if (x == y) return 0; string[] x1, y1; if (!table.TryGetValue(x, out x1)) { x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)"); table.Add(x, x1); } if (!table.TryGetValue(y, out y1)) { y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)"); table.Add(y, y1); } int returnVal; for (int i = 0; i < x1.Length && i < y1.Length; i++) { if (x1[i] != y1[i]) { returnVal = PartCompare(x1[i], y1[i]); return isAscending ? returnVal : -returnVal; } } if (y1.Length > x1.Length) { returnVal = 1; } else if (x1.Length > y1.Length) { returnVal = -1; } else { returnVal = 0; } return isAscending ? returnVal : -returnVal; } private static int PartCompare(string left, string right) { int x, y; if (!int.TryParse(left, out x)) return left.CompareTo(right); if (!int.TryParse(right, out y)) return left.CompareTo(right); return x.CompareTo(y); } #endregion private Dictionary<string, string[]> table = new Dictionary<string, string[]>(); public void Dispose() { table.Clear(); table = null; } }
My solution: void Main() { new[] {"a4","a3","a2","a10","b5","b4","b400","1","C1d","c1d2"}.OrderBy(x => x, new NaturalStringComparer()).Dump(); } public class NaturalStringComparer : IComparer<string> { private static readonly Regex _re = new Regex(#"(?<=\D)(?=\d)|(?<=\d)(?=\D)", RegexOptions.Compiled); public int Compare(string x, string y) { x = x.ToLower(); y = y.ToLower(); if(string.Compare(x, 0, y, 0, Math.Min(x.Length, y.Length)) == 0) { if(x.Length == y.Length) return 0; return x.Length < y.Length ? -1 : 1; } var a = _re.Split(x); var b = _re.Split(y); int i = 0; while(true) { int r = PartCompare(a[i], b[i]); if(r != 0) return r; ++i; } } private static int PartCompare(string x, string y) { int a, b; if(int.TryParse(x, out a) && int.TryParse(y, out b)) return a.CompareTo(b); return x.CompareTo(y); } } Results: 1 a2 a3 a4 a10 b4 b5 b400 C1d c1d2
You do need to be careful -- I vaguely recall reading that StrCmpLogicalW, or something like it, was not strictly transitive, and I have observed .NET's sort methods to sometimes get stuck in infinite loops if the comparison function breaks that rule. A transitive comparison will always report that a < c if a < b and b < c. There exists a function that does a natural sort order comparison that does not always meet that criterion, but I can't recall whether it is StrCmpLogicalW or something else.
This is my code to sort a string having both alpha and numeric characters. First, this extension method: public static IEnumerable<string> AlphanumericSort(this IEnumerable<string> me) { return me.OrderBy(x => Regex.Replace(x, #"\d+", m => m.Value.PadLeft(50, '0'))); } Then, simply use it anywhere in your code like this: List<string> test = new List<string>() { "The 1st", "The 12th", "The 2nd" }; test = test.AlphanumericSort(); How does it works ? By replaceing with zeros: Original | Regex Replace | The | Returned List | Apply PadLeft | Sorting | List | | | "The 1st" | "The 001st" | "The 001st" | "The 1st" "The 12th" | "The 012th" | "The 002nd" | "The 2nd" "The 2nd" | "The 002nd" | "The 012th" | "The 12th" Works with multiples numbers: Alphabetical Sorting | Alphanumeric Sorting | "Page 21, Line 42" | "Page 3, Line 7" "Page 21, Line 5" | "Page 3, Line 32" "Page 3, Line 32" | "Page 21, Line 5" "Page 3, Line 7" | "Page 21, Line 42" Hope that's will help.
Here's a version for .NET Core 2.1+ / .NET 5.0+, using spans to avoid allocations public class NaturalSortStringComparer : IComparer<string> { public static NaturalSortStringComparer Ordinal { get; } = new NaturalSortStringComparer(StringComparison.Ordinal); public static NaturalSortStringComparer OrdinalIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.OrdinalIgnoreCase); public static NaturalSortStringComparer CurrentCulture { get; } = new NaturalSortStringComparer(StringComparison.CurrentCulture); public static NaturalSortStringComparer CurrentCultureIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.CurrentCultureIgnoreCase); public static NaturalSortStringComparer InvariantCulture { get; } = new NaturalSortStringComparer(StringComparison.InvariantCulture); public static NaturalSortStringComparer InvariantCultureIgnoreCase { get; } = new NaturalSortStringComparer(StringComparison.InvariantCultureIgnoreCase); private readonly StringComparison _comparison; public NaturalSortStringComparer(StringComparison comparison) { _comparison = comparison; } public int Compare(string x, string y) { // Let string.Compare handle the case where x or y is null if (x is null || y is null) return string.Compare(x, y, _comparison); var xSegments = GetSegments(x); var ySegments = GetSegments(y); while (xSegments.MoveNext() && ySegments.MoveNext()) { int cmp; // If they're both numbers, compare the value if (xSegments.CurrentIsNumber && ySegments.CurrentIsNumber) { var xValue = long.Parse(xSegments.Current); var yValue = long.Parse(ySegments.Current); cmp = xValue.CompareTo(yValue); if (cmp != 0) return cmp; } // If x is a number and y is not, x is "lesser than" y else if (xSegments.CurrentIsNumber) { return -1; } // If y is a number and x is not, x is "greater than" y else if (ySegments.CurrentIsNumber) { return 1; } // OK, neither are number, compare the segments as text cmp = xSegments.Current.CompareTo(ySegments.Current, _comparison); if (cmp != 0) return cmp; } // At this point, either all segments are equal, or one string is shorter than the other // If x is shorter, it's "lesser than" y if (x.Length < y.Length) return -1; // If x is longer, it's "greater than" y if (x.Length > y.Length) return 1; // If they have the same length, they're equal return 0; } private static StringSegmentEnumerator GetSegments(string s) => new StringSegmentEnumerator(s); private struct StringSegmentEnumerator { private readonly string _s; private int _start; private int _length; public StringSegmentEnumerator(string s) { _s = s; _start = -1; _length = 0; CurrentIsNumber = false; } public ReadOnlySpan<char> Current => _s.AsSpan(_start, _length); public bool CurrentIsNumber { get; private set; } public bool MoveNext() { var currentPosition = _start >= 0 ? _start + _length : 0; if (currentPosition >= _s.Length) return false; int start = currentPosition; bool isFirstCharDigit = Char.IsDigit(_s[currentPosition]); while (++currentPosition < _s.Length && Char.IsDigit(_s[currentPosition]) == isFirstCharDigit) { } _start = start; _length = currentPosition - start; CurrentIsNumber = isFirstCharDigit; return true; } } }
Adding to Greg Beech's answer (because I've just been searching for that), if you want to use this from Linq you can use the OrderBy that takes an IComparer. E.g.: var items = new List<MyItem>(); // fill items var sorted = items.OrderBy(item => item.Name, new NaturalStringComparer());
Here's a relatively simple example that doesn't use P/Invoke and avoids any allocation during execution. Feel free to use the code from here, or if it's easier there's a NuGet package: https://www.nuget.org/packages/NaturalSort https://github.com/drewnoakes/natural-sort internal sealed class NaturalStringComparer : IComparer<string> { public static NaturalStringComparer Instance { get; } = new NaturalStringComparer(); public int Compare(string x, string y) { // sort nulls to the start if (x == null) return y == null ? 0 : -1; if (y == null) return 1; var ix = 0; var iy = 0; while (true) { // sort shorter strings to the start if (ix >= x.Length) return iy >= y.Length ? 0 : -1; if (iy >= y.Length) return 1; var cx = x[ix]; var cy = y[iy]; int result; if (char.IsDigit(cx) && char.IsDigit(cy)) result = CompareInteger(x, y, ref ix, ref iy); else result = cx.CompareTo(y[iy]); if (result != 0) return result; ix++; iy++; } } private static int CompareInteger(string x, string y, ref int ix, ref int iy) { var lx = GetNumLength(x, ix); var ly = GetNumLength(y, iy); // shorter number first (note, doesn't handle leading zeroes) if (lx != ly) return lx.CompareTo(ly); for (var i = 0; i < lx; i++) { var result = x[ix++].CompareTo(y[iy++]); if (result != 0) return result; } return 0; } private static int GetNumLength(string s, int i) { var length = 0; while (i < s.Length && char.IsDigit(s[i++])) length++; return length; } } It doesn't ignore leading zeroes, so 01 comes after 2. Corresponding unit test: public class NumericStringComparerTests { [Fact] public void OrdersCorrectly() { AssertEqual("", ""); AssertEqual(null, null); AssertEqual("Hello", "Hello"); AssertEqual("Hello123", "Hello123"); AssertEqual("123", "123"); AssertEqual("123Hello", "123Hello"); AssertOrdered("", "Hello"); AssertOrdered(null, "Hello"); AssertOrdered("Hello", "Hello1"); AssertOrdered("Hello123", "Hello124"); AssertOrdered("Hello123", "Hello133"); AssertOrdered("Hello123", "Hello223"); AssertOrdered("123", "124"); AssertOrdered("123", "133"); AssertOrdered("123", "223"); AssertOrdered("123", "1234"); AssertOrdered("123", "2345"); AssertOrdered("0", "1"); AssertOrdered("123Hello", "124Hello"); AssertOrdered("123Hello", "133Hello"); AssertOrdered("123Hello", "223Hello"); AssertOrdered("123Hello", "1234Hello"); } private static void AssertEqual(string x, string y) { Assert.Equal(0, NaturalStringComparer.Instance.Compare(x, y)); Assert.Equal(0, NaturalStringComparer.Instance.Compare(y, x)); } private static void AssertOrdered(string x, string y) { Assert.Equal(-1, NaturalStringComparer.Instance.Compare(x, y)); Assert.Equal( 1, NaturalStringComparer.Instance.Compare(y, x)); } }
I've actually implemented it as an extension method on the StringComparer so that you could do for example: StringComparer.CurrentCulture.WithNaturalSort() or StringComparer.OrdinalIgnoreCase.WithNaturalSort(). The resulting IComparer<string> can be used in all places like OrderBy, OrderByDescending, ThenBy, ThenByDescending, SortedSet<string>, etc. And you can still easily tweak case sensitivity, culture, etc. The implementation is fairly trivial and it should perform quite well even on large sequences. I've also published it as a tiny NuGet package, so you can just do: Install-Package NaturalSort.Extension The code including XML documentation comments and suite of tests is available in the NaturalSort.Extension GitHub repository. The entire code is this (if you cannot use C# 7 yet, just install the NuGet package): public static class StringComparerNaturalSortExtension { public static IComparer<string> WithNaturalSort(this StringComparer stringComparer) => new NaturalSortComparer(stringComparer); private class NaturalSortComparer : IComparer<string> { public NaturalSortComparer(StringComparer stringComparer) { _stringComparer = stringComparer; } private readonly StringComparer _stringComparer; private static readonly Regex NumberSequenceRegex = new Regex(#"(\d+)", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static string[] Tokenize(string s) => s == null ? new string[] { } : NumberSequenceRegex.Split(s); private static ulong ParseNumberOrZero(string s) => ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0; public int Compare(string s1, string s2) { var tokens1 = Tokenize(s1); var tokens2 = Tokenize(s2); var zipCompare = tokens1.Zip(tokens2, TokenCompare).FirstOrDefault(x => x != 0); if (zipCompare != 0) return zipCompare; var lengthCompare = tokens1.Length.CompareTo(tokens2.Length); return lengthCompare; } private int TokenCompare(string token1, string token2) { var number1 = ParseNumberOrZero(token1); var number2 = ParseNumberOrZero(token2); var numberCompare = number1.CompareTo(number2); if (numberCompare != 0) return numberCompare; var stringCompare = _stringComparer.Compare(token1, token2); return stringCompare; } } }
Inspired by Michael Parker's solution, here is an IComparer implementation that you can drop in to any of the linq ordering methods: private class NaturalStringComparer : IComparer<string> { public int Compare(string left, string right) { int max = new[] { left, right } .SelectMany(x => Regex.Matches(x, #"\d+").Cast<Match>().Select(y => (int?)y.Value.Length)) .Max() ?? 0; var leftPadded = Regex.Replace(left, #"\d+", m => m.Value.PadLeft(max, '0')); var rightPadded = Regex.Replace(right, #"\d+", m => m.Value.PadLeft(max, '0')); return string.Compare(leftPadded, rightPadded); } }
Here is a naive one-line regex-less LINQ way (borrowed from python): var alphaStrings = new List<string>() { "10","2","3","4","50","11","100","a12","b12" }; var orderedString = alphaStrings.OrderBy(g => new Tuple<int, string>(g.ToCharArray().All(char.IsDigit)? int.Parse(g) : int.MaxValue, g)); // Order Now: ["2","3","4","10","11","50","100","a12","b12"]
Expanding on a couple of the previous answers and making use of extension methods, I came up with the following that doesn't have the caveats of potential multiple enumerable enumeration, or performance issues concerned with using multiple regex objects, or calling regex needlessly, that being said, it does use ToList(), which can negate the benefits in larger collections. The selector supports generic typing to allow any delegate to be assigned, the elements in the source collection are mutated by the selector, then converted to strings with ToString(). private static readonly Regex _NaturalOrderExpr = new Regex(#"\d+", RegexOptions.Compiled); public static IEnumerable<TSource> OrderByNatural<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> selector) { int max = 0; var selection = source.Select( o => { var v = selector(o); var s = v != null ? v.ToString() : String.Empty; if (!String.IsNullOrWhiteSpace(s)) { var mc = _NaturalOrderExpr.Matches(s); if (mc.Count > 0) { max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length)); } } return new { Key = o, Value = s }; }).ToList(); return selection.OrderBy( o => String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0'))) .Select(o => o.Key); } public static IEnumerable<TSource> OrderByDescendingNatural<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> selector) { int max = 0; var selection = source.Select( o => { var v = selector(o); var s = v != null ? v.ToString() : String.Empty; if (!String.IsNullOrWhiteSpace(s)) { var mc = _NaturalOrderExpr.Matches(s); if (mc.Count > 0) { max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length)); } } return new { Key = o, Value = s }; }).ToList(); return selection.OrderByDescending( o => String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0'))) .Select(o => o.Key); }
A version that's easier to read/maintain. public class NaturalStringComparer : IComparer<string> { public static NaturalStringComparer Instance { get; } = new NaturalStringComparer(); public int Compare(string x, string y) { const int LeftIsSmaller = -1; const int RightIsSmaller = 1; const int Equal = 0; var leftString = x; var rightString = y; var stringComparer = CultureInfo.CurrentCulture.CompareInfo; int rightIndex; int leftIndex; for (leftIndex = 0, rightIndex = 0; leftIndex < leftString.Length && rightIndex < rightString.Length; leftIndex++, rightIndex++) { var leftChar = leftString[leftIndex]; var rightChar = rightString[leftIndex]; var leftIsNumber = char.IsNumber(leftChar); var rightIsNumber = char.IsNumber(rightChar); if (!leftIsNumber && !rightIsNumber) { var result = stringComparer.Compare(leftString, leftIndex, 1, rightString, leftIndex, 1); if (result != 0) return result; } else if (leftIsNumber && !rightIsNumber) { return LeftIsSmaller; } else if (!leftIsNumber && rightIsNumber) { return RightIsSmaller; } else { var leftNumberLength = NumberLength(leftString, leftIndex, out var leftNumber); var rightNumberLength = NumberLength(rightString, rightIndex, out var rightNumber); if (leftNumberLength < rightNumberLength) { return LeftIsSmaller; } else if (leftNumberLength > rightNumberLength) { return RightIsSmaller; } else { if(leftNumber < rightNumber) { return LeftIsSmaller; } else if(leftNumber > rightNumber) { return RightIsSmaller; } } } } if (leftString.Length < rightString.Length) { return LeftIsSmaller; } else if(leftString.Length > rightString.Length) { return RightIsSmaller; } return Equal; } public int NumberLength(string str, int offset, out int number) { if (string.IsNullOrWhiteSpace(str)) throw new ArgumentNullException(nameof(str)); if (offset >= str.Length) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be less than the length of the string."); var currentOffset = offset; var curChar = str[currentOffset]; if (!char.IsNumber(curChar)) throw new ArgumentException($"'{curChar}' is not a number.", nameof(offset)); int length = 1; var numberString = string.Empty; for (currentOffset = offset + 1; currentOffset < str.Length; currentOffset++, length++) { curChar = str[currentOffset]; numberString += curChar; if (!char.IsNumber(curChar)) { number = int.Parse(numberString); return length; } } number = int.Parse(numberString); return length; } }
We had a need for a natural sort to deal with text with the following pattern: "Test 1-1-1 something" "Test 1-2-3 something" ... For some reason when I first looked on SO, I didn't find this post and implemented our own. Compared to some of the solutions presented here, while similar in concept, it could have the benefit of maybe being simpler and easier to understand. However, while I did try to look at performance bottlenecks, It is still a much slower implementation than the default OrderBy(). Here is the extension method I implement: public static class EnumerableExtensions { // set up the regex parser once and for all private static readonly Regex Regex = new Regex(#"\d+|\D+", RegexOptions.Compiled | RegexOptions.Singleline); // stateless comparer can be built once private static readonly AggregateComparer Comparer = new AggregateComparer(); public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> source, Func<T, string> selector) { // first extract string from object using selector // then extract digit and non-digit groups Func<T, IEnumerable<IComparable>> splitter = s => Regex.Matches(selector(s)) .Cast<Match>() .Select(m => Char.IsDigit(m.Value[0]) ? (IComparable) int.Parse(m.Value) : m.Value); return source.OrderBy(splitter, Comparer); } /// <summary> /// This comparer will compare two lists of objects against each other /// </summary> /// <remarks>Objects in each list are compare to their corresponding elements in the other /// list until a difference is found.</remarks> private class AggregateComparer : IComparer<IEnumerable<IComparable>> { public int Compare(IEnumerable<IComparable> x, IEnumerable<IComparable> y) { return x.Zip(y, (a, b) => new {a, b}) // walk both lists .Select(pair => pair.a.CompareTo(pair.b)) // compare each object .FirstOrDefault(result => result != 0); // until a difference is found } } } The idea is to split the original strings into blocks of digits and non-digits ("\d+|\D+"). Since this is a potentially expensive task, it is done only once per entry. We then use a comparer of comparable objects (sorry, I can't find a more proper way to say it). It compares each block to its corresponding block in the other string. I would like feedback on how this could be improved and what the major flaws are. Note that maintainability is important to us at this point and we are not currently using this in extremely large data sets.
Let me explain my problem and how i was able to solve it. Problem:- Sort files based on FileName from FileInfo objects which are retrieved from a Directory. Solution:- I selected the file names from FileInfo and trimed the ".png" part of the file name. Now, just do List.Sort(), which sorts the filenames in Natural sorting order. Based on my testing i found that having .png messes up sorting order. Have a look at the below code var imageNameList = new DirectoryInfo(#"C:\Temp\Images").GetFiles("*.png").Select(x =>x.Name.Substring(0, x.Name.Length - 4)).ToList(); imageNameList.Sort();
Get the index of the nth occurrence of a string?
Unless I am missing an obvious built-in method, what is the quickest way to get the nth occurrence of a string within a string? I realize that I could loop the IndexOf method by updating its start index on each iteration of the loop. But doing it this way seems wasteful to me.
You really could use the regular expression /((s).*?){n}/ to search for n-th occurrence of substring s. In C# it might look like this: public static class StringExtender { public static int NthIndexOf(this string target, string value, int n) { Match m = Regex.Match(target, "((" + Regex.Escape(value) + ").*?){" + n + "}"); if (m.Success) return m.Groups[2].Captures[n - 1].Index; else return -1; } } Note: I have added Regex.Escape to original solution to allow searching characters which have special meaning to regex engine.
That's basically what you need to do - or at least, it's the easiest solution. All you'd be "wasting" is the cost of n method invocations - you won't actually be checking any case twice, if you think about it. (IndexOf will return as soon as it finds the match, and you'll keep going from where it left off.)
That's basically what you need to do - or at least, it's the easiest solution. All you'd be "wasting" is the cost of n method invocations - you won't actually be checking any case twice, if you think about it. (IndexOf will return as soon as it finds the match, and you'll keep going from where it left off.) Here is the recursive implementation (of the above idea) as an extension method, mimicing the format of the framework method(s): public static int IndexOfNth(this string input, string value, int startIndex, int nth) { if (nth < 1) throw new NotSupportedException("Param 'nth' must be greater than 0!"); if (nth == 1) return input.IndexOf(value, startIndex); var idx = input.IndexOf(value, startIndex); if (idx == -1) return -1; return input.IndexOfNth(value, idx + 1, --nth); } Also, here are some (MBUnit) unit tests that might help you (to prove it is correct): using System; using MbUnit.Framework; namespace IndexOfNthTest { [TestFixture] public class Tests { //has 4 instances of the private const string Input = "TestTest"; private const string Token = "Test"; /* Test for 0th index */ [Test] public void TestZero() { Assert.Throws<NotSupportedException>( () => Input.IndexOfNth(Token, 0, 0)); } /* Test the two standard cases (1st and 2nd) */ [Test] public void TestFirst() { Assert.AreEqual(0, Input.IndexOfNth("Test", 0, 1)); } [Test] public void TestSecond() { Assert.AreEqual(4, Input.IndexOfNth("Test", 0, 2)); } /* Test the 'out of bounds' case */ [Test] public void TestThird() { Assert.AreEqual(-1, Input.IndexOfNth("Test", 0, 3)); } /* Test the offset case (in and out of bounds) */ [Test] public void TestFirstWithOneOffset() { Assert.AreEqual(4, Input.IndexOfNth("Test", 4, 1)); } [Test] public void TestFirstWithTwoOffsets() { Assert.AreEqual(-1, Input.IndexOfNth("Test", 8, 1)); } } }
private int IndexOfOccurence(string s, string match, int occurence) { int i = 1; int index = 0; while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1) { if (i == occurence) return index; i++; } return -1; } or in C# with extension methods public static int IndexOfOccurence(this string s, string match, int occurence) { int i = 1; int index = 0; while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1) { if (i == occurence) return index; i++; } return -1; }
After some benchmarking, this seems to be the simplest and most effcient solution public static int IndexOfNthSB(string input, char value, int startIndex, int nth) { if (nth < 1) throw new NotSupportedException("Param 'nth' must be greater than 0!"); var nResult = 0; for (int i = startIndex; i < input.Length; i++) { if (input[i] == value) nResult++; if (nResult == nth) return i; } return -1; }
Here I go again! Another benchmark answer from yours truly :-) Once again based on the fantastic BenchmarkDotNet package (if you're serious about benchmarking dotnet code, please, please use this package). The motivation for this post is two fold: PeteT (who asked it originally) wondered that it seems wasteful to use String.IndexOf varying the startIndex parameter in a loop to find the nth occurrence of a character while, in fact, it's the fastest method, and because some answers uses regular expressions which are an order of magnitude slower (and do not add any benefits, in my opinion not even readability, in this specific case). Here is the code I've ended up using in my string extensions library (it's not a new answer to this question, since others have already posted semantically identical code here, I'm not taking credit for it). This is the fastest method (even, possibly, including unsafe variations - more on that later): public static int IndexOfNth(this string str, char ch, int nth, int startIndex = 0) { if (str == null) throw new ArgumentNullException("str"); var idx = str.IndexOf(ch, startIndex); while (idx >= 0 && --nth > 0) idx = str.IndexOf(ch, startIndex + idx + 1); return idx; } I've benchmarked this code against two other methods and the results follow: The benchmarked methods were: [Benchmark] public int FindNthRegex() { Match m = Regex.Match(text, "((" + Regex.Escape("z") + ").*?){" + Nth + "}"); return (m.Success) ? m.Groups[2].Captures[Nth - 1].Index : -1; } [Benchmark] public int FindNthCharByChar() { var occurrence = 0; for (int i = 0; i < text.Length; i++) { if (text[i] == 'z') occurrence++; if (Nth == occurrence) return i; } return -1; } [Benchmark] public int FindNthIndexOfStartIdx() { var idx = text.IndexOf('z', 0); var nth = Nth; while (idx >= 0 && --nth > 0) idx = text.IndexOf('z', idx + 1); return idx; } The FindNthRegex method is the slower of the bunch, taking an order (or two) of magnitude more time than the fastest. FindNthByChar loops over each char on the string and counts each match until it finds the nth occurrence. FindNthIndexOfStartIdx uses the method suggested by the opener of this question which, indeed, is the same I've been using for ages to accomplish this and it is the fastest of them all. Why is it so much faster than FindNthByChar? It's because Microsoft went to great lengths to make string manipulation as fast as possible in the dotnet framework. And they've accomplished that! They did an amazing job! I've done a deeper investigation on string manipulations in dotnet in an CodeProject article which tries to find the fastest method to remove all whitespace from a string: Fastest method to remove all whitespace from Strings in .NET There you'll find why string manipulations in dotnet are so fast, and why it's next to useless trying to squeeze more speed by writing our own versions of the framework's string manipulation code (the likes of string.IndexOf, string.Split, string.Replace, etc.) The full benchmark code I've used follows (it's a dotnet6 console program): UPDATE: Added two methods FindNthCharByCharInSpan and FindNthCharRecursive (and now FindNthByLinq). using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Text; using System.Text.RegularExpressions; var summary = BenchmarkRunner.Run<BenchmarkFindNthChar>(); public class BenchmarkFindNthChar { const string BaseText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; [Params(100, 1000)] public int BaseTextRepeatCount { get; set; } [Params(500)] public int Nth { get; set; } private string text; [GlobalSetup] public void BuildTestData() { var sb = new StringBuilder(); for (int i = 0; i < BaseTextRepeatCount; i++) sb.AppendLine(BaseText); text = sb.ToString(); } [Benchmark] public int FindNthRegex() { Match m = Regex.Match(text, "((" + Regex.Escape("z") + ").*?){" + Nth + "}"); return (m.Success) ? m.Groups[2].Captures[Nth - 1].Index : -1; } [Benchmark] public int FindNthCharByChar() { var occurrence = 0; for (int i = 0; i < text.Length; i++) { if (text[i] == 'z') occurrence++; if (Nth == occurrence) return i; } return -1; } [Benchmark] public int FindNthIndexOfStartIdx() { var idx = text.IndexOf('z', 0); var nth = Nth; while (idx >= 0 && --nth > 0) idx = text.IndexOf('z', idx + 1); return idx; } [Benchmark] public int FindNthCharByCharInSpan() { var span = text.AsSpan(); var occurrence = 0; for (int i = 0; i < span.Length; i++) { if (span[i] == 'z') occurrence++; if (Nth == occurrence) return i; } return -1; } [Benchmark] public int FindNthCharRecursive() => IndexOfNth(text, "z", 0, Nth); public static int IndexOfNth(string input, string value, int startIndex, int nth) { if (nth == 1) return input.IndexOf(value, startIndex); var idx = input.IndexOf(value, startIndex); if (idx == -1) return -1; return IndexOfNth(input, value, idx + 1, --nth); } [Benchmark] public int FindNthByLinq() { var items = text.Select((c, i) => (c, i)).Where(t => t.c == 'z'); return (items.Count() > Nth - 1) ? items.ElementAt(Nth - 1).i : -1; } } UPDATE 2: The new benchmark results (with Linq-based benchmark) follows: The Linq-based solution is only better than the recursive method, but it's good to have it here for completeness.
Maybe it would also be nice to work with the String.Split() Method and check if the requested occurrence is in the array, if you don't need the index, but the value at the index
Or something like this with the do while loop private static int OrdinalIndexOf(string str, string substr, int n) { int pos = -1; do { pos = str.IndexOf(substr, pos + 1); } while (n-- > 0 && pos != -1); return pos; }
System.ValueTuple ftw: var index = line.Select((x, i) => (x, i)).Where(x => x.Item1 == '"').ElementAt(5).Item2; writing a function from that is homework
Tod's answer can be simplified somewhat. using System; static class MainClass { private static int IndexOfNth(this string target, string substring, int seqNr, int startIdx = 0) { if (seqNr < 1) { throw new IndexOutOfRangeException("Parameter 'nth' must be greater than 0."); } var idx = target.IndexOf(substring, startIdx); if (idx < 0 || seqNr == 1) { return idx; } return target.IndexOfNth(substring, --seqNr, ++idx); // skip } static void Main () { Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 1)); Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 2)); Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 3)); Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 4)); } } Output 1 3 5 -1
This might do it: Console.WriteLine(str.IndexOf((#"\")+2)+1);