4
\$\begingroup\$

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;
}
\$\endgroup\$
2
  • \$\begingroup\$ 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. \$\endgroup\$
    – avip
    Nov 14, 2012 at 7:56
  • \$\begingroup\$ @avip sorry, I will clarify the operation \$\endgroup\$
    – J. Lennon
    Nov 14, 2012 at 10:01

3 Answers 3

4
\$\begingroup\$

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 ;)

\$\endgroup\$
0
3
\$\begingroup\$

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);
}
\$\endgroup\$
2
  • \$\begingroup\$ Yep, you are right. I've missed it. \$\endgroup\$
    – Danil
    Nov 14, 2012 at 14:58
  • \$\begingroup\$ worked well in tests. but looking now, this code does not meet one of the requirements. it still lets texts exactly alike. \$\endgroup\$
    – J. Lennon
    Nov 26, 2012 at 16:21
2
\$\begingroup\$

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);
    }
\$\endgroup\$
2
  • \$\begingroup\$ good idea to use the linq before the loop, although I think that horrible "p.Trim()" \$\endgroup\$
    – J. Lennon
    Nov 14, 2012 at 0:48
  • \$\begingroup\$ 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. \$\endgroup\$
    – dreza
    Nov 14, 2012 at 1:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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