HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Find the first non-repeating character in a string: ("DEFD" → E)

Submitted by: @import:stackexchange-codereview··
0
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:

/// 
    /// 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.

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(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 @PieterWitvoet
var 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.