It's easier to do it with LINQ which is in this case virtually a one-liner.
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 @PieterWitvoet
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 StringComparer.OrdinalIgnoreCase. You can also use this to create a dictionary that is non-case-sensitive:
var dic = new Dictionary<string, int>(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<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; }
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 < 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(); }
And the tested methods:
// by @somnia06 (OP) public static string FindFirstNonRepeatingCharacter0(string givenString) { char nonRepeatingCharacter = ' '; Dictionary<char, int> charCounter = new Dictionary<char, int>(); 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.ToString(); } // by @somnia06 (OP) + modified by @t3chb0t public static string FindFirstNonRepeatingCharacter1(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; } // 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<string, List<int>>(StringComparer.OrdinalIgnoreCase); foreach (var x in givenString.Select((c, i) => new { c = c.ToString(), i })) { var indicies = (List<int>)null; if (charIndex.TryGetValue(x.c, out indicies)) { indicies.Add(x.i); } else { charIndex[x.c] = new List<int>(new[] { x.i }); } } var firstSingleCharIndex = charIndex .Where(i => i.Value.Count == 1).ToList(); if (!firstSingleCharIndex.Any()) return ""; return givenString[firstSingleCharIndex.Min(i => i.Value.FirstOrDefault())].ToString(); } // by @somnia06 (OP) + modified by @t3chb0t public static string FindFirstNonRepeatingCharacter4(string givenString) { var charCounter = new Dictionary<char, int>(new UpperCaseInvariantCharComparer()); for (int i = 0; i < givenString.Length; i++) { var ch = givenString[i]; if (charCounter.ContainsKey(ch)) charCounter[ch]++; else charCounter[ch] = 1; } for (int i = 0; i < givenString.Length; i++) { var ch = givenString[i]; if (charCounter[ch] == 1) { return ch.ToString(); } } return null; } class UpperCaseInvariantCharComparer : IEqualityComparer<char> { public bool Equals(char x, char y) => Char.ToUpperInvariant(x) == Char.ToUpperInvariant(y); public int GetHashCode(char x) => Char.ToUpperInvariant(x).GetHashCode(); } // by @AhmadIbrahim public static string FindFirstNonRepeatingCharacter5(string input) { if (input == null) throw new ArgumentNullException(); var repeatingChars = new List<char>(); var candidates = new List<char>(); foreach (var i in input) { var c = char.ToUpperInvariant(i); if (repeatingChars.Contains(c)) continue; if (candidates.Contains(c)) { candidates.Remove(c); repeatingChars.Add(c); } else { candidates.Add(c); } } if (candidates.Count == 0) throw new Exception("No non-repeating characters"); return candidates[0].ToString(); }