23


10

私はこの課題をstackoverflowコミュニティの注目を集めることを望みました。 元々の問題とその答えは ここです。 ところで、あなたが前にそれに従わなかったならば、あなたはエリックのブログを読もうとするべきです、それは純粋な知恵です。

概要:

null以外のIEnumerableを取り、次の特性を持つ文字列を返す関数を作成します。

  1. シーケンスが空の場合、結果の文字列は "\ {}"です。

  2. シーケンスが単一の項目 "ABC"の場合、結果の文字列は "\ {ABC}"です。

  3. シーケンスが2項目シーケンス "ABC"、 "DEF"の場合、結果の文字列は "\ {ABC and DEF}"です。

  4. "ABC"、 "DEF"、 "G"、 "H"など、シーケンスに3つ以上の項目がある場合、結果の文字列は "\ {ABC、DEF、G and H}"になります。 (注:オックスフォードのカンマはありません!)

ご存知のように、私たち自身のJon Skeet(はい、https://stackoverflow.com/questions/305223/jon-skeet-facts[heは同時に2つの場所に存在する可能性があることがよく知られています。解決策が彼の(私見)はおそらくあなたがそのパフォーマンスを破ることはできませんが最もエレガントではありません。

どう思いますか? そこにはかなり良い選択肢があります。 私は本当に(Fernando Nicoletからの)選択および集約方法を含む解決策の1つが好きです。 Linqは非常に強力で、このような課題に時間をかけて多くのことを学びます。 私はそれを少しねじったので、(Countを使ってReverseを避けることによって)もう少しパフォーマンスがよくそして明確になります:

パブリック静的文字列CommaQuibbling(IEnumerable items){int last = items.Count() -  1;} Func getSeparator =(i)=> i == 0? string.Empty:(i ==最後? "and": "、"); string answer = string.Empty;

"{" items.Select((s、i)=> new {Index = i、Value = s}).Aggregate(answer、(s、a)=> s getSeparator(a.Index)a.Value) "を返してください。 ";" }

25 Answer


33


非効率的ですが、私は明らかに思います。

パブリックスタティック文字列CommaQuibbling(IEnumerable items){List list = new List(items); if(list.Count == 0){return "{}"; if(list.Count == 1){return "{" list [0] "}"; }

String [] initial = list.GetRange(0、list.Count  -  1).ToArray(); "{" String.Join( "、"、initial) "と" list [list.Count  -  1] "}"を返します。 }

私がコードを保守していたのなら、もっと賢いバージョンにしたいと思います。


28


このアプローチはどうですか? 純粋に累積的 - バックトラッキングなし、一度だけ繰り返します。 生のパフォーマンスのためには、LINQの答えがどれほど「きれい」であるかに関係なく、LINQなどを使ったほうがうまくいくかどうかわからない

システムを使用する。 System.Collections.Genericを使用します。 System.Textを使用します。

static class Program {public static string CommaQuibbling(IEnumerable items){StringBuilder sb = new StringBuilder( '{'); using(var iter = items.GetEnumerator()){if(iter.MoveNext()){//最初の項目は直接追加できますsb.Append(iter.Current); if(iter.MoveNext()){//複数の;別の文字列があることがわかっている場合にのみ、各//用語を追加してください。lastItem = iter.Current; while(iter.MoveNext()){//中期; "、" sb.Append( "、").Append(lastItem)を使用してください。 lastItem = iter.Current; //最後の用語を追加します。少なくとも//第2項にあるので、常に "and" sb.Append( "and").Append(lastItem);を使用します。 ToString(); sb.Append( '}')を返します。 static void Main(){Console.WriteLine(CommaQuibbling(new string [] {}));} Console.WriteLine(CommaQuibbling(new string [] {"ABC"})); Console.WriteLine(CommaQuibbling(new string [] {"ABC"、 "DEF"})); Console.WriteLine(CommaQuibbling(new string [] {"ABC"、 "DEF"、 "G"、 "H"})); }}


5


最初の/最後の情報を必要とするストリームで私がたくさんやっていたのなら、私はこの拡張を持っているでしょう:

[Flags]
パブリック列挙体StreamPosition {最初= 1、最後= 2}

public static IEnumerable MapWithPositions(このIEnumerableストリーム、Funcマップ){using(var enumerator = stream.GetEnumerator()){if(!enumerator.MoveNext())

var cur = enumerator.Current; var flags = StreamPosition.First; while(true){if(!enumerator.MoveNext())flags | = StreamPosition.Last;リターンマップ(flags、cur)を返します。 if((フラグ

それから最も簡単な(最も速くはない、もっと便利な拡張メソッドが2、3必要になるだろう)解決策は次のようになるだろう。

public static string Quibble(IEnumerable strings){return "{" String.Join( ""、strings.MapWithPositions((pos、item)=>((pos "":pos == StreamPosition.Last? "and": "、")item)) "}"; }


3


ここではPythonのライナーとして

>>> f =ラムダs: "{%s}"% "、" .join(s)[::  -  1] .replace( '、'、 'dna'、1)[::  -  1] >> > f([]) '{}' >>> f(["ABC"]) '{ABC}' >>> f(["ABC"、 "DEF"]) '{ABC and DEF}' >> > f(["ABC"、 "DEF"、 "G"、 "H"]) '' {ABC、DEF、G and H} '

このバージョンは理解しやすいかもしれません

>>> f =λs: "{%s}"% "と" .join(s).replace( 'と'、 '、'、len(s)-2)>>> f([]) ' {} '>>> f(["" ABC "])' {ABC} '>>> f([" "ABC"、 "DEF"])' {ABCとDEF} '>>> f(["" ABC " 、 "DEF"、 "G"、 "H"]) '{ABC、DEF、G and H}'


2


免責事項:私はこれを新しいテクノロジを試すための言い訳として使用しました。そのため、私の解決策は、明快さと保守性に対するEricの当初の要求に実際には対応していません。

素朴な列挙子ソリューション(この+ `foreach +`バリアントは、列挙子を手動で操作する必要がないため、優れていると思います。)

public static string NaiveConcatenate(IEnumerable sequence)
{
    StringBuilder sb = new StringBuilder();
    sb.Append('{');

    IEnumerator enumerator = sequence.GetEnumerator();

    if (enumerator.MoveNext())
    {
        string a = enumerator.Current;
        if (!enumerator.MoveNext())
        {
            sb.Append(a);
        }
        else
        {
            string b = enumerator.Current;
            while (enumerator.MoveNext())
            {
                sb.Append(a);
                sb.Append(", ");
                a = b;
                b = enumerator.Current;
            }
            sb.AppendFormat("{0} and {1}", a, b);
        }
    }

    sb.Append('}');
    return sb.ToString();
}
  • LINQを使った解決策*

public static string ConcatenateWithLinq(IEnumerable sequence)
{
    return (from item in sequence select item)
        .Aggregate(
        new {sb = new StringBuilder("{"), a = (string) null, b = (string) null},
        (s, x) =>
            {
                if (s.a != null)
                {
                    s.sb.Append(s.a);
                    s.sb.Append(", ");
                }
                return new {s.sb, a = s.b, b = x};
            },
        (s) =>
            {
                if (s.b != null)
                    if (s.a != null)
                        s.sb.AppendFormat("{0} and {1}", s.a, s.b);
                    else
                        s.sb.Append(s.b);
                s.sb.Append("}");
                return s.sb.ToString();
            });
}
  • TPLによる解決策*

このソリューションでは、プロデューサーコンシューマーキューを使用して、入力シーケンスをプロセッサに送りますが、少なくとも2つの要素をキューに入れたままにします。 プロデューサが入力シーケンスの最後に達すると、最後の2つの要素は特別な処理で処理されます。

後から考えると、コンシューマを非同期的に動作させる必要はありません。コンカレントキューの必要性は排除されますが、前述したように、私はこれを新しいテクノロジの言い訳として使用しているだけです。

public static string ConcatenateWithTpl(IEnumerable sequence)
{
    var queue = new ConcurrentQueue();
    bool stop = false;

    var consumer = Future.Create(
        () =>
            {
                var sb = new StringBuilder("{");
                while (!stop || queue.Count > 2)
                {
                    string s;
                    if (queue.Count > 2 && queue.TryDequeue(out s))
                        sb.AppendFormat("{0}, ", s);
                }
                return sb;
            });

    // Producer
    foreach (var item in sequence)
        queue.Enqueue(item);

    stop = true;
    StringBuilder result = consumer.Value;

    string a;
    string b;

    if (queue.TryDequeue(out a))
        if (queue.TryDequeue(out b))
            result.AppendFormat("{0} and {1}", a, b);
        else
            result.Append(a);

    result.Append("}");
    return result.ToString();
}

単体テストは簡潔にするために避けました。


2


これは単純なF#の解決策で、前方への反復を1回だけ行います。

CommaQuibble items = let sb = System.Text.StringBuilder( "{")// ppは前の2、pは前のlet let pp、p = items |> Seq.fold(fun(pp:stringオプション、p)s  - > pp <> Noneならばsb.Append(pp.Value).Append( "、")|> ignore(p、Some(s)))(なし、None)もしpp <> Noneならばsb.Append(pp .Value).Append( "and")|> p <> Noneならば無視し、sb.Append(p.Value)|> ignore sb.Append( "}")。ToString()

(編集:これはSkeetのものと非常によく似ています。)

テストコード:

テストl = printfn "%s"とします(CommaQuibble l)。

Test [] Test ["ABC"] Test ["ABC"; "DEF"] Test ["ABC"; "DEF"; "G"] Test ["ABC"; "DEF"; "G"; "H" ]テスト["ABC"; null; "G"; "H"]


2


遅刻:

public static string CommaQuibbling(IEnumerable items){string [] parts = items.ToArray(); StringBuilder result = new StringBuilder( '{'); (int i = 0; i <parts.Length; i){if(i> 0)の結果。 "and": "、"); result.Append(parts [i]); To return();} return result.Append( '}')。ToString(); }


2


私はシリアルカンマのファンです:私は食べて、撃ち、そして去ります。

私は継続的にこの問題に対する解決策を必要としており、そして3つの言語でそれを解決した(しかしC#ではない)。 `+ IEnumerable `で動作する ` concat +`メソッドを記述することで、次のソリューション(http://www.lua.org [Lua]で、中括弧で回答をラップしません)を適応させます。

function commafy(t, andword)
  andword = andword or 'and'
  local n = #t -- number of elements in the numeration
  if n == 1 then
    return t[1]
  elseif n == 2 then
    return concat { t[1], ' ', andword, ' ', t[2] }
  else
    local last = t[n]
    t[n] = andword .. ' ' .. t[n]
    local answer = concat(t, ', ')
    t[n] = last
    return answer
  end
end


2


これは見やすく読むことはできませんが、最大数千万の文字列まで拡張できます。 私は古いPentium 4ワークステーションで開発していて、それは約350ミリ秒で平均長8の1,000,000文字列を処理します。

パブリック静的文字列CreateLippertString(IEnumerable strings){char [] combinedString; char [] commaSeparator = new char [] {'、'、 ''}; char [] andSeparator = new char [] {''、 'A'、 'N'、 'D'、 ''};

int totalLength = 2。 // '{'および '}' int numEntries = 0; int currentEntry = 0。 int currentPosition = 0; int secondToLast;最後のint。 int commaLength = commaSeparator.Length; int andLength = andSeparator.Length; int cbComma = commaLength * sizeof(char); int cbAnd = andLength * sizeof(char);

//各文字列の長さの合計を計算する(文字列中の文字列s){totalLength = s.Length;
        ++numEntries;
}

//(numEntries> = 2)totalLength = 5の場合、//全長に定数文字の長さを追加する// "AND"

if(numEntries> 2)totalLength =(2 *(numEntries  -  2)); //アイテム間の "、"

//後で役立つようにいくつかのメタ変数を設定するsecondToLast = numEntries  -  2; last = numEntries  -  1。

//結合された文字列にメモリを割り当てるcombinedString = new char [totalLength]; //最初の文字を{combinedString [0] = '{'に設定します。 currentPosition = 1;

if(numEntries> 0){//各文字列をそれぞれの場所にコピーする(文字列内の文字列s){Buffer.BlockCopy(s.ToCharArray()、0、combinedString、currentPosition * sizeof(char)、s.Length * sizeof) (char)); currentPosition = s.Length;

if(currentEntry == secondToLast){Buffer.BlockCopy(andSeparator、0、combinedString、currentPosition * sizeof(char)、cbAnd); currentPosition = andLength;そうでなければ(currentEntry == last){combinedString [currentPosition] = '}'; //最後の文字を '}'に設定しますそうでなければ(currentEntry <secondToLast){Buffer.BlockCopy(commaSeparator、0、combinedString、currentPosition * sizeof(char)、cbComma); //列挙子への最後の呼び出しをしなくてもよい。 currentPosition = commaLength; }

            ++currentEntry;
elseString {//最後の文字を '}'に設定します。combinedString [1] = '}'; }

新しい文字列(combinedString)を返します。 }


2


もう1つの変形 - コードを明確にするために句読点と反復ロジックを分離する。 それでも、香りについて考えています。

純粋なIEnumerable / string /で要求どおりに動作し、リスト内の文字列はnullにできません。

public static string Concat(IEnumerable strings){return "{" strings.reduce( ""、(acc、prev、cur、next)=> acc.Append(句読点(prev、cur、next))。Append(cur)) "}";プライベート静的文字列句読点(文字列前、文字列cur、文字列next){if(null == prev || null == cur)return ""; if(null == next)が "and"を返す場合"、"を返します。 }

プライベートスタティック文字列reduce(このIEnumerable文字列、文字列acc、Func func)

var accumulatorBuilder = new StringBuilder(acc);文字列cur = null。文字列prev = null。 foreach(var next in strings){func(accumulatorBuilder、prev、cur、next); prev = cur; cur = next; func(accumulatorBuilder、prev、cur、null);

accumulatorBuilder.ToString()を返します。 }

F#は確かにずっと良く見えます:

rec = list = match listと一致させます。 []  - > "" | head :: curr :: []  - > head "and" curr | head :: curr :: tail  - > head "、" curr :: tail |> reduce | head :: []  - > head

concat list = "{"(list |> reduce) "}"とします。