views:

111

answers:

5

Hi,

Is it possible to write a lambda expression that will iterate through array of objects and replace all occurences of 'X', 'Y', ' ' and 'Z' in one of the properties?

E.g.

return query.Select(x => { x.SomePropertyName= x.SomePropertyName.Trim().Replace(' ', "_"); return x; }).ToList();

For some reason, a query above doesn't replace a single character, when I need to replace multiple characters.

Thank you

+3  A: 

If you want to use LINQ, you can use Select, ToArray and the String(char[]) constructor like this:

var result = query.ToList();
foreach (var x in result)
{
    x.SomeProperty =
        new string(x.SomeProperty
                    .Select(c => (c == 'X' || ... || c == ' ') ? '_' : c)
                    .ToArray());
}

Note that LINQ is not intended to be used to cause side-effects, but to create new enumerables from existing enumerables. So a foreach loop is better here.


But why not simply a chain of Replace calls?

var result = query.ToList();
foreach (var x in result)
{
    x.SomeProperty = x.SomeProperty
                      .Replace('X', '_')
                      .Replace('Y', '_')
                      .Replace('Z', '_')
                      .Replace(' ', '_');
}

Or are you trying to replace a single character with a sequence of characters? Then use the String.Replace overload that takes two strings:

var result = query.ToList();
foreach (var x in result)
{
    x.SomeProperty = x.SomeProperty.Replace(" ", "ABC");
}
dtb
Thanks for this!
vikp
+2  A: 

When I want to replace one of a number of characters with one single other character, I often use a combination of string.Split and string.Join:

char[] unwanted = new[] {'X', 'Y', 'Z'};
query = query.Select(x =>
{
    x.SomePropertyName = string.Join("_", x.SomePropertyName.Split(unwanted));
    return x;
});

This will replace any occurrence of 'X', 'Y' or 'Z' with '_'. You can combine this with the looping construct of your choice.

As discussed in the comments, using Select does not really add any value in this case. A normal foreach loop would do the job, and would even produce more compact code:

char[] unwanted = new[] {'X', 'Y', 'Z'};
foreach(var x in query)
{
    x.SomePropertyName = string.Join("_", x.SomePropertyName.Split(unwanted));
};
Fredrik Mörk
This is exactly what I'm after. Thank you
vikp
It's a bit of an abuse of the `Select` method though!
LukeH
Does this work if the first or final character is 'X', 'Y', or 'Z'? Eg. Does 'XBC' return '_BC' and 'ABX' return 'AB_'? Or does 'XYZ' return '___'?
Robert Gowland
@LukeH: I absolutely agree :) I would personally probably just use a `foreach` loop.
Fredrik Mörk
@Robert: yes, it works also when the unwanted characters appears first or last in the string.
Fredrik Mörk
It's interesting that you say it's an "abuse of the Select method". I thought that LINQ solution would be more elegant, but that was just my assumption. What is the reasoning behind your statement?
vikp
@vikp: the way that the code is currently constructed, it will use a delegate to an anonymous method passed to an extension method in order to essentially just loop over the sequence and perform a simple operation on each object. LINQ doesn't really add anything than some extra complexity in this particular situation. A `foreach` loop would even produce more compact code, since you would not need to `return x` for each iteration. See updated answer.
Fredrik Mörk
I see what you mean, I think that I got carried away with LINQ and I overcomplicate things when it's not needed. Thank you!
vikp
+1  A: 

If you're in the mood to use a regular expressions, you can do something like this:

query.Select(
    x => { 
        x.SomePropertyName = Regex.Replace(x.SomePropertyName, @"[XYZ\s]", "_"); 
        return x; })
Pat Daburu
A: 

loop through each character in the string and replace it if necessary.

 var xyz = new HashSet<char>(new[] { 'X', 'Y', 'Z' });
 char replacement = '_';
 var results = query
  .Select(x =>
     {
      var value = x.SomePropertyName
     .Trim()
     .Select(y => xyz.Contains(y) ? replacement : y)
     .ToArray();
      x.SomePropertyName = new string(value);
      return x;
     })
  .ToList();
Handcraftsman
+1  A: 

Although you requested LINQ, this (ab)use of LINQ relies on side-effects and clutters the code. An alternative to use with an array of objects (which you stated is the case in your question) is to use the Array.ForEach method:

// using Fredrik Mörk's approach
Array.ForEach(query, x =>
    x.SomePropertyName = string.Join("_", x.SomePropertyName.Split(unwanted)));

// using Regex
Array.ForEach(query, x => x.SomePropertyName =
    Regex.Replace(x.SomePropertyName, "[XYZ]", "_", RegexOptions.IgnoreCase));

Of course use of regex requires some knowledge. Be mindful of using metacharacters that hold a special meaning in regex. You can sanitize your characters by using the Regex.Escape method.

Ahmad Mageed
Man... this is even less legible than the LINQ... (I mean the regex approach)
code4life