Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

The basic idea is not to allow the same text and add +1 at the end.

Imagine that I have a datagrid (WPF) with names, and every time I click the add button, I will put a new name on the list automatically by running this method to ensure that no two names are alike.

My standard used: if there is repeated text, add to the end "(" + count + ")", similar to Windows Explorer.

Are there ways to make this using LINQ or Regex?

private void Test()
{
    var result = CreateText("t", new string[] { "t", "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t(8)", "t(9)", "t(10)" });
    Assert.IsTrue(result == "t(11)");
}

public static String CreateText(string newText, String[] texts)
{
    int addCount = 0;
    foreach (String text in texts)
    {
        if (text.IndexOf(newText) == 0)
        {
            if (text == newText && addCount == 0)
            {
                addCount = 1;
                continue;
            }
            else
            {
                if (text.LastIndexOf('(') == newText.Length && text.LastIndexOf(')') == text.Length - 1)
                {
                    try
                    {
                        var initial = text.LastIndexOf('(') + 1;
                        var size = text.LastIndexOf(')') - initial;
                        int count = Int16.Parse(text.Substring(initial, size));
                        if (addCount <= count)
                        {
                            addCount = count + 1;
                        }
                    }
                    catch (Exception)
                    {
                        addCount = 0;
                    }
                }
            }
        }
    }
    if (addCount > 0)
    {
        newText += "(" + addCount + ")";
    }
    return newText;
}
share|improve this question
    
It looks kind of under specified to me. What is the output of {"t(1)", "a(2)"}? {"t(t(1))", "T(42)"}? {"hello","world"}? Your test suggests you didn't clarify to yourself what are the requirements here. –  avip Nov 14 '12 at 7:56
    
@avip sorry, I will clarify the operation –  J. Lennon Nov 14 '12 at 10:01

3 Answers 3

up vote 4 down vote accepted

Here is shorter method with regex

    private static String CreateText(string textIdentifier, IEnumerable<string> texts)
    {
        const int defaultNumber = 1;

        Regex regex = new Regex(@"\(\s*(?<num>\d+)\s*\)");

        var numbers = 
            texts.Select(p => p.Trim()).Where(p => p.StartsWith(textIdentifier))
            .Select(str => { var m = regex.Match(str); return m.Success ? int.Parse(m.Groups["num"].Value) + 1 : defaultNumber; });

        int max = numbers.Any() ? numbers.Max() : 0;

        return max == 0 ? textIdentifier : string.Format("{0}({1})", textIdentifier, max);
    }

+1 to @dreza for tests ;)

share|improve this answer
    
+1 to give you credit for doing most of the work for my answer :) –  Gene S Nov 14 '12 at 19:22

There might be one problem with @Danil code. Although it passes the unit test that was provided what if the array contained values of "t", "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t(8)", "t(9)", "test(10)"

In this case I would think you would want the return value to be t(10). Maybe the above scenerio is not possible, but if it is, here is a revision to @Danil code which would handle it.

private static String CreateText(string textIdentifier, IEnumerable<string> texts)
{
    const int defaultNumber = 0;

    Regex regex = new Regex("^" + textIdentifier + @"\(\s*(?<num>\d+)\s*\)");

    var max = 
        texts.Select(p => p.Trim())
        .Select(str => { var m = regex.Match(str); return m.Success ? int.Parse(m.Groups["num"].Value) + 1 : defaultNumber; })
        .Max();

    return max == 0 ? textIdentifier : string.Format("{0}({1})", textIdentifier, max);
}
share|improve this answer
    
Yep, you are right. I've missed it. –  Danil Nov 14 '12 at 14:58
    
+1 didn't think of that case test(10) nice spotting –  dreza Nov 14 '12 at 19:15
    
worked well in tests. but looking now, this code does not meet one of the requirements. it still lets texts exactly alike. –  J. Lennon Nov 26 '12 at 16:21

Here's a way. Not fully Linq but using a little bit in the for loop. Also passes a few more unit tests than your original.

[EDIT]: After re-reading your question and some comments by other reviewers I added a few more unit tests and low and behold my original answer broke. So I've re-worked it and now made it even more complicated :) but passes the new unit tests I put in 10,11 and 12.

Whether your code needs to pass thoses tests or not though I'll leave that for you to decide.

As for optimization. I haven't profiled it so can't comment there.

    [TestMethod]
    public void Test()
    {
        var result = CreateText("t", new string[] { "t", "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t(8)", "t(9)", "t(10)" });
        Assert.IsTrue(result == "t(11)");
    }
    [TestMethod]
    public void Test1()
    {
        var result = CreateText("t", new string[] { "t" });
        Assert.AreEqual("t(1)", result);
    }

    [TestMethod]
    public void Test2()
    {
        var result = CreateText("t", new string[] { "t", "t(4)", "t(2)", "z(3)", "t (45)", "t5)", "t(10)", "t(12)", "t( 89 ),ttt, t, t(23), t(8, t(we", "t( 89 )", "t(23)", "t(8", "t(we" });
        Assert.AreEqual("t(90)", result);
    }

    [TestMethod]
    public void Test3()
    {
        var result = CreateText("t", new string[] { });
        Assert.IsTrue(result == "t");
    }

    [TestMethod]
    public void Test4()
    {
        var result = CreateText("z", new string[] { "t(1)" });
        Assert.AreEqual("z", result);
    }

    [TestMethod]
    public void Test5()
    {
        var result = CreateText("z", new string[] { "t(-1)" });
        Assert.AreEqual("z", result);
    }

    [TestMethod]
    public void Test6()
    {
        var result = CreateText("t", new string[] { "    t(0)" });
        Assert.AreEqual("t(1)", result);
    }

    [TestMethod]
    public void Test7()
    {
        var result = CreateText("t", new string[] { "    t(9)     " });
        Assert.AreEqual("t(10)", result);
    }

    [TestMethod]
    public void Test8()
    {
        var result = CreateText("t", new string[] { "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t(8)", "t(9)", "test(10)" });
        Assert.AreEqual("t(10)", result);
    }

    [TestMethod]
    public void Test9()
    {
        var result = CreateText("t", new string[] { "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t(8)", "t (9)", "test(10)" });
        Assert.AreEqual("t(10)", result);
    }

    [TestMethod]
    public void Test10()
    {
        var result = CreateText("t", new string[] { "t(1)", "t(2)", "t(3)", "t(4)", "t(5)", "t(6)", "t(7)", "t t(8)", "t (9)", "test(10)" });
        Assert.AreEqual("t(10)", result);
    }

    [TestMethod]
    public void Test11()
    {
        var result = CreateText("t", new string[] { "t(1)", "t(2)", "t(3)", "t(4)", "t((((24)", "ttt(22)", "t(7)", "t t(8)", "t (9)", "t(1)" });
        Assert.AreEqual("t(10)", result);
    }

    [TestMethod]
    public void Test12()
    {
        var result = CreateText("t", new string[] { "t(1)", "t(2)", "t(3)", "t(4)", "t((((24)", "ttt(22)", "t(29)) ))", "t t(8)", "t (9)", "t(1)" });
        Assert.AreEqual("t(30)", result);
    }

    private static String CreateText(string textIdentifier, IEnumerable<string> texts)
    {
        const char startDeliminator = '(';
        const char endDeliminator = ')';

        int nextCount = 0;

        foreach (String text in texts.Select(p => p.Trim()).Where(p => p.StartsWith(textIdentifier)))
        {
            short addCount = 0;

            var startDelminatorIndex = text.LastIndexOf(startDeliminator);

            // there must be at least one start deliminator
            if (startDelminatorIndex >= 0)
            {
                var endDeliminatorIndex = text.IndexOf(endDeliminator, startDelminatorIndex);
                var textName = text.Substring(0, startDelminatorIndex).Trim();

                if (textName.Equals(textIdentifier, StringComparison.CurrentCultureIgnoreCase))
                {
                    if (endDeliminatorIndex > startDelminatorIndex)
                    {
                        startDelminatorIndex++; // get past the (
                        var size = endDeliminatorIndex - startDelminatorIndex;
                        Int16.TryParse(text.Substring(startDelminatorIndex, size).Trim(), out addCount);
                    }
                }
            }

            nextCount = Math.Max(nextCount, addCount + 1);
        }

        return nextCount == 0 ? textIdentifier : string.Format("{0}({1})", textIdentifier, nextCount);
    }
share|improve this answer
    
good idea to use the linq before the loop, although I think that horrible "p.Trim()" –  J. Lennon Nov 14 '12 at 0:48
    
Yeah, unless you don't need to worry about whitespace i.e. assume it's already trimmed you could remove it. However without Test7 and Test6 don't pass. –  dreza Nov 14 '12 at 1:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.