patterncsharpModerate
Find the first non-repeating character in a string: ("DEFD" → E)
Viewed 0 times
thenonrepeatingcharacterfirstdefdfindstring
Problem
I recently had an interview and got to phase 2 which is a coding assessment. One of the questions was:
Find the first non-repeating character in a string: ("DEFD" --> E)
I didn't pass the coding assessment and was wondering if the community can help me improve.
Solution:
Test:
```
[TestMethod()]
public void FindFirstNonRepeatingTestCharacterAllUpperCase()
{
string testString = "DEFD";
char testChar = BlueWolfSolutions.FindFirstNonRepeatingCharacter(testString);
Assert.IsFalse(testChar == 'D' && testChar == 'F');
Assert.AreEqual('E', testChar);
}
///
/// Testing lower case entries.
///
[TestMethod()]
public void FindFirstNonRepeatingCharacterTestAllLowerCase()
{
string testString = "dfed";
char testChar = Blue
Find the first non-repeating character in a string: ("DEFD" --> E)
I didn't pass the coding assessment and was wondering if the community can help me improve.
Solution:
///
/// Assuming that letter case doesn't matter, I will convert letters to upper case.
/// I will return ' ' if the non repeating character does not exist.
///
///
/// A string
/// The first non repeating character or ' '
public static char FindFirstNonRepeatingCharacter(string givenString)
{
char nonRepeatingCharacter = ' ';
Dictionary charCounter = new Dictionary();
char[] charArray = givenString.ToUpper().ToCharArray();
for (int i = 0; i < charArray.Length; i++)
{
char currentChar = charArray[i];
if (charCounter.ContainsKey(currentChar)) {
charCounter[currentChar]++;
} else
{
charCounter.Add(currentChar, 1);
}
}
for (int i = 0; i < charArray.Length; i++)
{
char currentChar = charArray[i];
if (charCounter[currentChar] == 1)
{
nonRepeatingCharacter = currentChar;
break;
}
}
return nonRepeatingCharacter;
}Test:
```
[TestMethod()]
public void FindFirstNonRepeatingTestCharacterAllUpperCase()
{
string testString = "DEFD";
char testChar = BlueWolfSolutions.FindFirstNonRepeatingCharacter(testString);
Assert.IsFalse(testChar == 'D' && testChar == 'F');
Assert.AreEqual('E', testChar);
}
///
/// Testing lower case entries.
///
[TestMethod()]
public void FindFirstNonRepeatingCharacterTestAllLowerCase()
{
string testString = "dfed";
char testChar = Blue
Solution
It's easier to do it with LINQ which is in this case virtually a one-liner.
As far as your code is concerned here are a few improvements:
There is no need to convert the string to lower or upper case. Instead you can use the
You don't have to turn a string into an array, it is an array already so you can simply iterate with a
Now that we know this we can shorten it too (if we are still not going to use linq to help us):
EDIT: Performance Tests
I've tested some of the suggestet solutions and here are the results:
Source code:
And the tested methods:
```
// by @somnia06 (OP)
public static string FindFirstNonRepeatingCharacter0(string givenString)
{
char nonRepeatingCharacter = ' ';
Dictionary charCounter = new Dictionary();
char[] charArray = givenString.ToUpper().ToCharArray();
for (int i = 0; i (StringComparer.OrdinalIgnoreCase);
foreach (var ch in givenString)
{
charCounter[ch.ToString()] =
charCounter.ContainsKey(ch.ToString())
? charCounter[ch.ToString()] + 1
: 1;
}
foreach (var ch in givenString)
{
if (charCounter[ch.ToString()] == 1)
{
return ch.ToString();
}
}
return null;
}
// by @t3chb0t
public static string FindFirstNonRepeatingCharacter21(string givenString)
{
return
givenString
.GroupBy(c => c.ToString(), c => c, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.FirstOrDefault();
}
// by @t3chb0t
public static string FindFirstNonRepeatingCharacter22(string givenString)
{
return
givenString
.GroupBy(c => c, c => c)
.Where(g => g.Count() == 1)
.Select(g => g.Key.ToString())
.FirstOrDefault();
}
// by @somnia06 (OP) + modified by @t3chb0t
public static string FindFirstNonRepeatingCharacter3(string givenString)
{
var charIndex = new Dictionary>(StringComparer.OrdinalIgnoreCase);
foreach (var x in givenString.Select((c, i) => new { c = c.ToString(), i }))
{
var indicies = (List)null;
if (charIndex.TryGetValue(x.c, out indicies))
{
indi
var text = "DEFD";
var result =
text
.AsQueryable() // this isn't necessary as noted by @CodesInChaos
.GroupBy(c => c.ToString(), c => c, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.First(); // this should be FirstOrDefault as noted by @PieterWitvoetAs far as your code is concerned here are a few improvements:
There is no need to convert the string to lower or upper case. Instead you can use the
StringComparer.OrdinalIgnoreCase. You can also use this to create a dictionary that is non-case-sensitive:var dic = new Dictionary(StringComparer.OrdinalIgnoreCase);You don't have to turn a string into an array, it is an array already so you can simply iterate with a
foreach over it.Now that we know this we can shorten it too (if we are still not going to use linq to help us):
public static string FindFirstNonRepeatingCharacter(string givenString)
{
var charCounter = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var ch in givenString)
{
charCounter[ch.ToString()] =
charCounter.ContainsKey(ch.ToString())
? charCounter[ch.ToString()] + 1
: 1;
}
foreach (var ch in givenString)
{
if (charCounter[ch.ToString()] == 1)
{
return ch.ToString();
}
}
return null;
}EDIT: Performance Tests
I've tested some of the suggestet solutions and here are the results:
Original (ToUpper) Ü 00:00:00.0962950 100,00 %
Original (StringComparer) ü 00:00:00.4488175 466,00 %
GroupBy (string) ü 00:00:00.1212308 126,00 %
GroupBy (char) ü 00:00:00.0362518 38,00 %
Indexed ü 00:00:00.1571029 163,00 %
Custom equality char comparer ü 00:00:00.3152172 327,00 %
Not counting chars Ü 00:00:00.0861682 90,00 %Source code:
void Main()
{
// creating some random test string with 1.000.000 + 1 charachters
var textBuilder = new StringBuilder();
var length = 1000000;
var rnd = new Random((int)DateTime.Now.Ticks);
for (var i = 0; i >
{
["Original (ToUpper)"] = FindFirstNonRepeatingCharacter0,
["Original (StringComparer)"] = FindFirstNonRepeatingCharacter1,
["GroupBy (string) "] = FindFirstNonRepeatingCharacter21,
["GroupBy (char) "] = FindFirstNonRepeatingCharacter22,
["Indexed"] = FindFirstNonRepeatingCharacter3,
["Custom equality char comparer"] = FindFirstNonRepeatingCharacter4,
["Not counting chars"] = FindFirstNonRepeatingCharacter5,
};
var results = new List();
// running tests
foreach (var func in funcs)
{
var sw = Stopwatch.StartNew();
var ch = func.Value(text);
sw.Stop();
// gathering results
results.Add(new
{
Func = func.Key,
Result = ch,
Elapsed = sw.Elapsed,
// calculating performance in relation to OP's function
Difference = string.Format("{0,10:P2}", results.Count == 0 ? 1 : Math.Round(sw.Elapsed.TotalMilliseconds / results[0].Elapsed.TotalMilliseconds, 2) * 1)
});
}
results.Dump();
}And the tested methods:
```
// by @somnia06 (OP)
public static string FindFirstNonRepeatingCharacter0(string givenString)
{
char nonRepeatingCharacter = ' ';
Dictionary charCounter = new Dictionary();
char[] charArray = givenString.ToUpper().ToCharArray();
for (int i = 0; i (StringComparer.OrdinalIgnoreCase);
foreach (var ch in givenString)
{
charCounter[ch.ToString()] =
charCounter.ContainsKey(ch.ToString())
? charCounter[ch.ToString()] + 1
: 1;
}
foreach (var ch in givenString)
{
if (charCounter[ch.ToString()] == 1)
{
return ch.ToString();
}
}
return null;
}
// by @t3chb0t
public static string FindFirstNonRepeatingCharacter21(string givenString)
{
return
givenString
.GroupBy(c => c.ToString(), c => c, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.FirstOrDefault();
}
// by @t3chb0t
public static string FindFirstNonRepeatingCharacter22(string givenString)
{
return
givenString
.GroupBy(c => c, c => c)
.Where(g => g.Count() == 1)
.Select(g => g.Key.ToString())
.FirstOrDefault();
}
// by @somnia06 (OP) + modified by @t3chb0t
public static string FindFirstNonRepeatingCharacter3(string givenString)
{
var charIndex = new Dictionary>(StringComparer.OrdinalIgnoreCase);
foreach (var x in givenString.Select((c, i) => new { c = c.ToString(), i }))
{
var indicies = (List)null;
if (charIndex.TryGetValue(x.c, out indicies))
{
indi
Code Snippets
var text = "DEFD";
var result =
text
.AsQueryable() // this isn't necessary as noted by @CodesInChaos
.GroupBy(c => c.ToString(), c => c, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.First(); // this should be FirstOrDefault as noted by @PieterWitvoetvar dic = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);public static string FindFirstNonRepeatingCharacter(string givenString)
{
var charCounter = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (var ch in givenString)
{
charCounter[ch.ToString()] =
charCounter.ContainsKey(ch.ToString())
? charCounter[ch.ToString()] + 1
: 1;
}
foreach (var ch in givenString)
{
if (charCounter[ch.ToString()] == 1)
{
return ch.ToString();
}
}
return null;
}Original (ToUpper) Ü 00:00:00.0962950 100,00 %
Original (StringComparer) ü 00:00:00.4488175 466,00 %
GroupBy (string) ü 00:00:00.1212308 126,00 %
GroupBy (char) ü 00:00:00.0362518 38,00 %
Indexed ü 00:00:00.1571029 163,00 %
Custom equality char comparer ü 00:00:00.3152172 327,00 %
Not counting chars Ü 00:00:00.0861682 90,00 %void Main()
{
// creating some random test string with 1.000.000 + 1 charachters
var textBuilder = new StringBuilder();
var length = 1000000;
var rnd = new Random((int)DateTime.Now.Ticks);
for (var i = 0; i < length; i++)
{
if (rnd.Next(1, 3) == 1)
textBuilder.Append((char)rnd.Next(65, 91));
else
textBuilder.Append((char)rnd.Next(97, 123));
}
textBuilder.Append("ü");
var text = textBuilder.ToString();
// test functions and descriptions
var funcs = new Dictionary<string, Func<string, string>>
{
["Original (ToUpper)"] = FindFirstNonRepeatingCharacter0,
["Original (StringComparer)"] = FindFirstNonRepeatingCharacter1,
["GroupBy (string) "] = FindFirstNonRepeatingCharacter21,
["GroupBy (char) "] = FindFirstNonRepeatingCharacter22,
["Indexed"] = FindFirstNonRepeatingCharacter3,
["Custom equality char comparer"] = FindFirstNonRepeatingCharacter4,
["Not counting chars"] = FindFirstNonRepeatingCharacter5,
};
var results = new List<dynamic>();
// running tests
foreach (var func in funcs)
{
var sw = Stopwatch.StartNew();
var ch = func.Value(text);
sw.Stop();
// gathering results
results.Add(new
{
Func = func.Key,
Result = ch,
Elapsed = sw.Elapsed,
// calculating performance in relation to OP's function
Difference = string.Format("{0,10:P2}", results.Count == 0 ? 1 : Math.Round(sw.Elapsed.TotalMilliseconds / results[0].Elapsed.TotalMilliseconds, 2) * 1)
});
}
results.Dump();
}Context
StackExchange Code Review Q#133518, answer score: 18
Revisions (0)
No revisions yet.