2016-11-28 7 views
2

を連続しnonalphanum文字を交換し、フォーマットすることの必要性:クリーンアップ文字列:私は、文字列を持っている、単一の区切り文字で

  • は、単一のセパレータ
  • を有する1つまたは複数の非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); 
} 

は良くあり/ more-linq-like/short/more performant solution(なし、正規表現を使用しています)?

+1

以下、このいずれかを試すことができます**?また、http://codereview.stackexchange.com/ – Sehnsucht

+0

に適しています。前の文字が英数字ではなかったかどうかを追跡している場合は、リストと結合の必要性を排除することができますので、区切り文字を 'StringBuilder '。また、 'StringBuilder'は私が知っている' Any'メソッドを持っていないので、 'sb.Count> 0'はあなたが望むものです。 – juharr

+2

このコードはコンパイルできません。string.Joinは文字列を想定しています。文字列ビルダーのAny()メソッドはありません。 – 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()); } 

タプルでは、それは複雑になり何を「緩和」するためにいくつかのヘルパー(いわば)可読性[それはもはや技術的にワンライナーではありませんが]

//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#0は同じ味でビット)

は、1にも

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

これは、非アルファベットの文字をセパレータに変換するだけなので、複数のセパレータが可能です。 'abc _?#def'は例えば' abc --- def'になります(目標は 'abc-def'になります)。 – user5997884

+0

後で 'Where'の代わりに' Split'で 'StringSplitOptions.RemoveEmptyEntries'を使うだけです。また、 'ToList'の必要はありません。' string.Join'は 'IEnumerable 'をとるオーバーロードを持っているからです。 – juharr

+0

@juharr情報のおかげで、私は私の答えを更新しました。 – MemoryLeak

関連する問題