5

Here's a simple problem. I have an application that takes a phone number like "13335557777", and needs to reverse it and insert a dot between each number, like this:

"7.7.7.7.5.5.5.3.3.3.1."

I know I can do this with a StringBuilder and a for-loop to reverse the string and insert the dots, but is there a clever way to do this in LINQ (or some other way)?

Note: for this, I'm not really concerned with performance or memory allocation or whatever, just curious to see how this would be done in LINQ.

2
  • Did you really want a dot after the last 1? If so, it slightly changes the problem. Commented Mar 24, 2009 at 22:58
  • Yeah, that wasn't clear. It doesn't really matter, I'll take solutions that do it either way. Commented Mar 24, 2009 at 23:01

7 Answers 7

8

Try this

var source = GetTheString();
var reversed = source.Reverse().Select(x => x.ToString()).Aggregate((x,y) => x + "." + y);

EDIT

This solution is definitely aimed at the "clever" end. It's likely much more performant to use a StringBuilder to build up the string. This solution creates many intermediate strings.

EDIT2

There was some debate about the relative speed of the "clever" solution vs. the StringBuilder approach. I wrote up a quick benchmark to measure the approach. As expected, StringBuilder is faster.

  • Normal Aggregate (100 elements): 00:00:00.0418640
  • WithStringBuilder (100 elements): 00:00:00.0040099
  • Normal Aggregate (1000 elements): 00:00:00.3062040
  • WithStringBuilder (1000 elements): 00:00:00.0405955
  • Normal Aggregate (10000 elements): 00:00:03.0270392
  • WithStringBuilder (10000 elements): 00:00:00.4149977

However, whether or not the speed difference is signficant is highly dependent upon where it is actually used in your application.

Code for the benchmark.

public static class AggregateUnchanged {
    public static string Run(string input) {
        return input
            .Reverse()
            .Select(x => x.ToString())
            .Aggregate((x, y) => x + "." + y);
    }
}

public static class WithStringBuilder {
    public static string Run(string input) {
        var builder = new StringBuilder();
        foreach (var cur in input.Reverse()) {
            builder.Append(cur);
            builder.Append('.');
        }

        if (builder.Length > 0) {
            builder.Length = builder.Length - 1;
        }

        return builder.ToString();
    }
}

class Program {
    public static void RunAndPrint(string name, List<string> inputs, Func<string, string> worker) {

        // Test case. JIT the code and verify it actually works 
        var test = worker("123456");
        if (test != "6.5.4.3.2.1") {
            throw new InvalidOperationException("Bad algorithm");
        }

        var watch = new Stopwatch();
        watch.Start();
        foreach (var cur in inputs) {
            var result = worker(cur);
        }
        watch.Stop();
        Console.WriteLine("{0} ({2} elements): {1}", name, watch.Elapsed, inputs.Count);
    }

    public static string NextInput(Random r) {
        var len = r.Next(1, 1000);
        var builder = new StringBuilder();
        for (int i = 0; i < len; i++) {
            builder.Append(r.Next(0, 9));
        }
        return builder.ToString();
    }

    public static void RunAll(List<string> input) {
        RunAndPrint("Normal Aggregate", input, AggregateUnchanged.Run);
        RunAndPrint("WithStringBuilder", input, WithStringBuilder.Run);
    }

    static void Main(string[] args) {
        var random = new Random((int)DateTime.Now.Ticks);
        RunAll(Enumerable.Range(0, 100).Select(_ => NextInput(random)).ToList());
        RunAll(Enumerable.Range(0, 1000).Select(_ => NextInput(random)).ToList());
        RunAll(Enumerable.Range(0, 10000).Select(_ => NextInput(random)).ToList());
    }
}
Sign up to request clarification or add additional context in comments.

13 Comments

For info, while "clever" and "FP", I'm not sure it is good advice... the number of intermediate strings mean that StringBuilder would be more pragmatic.
@Marc, agreed, they asked for clever though so I felt obligated :)
Yeah, no problem, I did ask for clever at the expense of anything else.
I seriously doubt that this will create a performance issue, creating short strings isn't really that expensive. That said, I would suggest using Join instead.
Here's a question: when I have a string in Visual Studio, the "Reverse()" method doesn't show up in intellisense, but it pops up if you start to type it, and it compiles. Why doesn't it show up in intellisense? Is it one of those explicit interface things?
|
2

The benefit of this one is that String.Join is going to be cheaper than ".Aggregate((x,y) => x + "." + y)".

var target = string.Join(".", source.Reverse().Select(c => c.ToString()).ToArray());

Comments

1
        string x = "123456";
        StringBuilder y = new StringBuilder(x.Length * 2);

        for (int i = x.Length - 1; i >= 0; i--)
        {
            y.Append(x[i]);
            y.Append(".");
        }

3 Comments

"I know I can do this with a StringBuilder and a for-loop to reverse the string and insert the dots, but is there a clever way to do this in LINQ (or some other way)?"
I was just giving him the code so he didn't have to write it, jerk
FWIW, you don't need to ".ToString()" One of the Append() overloads takes a char.
0
string aString = "13335557777";
string reversed = (from c in aString.Reverse()
                 select c + ".").Aggregate((a, b) => a + b);

Comments

0

(removed an answer using a char list)

As per the comment on the other post, my view is that while the LINQ etc may be "clever", it isn't necessarily efficient. It will make a lot of intermediate strings that need to be collected, for example.

I'd stick to StringBuilder etc, unless you have good reason to change it.

2 Comments

Why use a StringBuilder when there is String.Join for specifically this purpose?
@Grauenwolf - because string.Join works on a string[], not a char[], so you'd need to first create a load of strings. With StringBuilder you don't need to do that.
0

Is this really a linq problem? Just work backwards in a loop per character:

string s = "";
string content = "35557777";
for (int i = content.Length -1; i > 0; i--)
{
    s += content[i] + ".";
}
Console.WriteLine(s);
Console.ReadLine();

If it's a string longer than 4k, use a StringBuilder. Using LINQ for "7.7.7.7.5.5.5.3.3.3.1." is not what LINQ is for, mark me to -99 if you want, and Marc too.

1 Comment

Thanks for the answer. I know doing string manip like this is not really a LINQ problem, but this was more of an "algorithm" type question. Maybe it would have been better if I used a list of objects rather than a string.
-1

As long as you're going through an array already, it'd be easier to use string.Join:

string[] source = GetTheStringAsArray();
string reversed = string.Join(".", source.Reverse());

3 Comments

You skipped the first step, breaking the string into an array or list.
string content = "35557777"; is an array :)
Tell me you didn't down vote my answer and then submit your own using essentially the same function...

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.