Here's an option that uses the curio of being able to treat chars as ints. It relies on the characters in the strings being a-z (but see the footnote if you want a wider range of chars). It's a simplistic form of dictionary where the character itself is used as an indexer, but it doesn't need any advanced technology at all - it purely relies on arrays and ints (and if you class foreach
/enumerations of chars in a string as advanced they could be swapped for a normal for
):
static bool SameChars(string str1, string str2){
if(str1.Length != str2.Length) return false;
var counts = new int[26];
var numPositive = 0;
foreach(var c in str1){
var r = ++counts[c - 'a'];
if(r == 1) numPositive++;
}
foreach(var c in str2){
var r = --counts[c - 'a'];
if(r < 0) return false;
if(r == 0) numPositive--;
}
return numPositive == 0;
}
If the string lengths differ it's an easy win. Otherwise we'll use an array of 26 ints and index it by the character we're looking at, to keep a count of the number of times we see each character. Suppose we're iterating and we encounter our first letter a
- the code ++counts[c - 'a']
will do 'a' - 'a'
(which is 0) and increment array index [0]
from 0 to 1.
The resulting counter, 1 is captured into r
. We examine r
and see if it's 1; the only time it will reasonably be 1 is the first time we see a given letter, so we increment numPositive
which is a counter of the number of array indexes that are holding a positive number
We then loop over the second string, using the chars in it to decrement the counts. Again we capture the number now stored in that array position, and here we can make a small optimization; if any array index goes negative, then str2
must contain more of that character than str1
does, so early return false
If the resulting counter for a given letter has hit 0, decrement 1 from numPositive
, the variable that is tracking the number of array positions that are holding positive numbers.
At the end of the operation we want to know if any of the counts elements are still holding a positive number; rather than checking each one, we can just look to see if numPositive
is 0
Footnote: If you wanted to support a wider character range you'd need a bigger array (e.g. if you want to support A-Z and a-z you'll need an array that is 'z'-'A'
long, and you'll need to do - 'A'
in your math
Edit:
Swapping the foreach
out for for
makes things about 10% quicker:
static bool SameCharsF(string str1, string str2){
if(str1.Length != str2.Length) return false;
var counts = new int[26];
var numPositive = 0;
for(int i = 0; i < str1.Length; i++){
var r = ++counts[str1[i] - 'a'];
if(r == 1) numPositive++;
}
for(int i = 0; i < str2.Length; i++){
var r = --counts[str1[i] - 'a'];
if(r < 0) return false;
if(r == 0) numPositive--;
}
return numPositive == 0;
}
Here are a couple of variations that use Dictionary; they can cope with any number of any kind of char without special treatment but they're 2-3x slower. Surprisingly, the four loop version that preloads the dictionary so ContainsKey can be omitted isn't significantly slower than the ContainsKey version:
static bool SameCharsDP(string str1, string str2){
if(str1.Length != str2.Length) return false;
var d = new Dictionary<char, int>();
var numPositive = 0;
foreach(var c in str1)
d[c] = 0;
foreach(var c in str2)
d[c] = 0;
foreach(var c in str1){
var r = ++d[c];
if(r == 1) numPositive++;
}
foreach(var c in str2){
var r = --d[c];
if(r < 0) return false;
if(r == 0) numPositive--;
}
return numPositive == 0;
}
static bool SameCharsDCK(string str1, string str2){
if(str1.Length != str2.Length) return false;
var d = new Dictionary<char, int>();
var numPositive = 0;
foreach(var c in str1){
if(!d.ContainsKey(c))
d[c]=0;
var r = ++d[c];
if(r == 1) numPositive++;
}
foreach(var c in str2){
if(!d.ContainsKey(c))
d[c]=0;
var r = --d[c];
if(r < 0) return false;
if(r == 0) numPositive--;
}
return numPositive == 0;
}