views:

214

answers:

7

A quick brain teaser: given a string

This  is a string with  repeating   spaces

What would be the LINQ expressing to end up with

This is a string with repeating spaces

Thanks!

For reference, here's one non-LINQ way:

private static IEnumerable<char> RemoveRepeatingSpaces(IEnumerable<char> text)
{
  bool isSpace = false;
  foreach (var c in text)
  {
    if (isSpace && char.IsWhiteSpace(c)) continue;

    isSpace = char.IsWhiteSpace(c);
    yield return c;
  }
}
+13  A: 

This is not a linq type task, use regex

string output = Regex.Replace(input," +"," ");

Of course you could use linq to apply this to a collection of strings.

Paul Creasey
+1, nice answer.
Darin Dimitrov
Don't you think Regex is way too heavy-handed for this?
Michael Teper
@Michael Teper: Personally, I think the other solutions that bend over backwards just to solve this problem using LINQ extension methods are the heavy-handed ones. You can solve the problem in one extremely short, readable line, or you can use a much less obvious method that requires 2 or more much longer lines. Which makes more sense?
Dan Tao
@Michael Not at all, it's a perfect, if rather simple, regex problem.
Paul Creasey
+1 @Dan Tao: Although I answered the question as asked by the OP, I agree with you completely.
Ani
Dan: I think this is a fundamental misunderstanding of the question. Think of the question as "How can I eliminate consecutive duplicates from a stream?". A Regex solution only works with this one concrete example of the general problem.
Gabe
I agree with Gabe, this is not what I was asking. I know how to solve this problem with Regex (merits aside) and I know how to solve it procedurally. I was interested in how to approach it using LINQ.
Michael Teper
@Gabe: Well, I see where you're coming from; **however**, a very important aspect of the question is "using LINQ," which, even in the context of your general rephrasing, is a very awkward requirement to impose. That is to say, I don't personally believe that a generic LINQ approach is the way to solve this problem, regardless of context. Consider Ani's answer, which absolutely requires that the input provide random access by index in order to be efficient. For strings, Regex makes more sense than LINQ here. In general, I would argue, a procedural approach makes more sense than LINQ here.
Dan Tao
Dan: First of all, Ani's solution just isn't as good as it could be (see mine). Second of all, given that the procedural solution is best, a regex isn't the right solution either!
Gabe
@Gabe: Regex, in this rare case, happens to be the most readable solution for the task the OP originally asked about. You may have pointed out the potential abstraction of the problem into something more general, but I actually don't think it's completely fair to say that the OP was all along concerned merely with this abstract case. (If he were, his comment above about Regex being "heavy-handed" would be a bit off-topic.) More importantly I've found that striving to generalize a solution to its logical extreme quite often ends up being a bad call.
Dan Tao
@Gabe: Case in point: this problem. The problem is: how can I remove excess spaces from a string? Your solution, in striving to generalize this problem to its purest form, resorts to code that is significantly less readable than what Paul has here **for the problem at hand**. Are you going to need that generalization? How many scenarios can you think of where you need to strip out consecutive occurrences of a specific value from a sequence? Not many, I would wager.
Dan Tao
Dan: The original poster actually agreed with me in a comment to *this very post*. He already knows the regex (it's rather trivial), but as an exercise he wanted to know how to do it with LINQ. Furthermore, if you want to know what other scenarios can use a generalized solution, just search SO: http://stackoverflow.com/search?q=consecutive+duplicates shows several
Gabe
@Gabe: I know the OP agreed with you. It just seemed to me like he was *changing* what he was asking primarily to justify demanding a LINQ requirement. I mean, look at the sequence of comments leading up to yours. To me the conversation is like... we're talking about strings, we're talking about strings, we're talking about strings, **bam** now we're talking about general set operations. And suddenly the OP says, "I wasn't asking about strings." I'm probably not being fair. I think ultimately I'm just strangely (unreasonably) bothered by the comment about Regex being "way too heavy-handed."
Dan Tao
Guys, let me try to settle this. True, I was not looking for a general solution, but I *was* looking for a LINQ solution, making Regex completely off-topic. I am frankly surprised it has garnered as many upvotes as it has. I guess that points to the general pragmatic quality of the SO readership.@Dan, my comment that Regex is "heavy-handed" is based on personal experience. I am actually quite well-versed in Regex and can attest not only to its power but also to its overhead, which is non-trivial.
Michael Teper
I should also mention that I really appreciate both your guys comments!
Michael Teper
@Michael: Haha, I am washing my hands of this one at this point. I don't know what it says about me that your question and subsequent clarifications have bothered me so much. Perhaps I need to reevaluate my life? Anyway, I've apologized to Gabe for being pugnacious; now I am going to bed. Good night!
Dan Tao
Dan: Just this morning somebody posted a question about removing consecutive duplicates in Python (http://stackoverflow.com/questions/3601934/python-find-sequential-change-in-one-member-of-list-pairs-report-other). Note that the answer used `zip` in much the same way as mine. I'd say that this problem is far more common than you imagine.
Gabe
While the OP may have a legitimate reason for wanting a LINQ solution, and may already know REGEX well. It is important to remember that the answerers have know way of knowing that, in many other situations the OP may have though that LINQ was the best solution to the problem and therefore "offtopic" posts like this are a very good thing, a good answer is useless is the question is wrong, and it often is on SO, so in the most part deviating off topic is a good thing. Its not something to get into an argument about though guys!
Paul Creasey
+6  A: 
public static string TrimInternal(this string text)
{
  var trimmed = text.Where((c, index) => !char.IsWhiteSpace(c) || (index != 0 && !char.IsWhiteSpace(text[index - 1])));
  return new string(trimmed.ToArray());
}
Ani
Cool, I didn't realize there was a Where overload that supplied an index. Good to know!
Michael Teper
This implementation will strip all beginning whitespace but will leave a single trailing whitespace. Simplifying the condition in the Where: index == 0 || !char.IsWhiteSpace(text[index-1]) should preserve a single leading whitespace. +1
devgeezer
+1  A: 

I'm abusing linq here but to answer your question, here you go

    var output = new StringBuilder();
    (from c in text
    where !Char.IsWhiteSpace(c) || output.Length > 0 && output[output.Length - 1] != c
    let x = output.Append(c)
    select c).ToList();
    Console.WriteLine(output);
Hasan Khan
A: 

Paul Creasey's answer is the way to go.

If you want to treat tabs as whitespace as well, go with:

text = Regex.Replace(text, "[ |\t]+", " ");

UPDATE:

The most logical way to solve this problem while satisfying the "using LINQ" requirement has been suggested by both Hasan and Ani. However, notice that these solutions involve accessing a character in a string by index.

The spirit of the LINQ approach is that it can be applied to any enumerable sequence. Because any reasonably efficient solution to this problem requires maintaining some kind of state (with Ani's and Hasan's solutions it's easy to miss this fact as the state is already maintained within the string itself), a generic approach that accepts any sequence of items is likely going to be much more straightforward to implement using procedural code.

This procedural code may then be abstracted into a method that looks like a LINQ-style method, of course. But I would not recommend tackling a problem like this with the attitude of "I want to use LINQ in this solution" from the get-go because it will impose very awkward restriction on your code.

For what it's worth, here's how I'd implement the general idea.

public static IEnumerable<T> StripConsecutives<T>(this IEnumerable<T> source, T value, IEqualityComparer<T> comparer)
{
    // null-checking omitted for brevity

    using (var enumerator = source.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            yield return enumerator.Current;
        }
        else
        {
            yield break;
        }

        T prev = enumerator.Current;
        while (enumerator.MoveNext())
        {
            T current = enumerator.Current;
            if (comparer.Equals(prev, value) && comparer.Equals(current, value))
            {
                // This is a consecutive occurrence of value --
                // moving on...
            }
            else
            {
                yield return current;
            }
            prev = current;
        }
    }
}
Dan Tao
You don't actually have to maintain state. You can do it functionally by letting the iterator maintain the state for you with the `Zip` function (see http://stackoverflow.com/questions/3595583/can-i-use-linq-to-strip-repeating-spaces-from-a-string/3603005#3603005). Obviously if I were implementing this in Linq-to-Objects, your implementation is the way to go. If you needed it to run on a SQL Server, for example, your method wouldn't work.
Gabe
@Gabe: My point was that this solution requires for *someone* (or *something*) to maintain state; notice I pointed to Ani's answer as an example where this is easy to miss because the state is maintained within the string itself. To simply observe that you can code a similar solution using `Zip` does not invalidate that point; it merely moves the maintenance of state somewhere else. Again, I wanted to stress to the OP that the requirement "I want to do this with LINQ" creates a conflict between the specified requirements and what a more logical approach would be.
Dan Tao
Dan: The OP explicitly stated that this is a "brain teaser", clearly indicating that he didn't want a "more logical" approach. Then he said "this is an operation on a set, and I am interested in seeing a set-based approach" (meaning 'stream' where he said 'set'), emphasizing that he wanted a more general approach.
Gabe
@Gabe: OK, fair point about the comment (I'm having trouble keeping track of in what order the OP said what). But how do you get from the term "brain teaser" to the conclusion that the OP "clearly" was not interested in a "logical" approach? Are logical answers to brain teasers unacceptable? I kind of thought they were the best ones...
Dan Tao
Dan: When somebody says "Given constraint X, solve problem Y", you can assume that they expect a logical answer to Y such that X holds. I think it's safe to say that usually an answer like "Just eliminate constraint X" is not what they're looking for, or they wouldn't have mentioned X in the first place. Do you go to code-golf questions and criticize the answers for being too hard to understand and unmaintainable?
Gabe
@Gabe: OK, yes, you're right. But surely you will agree with me that often questions are posted on SO where the OP says, "I want to use X to solve Y" when really, he/she just wants to solve Y, and has a *hunch* that X might be the way to do it (I'd say the two most typical values of X are LINQ and Regex). To me, this did not originally look like a code golf question. It looked like a practical question with an erroneous assumption embedded in it. If the OP had asked, "How can I use Regex to parse this HTML document?" the **correct** answer, to me, would've been: "Use this library instead."
Dan Tao
@Gabe: You know what I think it is that's bothering me? I think it's that it's just **too close** to a *regular* question for me to feel comfortable with the whole "let's treat it like a code golf question" perspective. Like, I can easily imagine that other users may stumble upon this question, see a LINQ answer, and think, "Oh, cool! That's how I will tackle this problem from now on!" Because it's not *obviously* the wrong approach; but I still think it's not the best approach. So it's just that proximity, the fine line between "fun puzzle" and "practical problem" that's irking me, I guess.
Dan Tao
@Gabe: Anyway, I apologize for being rather argumentative in the course of this fragmented conversation. I may have issues with the question, but that's totally just me. Obviously any developer stands to gain quite a bit by understanding the concepts that are discussed here.
Dan Tao
+2  A: 

In practice, I would probably just use your original solution or regular expressions (if you want a quick & simple solution). A geeky approach that uses lambda functions would be to define a fixed point operator:

T FixPoint<T>(T initial, Func<T, T> f) {
   T current = initial;
   do { 
     initial = current;
     current = f(initial);
   } while (initial != current);
   return current;
}

This keeps calling the operation f repeatedly until the operation returns the same value that it got as an argument. You can think of the operation as a generalized loop - it is quite useful, though I guess it is too geeky to be included in .NET BCL. Then you can write:

string res = FixPoint(original, s => s.Replace("  ", " "));

It is not as efficient as your original version, but unless there are too many spaces it should work fine.

Tomas Petricek
_@Tomas:_ I really like the way how you apply [strange complicated theory](http://en.wikipedia.org/wiki/Fixed_point_combinator) to programming problems and come up with elegant C# -) solution!
stakx
@stakx: Thanks :-)! I agree that this is way over the top solution, but I just couldn't resist...
Tomas Petricek
A: 

Linq is by definition related to enumerable (i.e. collections, list, arrays). You could transorm your string into a collection of char and select the non space one but this is definitevly not a job for Linq.

VdesmedT
A string **is** a collection of chars
Jamiec
@Jamiec: semantically it is, but is not implement as an IEnumerable<char> in the framework. Sorry for this lack of precision.
VdesmedT
+2  A: 

Since nobody seems to have given a satisfactory answer, I came up with one. Here's a string-based solution (.Net 4):

public static string RemoveRepeatedSpaces(this string s)
{
    return s[0] + string.Join("",
           s.Zip(
               s.Skip(1),
               (x, y) => x == y && y == ' ' ? (char?)null : y));
}

However, this is just a general case of removing repeated elements from a sequence, so here's the generalized version:

public static IEnumerable<T> RemoveRepeatedElements<T>(
                             this IEnumerable<T> s, T dup)
{
    return s.Take(1).Concat(
            s.Zip(
                s.Skip(1),
                (x, y) => x.Equals(y) && y.Equals(dup) ? (object)null : y)
            .OfType<T>());
}

Of course, that's really just a more specific version of a function that removes all consecutive duplicates from its input stream:

public static IEnumerable<T> RemoveRepeatedElements<T>(this IEnumerable<T> s)
{
    return s.Take(1).Concat(
            s.Zip(
                s.Skip(1),
                (x, y) => x.Equals(y) ? (object)null : y)
            .OfType<T>());
}

And obviously you can implement the first function in terms of the second:

public static string RemoveRepeatedSpaces(this string s)
{
    return string.Join("", s.RemoveRepeatedElements(' '));
}

BTW, I benchmarked my last function against the regex version (Regex.Replace(s, " +", " ")) and they were were within nanoseconds of each other, so the extra LINQ overhead is negligible compared to the extra regex overhead. When I generalized it to remove all consecutive duplicate characters, the equivalent regex (Regex.Replace(s, "(.)\\1+", "$1")) was 3.5 times slower than my LINQ version (string.Join("", s.RemoveRepeatedElements())).

I also tried the "ideal" procedural solution:

public static string RemoveRepeatedSpaces(string s)
{
    StringBuilder sb = new StringBuilder(s.Length);
    char lastChar = '\0';
    foreach (char c in s)
        if (c != ' ' || lastChar != ' ')
            sb.Append(lastChar = c);
    return sb.ToString();
}

This is more than 5 times faster than a regex!

Gabe
@Gabe: It seems to me that your LINQ wizardry has a weakness. This approach requires creating three separate enumerators for `s`. This assumes that these enumerators will all traverse `s` in the same order. An approach that uses a single enumerator assumes less about the data (though obviously I'm not saying it's perfect).
Dan Tao
Dan: I would hope that `s` is always traversed in the same order -- otherwise the concept of "consecutive duplicates" is somewhat meaningless. Of course if that were an issue (say, `s` is a list of lazily-generated random numbers), that's what `EnumerableEx.Memoize` (http://community.bartdesmet.net/blogs/bart/archive/2010/01/07/more-linq-with-system-interactive-functional-fun-and-taming-side-effects.aspx) is for.
Gabe
@Gabe: Suppose you have a stream of random numbers and you want to skip consecutive occurrences of the same value while processing them. That seems like a plausible scenario to me. I suppose you could use `Memoize` in that case. Really, it's just that I'm skeptical of the idea of using three enumerators to do what you can accomplish with one. The cost of having them seems highly uncertain.
Dan Tao
Dan: If you look at the edit at the end of my post, you can see that the cost of the "extra" enumerators is negligible (because they're lazy). If the enumerators each made a copy of the stream, that would be an unacceptable overhead, but since each enumerator is just an object with a pointer to the current position in the string, there's very little overhead.
Gabe
@Gabe: But that completely depends on the `IEnumerable<T>` implementation! You probably tested it on a `List<T>` or a `T[]` or something to that effect. In that case sure the overhead would be negligible. I'm just saying, for a *general* solution such as this one, you can't assume that creating an enumerator is always going to be cheap.
Dan Tao
@Dan, good point, but since my question was, after all, about a stable stream (i.e. a string), I am going to accept this answer. Thank you both very much!
Michael Teper
@Gabe: Even though I said I was finished, I just figured I'd make one last point since it concretizes what I was saying about the cost of creating multiple enumerators. Take a look at most of the collections in the `System.Collections.Concurrent` namespace in .NET 4.0. Calling `GetEnumerator` on these types creates a snapshot of the collection and returns an object that enumerates over the snapshot. So here is a concrete example where creating three enumerators would result in the construction of three distinct copies of an entire collection.
Dan Tao
+1 for creative thinking
Hasan Khan