2016-11-28 48 views
2

我有一个字符串替换连续nonalphanum字,需要进行格式化:清理字符串:单分隔符

  • 保持字母数字字母
  • 替换一个或多个非aplhanum字符为一个分隔

我想出了这一点:

string Format(string str , string separator) 
{ 
    if(string.IsNullOrEmpty(str)) 
     return string.Empty; 

    var words = new List<string>(); 
    var sb = new StringBuilder(); 

    foreach(var c in str.ToCharArray()) 
    { 
     if(char.IsLetterOrDigit(c)) 
     { 
      sb.Append(c); 
     } 
     else if(sb.Length > 0) 
     { 
      words.Add(sb.ToString()); 
      sb.Clear(); 
     } 
    } 

    if(sb.Any()) 
     words.Add(sb.ToString()); 

    return string.Join(seperator , words); 
} 

是否有更好的/更多linq样/更短/更高性能的解决方案(没有使用正则表达式)比这个?

+1

**?也更适合http://codereview.stackexchange.com/ – Sehnsucht

+0

如果你保持跟踪,如果前一个字符是非字母数字的,你可以消除对列表和连接的需要,所以你知道何时将分隔符附加到'StringBuilder '。 'StringBuilder'没有我知道的'Any'方法,所以'sb.Count> 0'可能就是你想要的。 – juharr

+2

此代码无法编译,string.Join expect字符串,没有方法任何()字符串生成器。 – mybirthname

回答

2

你可以去到“低级别”,并使用的事实,一个字符串是一个IEnumerable<char>使用它的GetEnumerator

string Format(string str, string separator) 
{ 
    var builder = new StringBuilder (str.Length); 

    using (var e = str.GetEnumerator()) 
    { 
     while (e.MoveNext()) 
     { 
      bool hasMoved = true; 

      if (!char.IsLetterOrDigit (e.Current)) 
      { 
       while ((hasMoved = e.MoveNext()) && !char.IsLetterOrDigit (e.Current)) 
        ; 
       builder.Append (separator); 
      } 

      if (hasMoved) 
       builder.Append (e.Current); 
     } 
    } 

    return builder.ToString(); 
} 

以防万一这里是一个正则表达式版本太多

private static readonly Regex rgx = new Regex(@"[^\w-[_]]+", RegexOptions.Compiled); 

string Format (string str, string separator) 
{ 
    return rgx.Replace (str, separator); 
} 

关于对LINQ的一行OP的评论附录:
这是可能的,但几乎没有“容易理解”

使用匿名类型

string Format (string str, string separator) 
{ 
    return str.Aggregate (new { builder = new StringBuilder (str.Length), prevDiscarded = false }, (state, ch) => char.IsLetterOrDigit (ch) ? new { builder = (state.prevDiscarded ? state.builder.Append (separator) : state.builder).Append (ch), prevDiscarded = false } : new { state.builder, prevDiscarded = true }, state => (state.prevDiscarded ? state.builder.Append (separator) : state.builder).ToString()); 
} 

同样的事情用一个元组,而不是

string Format (string str, string separator) 
{ 
    return str.Aggregate (Tuple.Create (new StringBuilder (str.Length), false), (state, ch) => char.IsLetterOrDigit (ch) ? Tuple.Create ((state.Item2 ? state.Item1.Append (separator) : state.Item1).Append (ch), false) : Tuple.Create (state.Item1, true), state => (state.Item2 ? state.Item1.Append (separator) : state.Item1).ToString()); 
} 

和Tuple我们可以做一些助手来“缓和”(这么说)可读性[尽管它在技术上不是一个衬垫了]

//top of file 
using State = System.Tuple<System.Text.StringBuilder, bool>; 

string Format (string str, string separator) 
{ 
    var initialState = Tuple.Create (new StringBuilder (str.Length), false); 

    Func<State, StringBuilder> addSeparatorIfPrevDiscarded = state => state.Item2 ? state.Item1.Append (separator) : state.Item1; 
    Func<State, char, State> aggregator = (state, ch) => char.IsLetterOrDigit (ch) ? Tuple.Create (addSeparatorIfPrevDiscarded (state).Append (ch), false) : Tuple.Create (state.Item1, true); 
    Func<State, string> resultSelector = state => addSeparatorIfPrevDiscarded (state).ToString(); 

    return str.Aggregate (initialState, aggregator, resultSelector); 
} 

是什么使得它复杂的是,(IMO)的LINQ *是不是真的适合当“项目输出“取决于同一集合中的前一个(或下一个)项目
* Linq并没有问题,但它很快就会产生大量的Func和匿名类型/元组语法(可能是C#7)。0将改变一个位)

在相同的味道,人们还可以接受的副作用,允许只具有布尔作为国家

string Format (string str, string separator) 
{ 
    var builder = new StringBuilder (str.Length); 

    Action<bool> addSeparatorIfPrevDiscarded = prevDiscarded => { if (prevDiscarded) builder.Append (separator); }; 
    Func<bool, char, bool> aggregator = (prevDiscarded, ch) => { 
     if (char.IsLetterOrDigit (ch)) { 
      addSeparatorIfPrevDiscarded (prevDiscarded); 
      builder.Append (ch); 
      return false; 
     } 

     return true; 
    }; 

    addSeparatorIfPrevDiscarded (str.Aggregate (false, aggregator)); 

    return builder.ToString(); 
} 
+0

稍微关闭:如果'str'以非字母数字结尾,这总是以分隔符结束。 –

+0

@PeterB并不完全确定这一点的预期行为,“指令”和示例代码相互矛盾(并且没有任何“修剪”无法解决的问题) – Sehnsucht

+0

这跳过了每个“单词”中的第一个字符“在第一个之后。例如,Format(“abc ... 123 ... XYZ”,' - ')'返回'abc-23-YZ'。 – Sphinxxx

1

像这样的东西,以避免需要一个List<string>和使用string.Join。它也会编译。

string Format(string str, char seperator) 
{ 
    if (string.IsNullOrEmpty(str)) 
     return string.Empty; 

    var sb = new StringBuilder(); 
    bool previousWasNonAlphaNum = false; 

    foreach (var c in str) 
    { 
     if (char.IsLetterOrDigit(c)) 
     { 
      if (previousWasNonAlphaNum && sb.Count > 0) 
       sb.Append(seperator); 
      sb.Append(c); 
     } 

     previousWasNonAlphaNum = !char.IsLetterOrDigit(c); 
    } 

    return sb.ToString(); 
} 
+0

字符串已经是'IEnumerable '不需要'ToCharArray' – Sehnsucht

+0

@Sehnsucht是的,只需从OP的代码中复制并粘贴即可。现在修复。 – juharr

0

试试这个,它会工作

string Format(string str, string separator) 
    { 
     var delimiter = char.Parse(separator); 
     var replaced = false; 
     var cArray = str.Select(c => 
     {     
      if (!char.IsLetterOrDigit(c) & !replaced) 
      { 
       replaced = true; 
       return delimiter; 
      } 
      else if (char.IsLetterOrDigit(c)) 
      { 
       replaced = false;      
      } 
      else 
      { 
       return ' '; 
      } 
      return c; 

     }).ToArray(); 

     return new string(cArray).Replace(" ",""); 
    } 

,或者你可以尝试一下为什么,**无正则表达式下面这一个

string Format(string str, string separator) 
    { 
     var delimiter = char.Parse(separator); 
     var cArray = str.Select(c => !char.IsLetterOrDigit(c) ? delimiter : c).ToArray(); 
     var wlist = new string(cArray).Split(new string[]{separator}, StringSplitOptions.RemoveEmptyEntries); 
     return string.Join(separator, wlist); 
    } 
+0

这仅将nonalphanum字符转换为分隔符,因此可以使用多个分隔符。例如'abc _?#def'将变成'abc --- def'(目标将是'abc-def')。 – user5997884

+0

你可以在'Split'中使用'StringSplitOptions.RemoveEmptyEntries'而不是'Where'。由于'string.Join'具有'IEnumerable '的超载,因此也不需要'ToList'。 – juharr

+0

@juharr感谢信息,我已经更新了我的答案。 – MemoryLeak