LINQ列表到句子格式(插入逗号&“和”)

时间:2010-09-29 17:04:28

标签: c# linq string

我有一个简单的linq查询:

var k = people.Select(x=>new{x.ID, x.Name});

然后我想要一个函数或linq lambda,或者用逗号和“ands”输出句子格式的名字。

{1, John}
{2, Mark}
{3, George}

"1:John, 2:Mark and 3:George"

我很好用硬编码ID + ":" + Name部分,但它可能是一个ToString(),具体取决于linq查询结果的类型。我只是想知道是否有一个简洁的方法来使用linq或String.Format()。

17 个答案:

答案 0 :(得分:6)

为什么选择Linq?

StringBuilder sb = new StringBuilder();

for(int i=0;i<k.Count();i++)
{
   sb.Append(String.Format("{0}:{1}", k[i].ID, k[i].Name);
   if(i + 2 < k.Count())
      sb.Append(", ");
   else if(i + 1 < k.Count())
      sb.Append(" and ");
}

真的,Linq让你做的就是隐藏循环。

另外,请确保您想要或不想要“Oxford Comma”;这个算法不会插入一个,但是会有一个小的改动(在除了最后一个之外的每个元素之后附加逗号和空格,并且在倒数第二个之后追加“和”)。

答案 1 :(得分:6)

public string ToPrettyCommas<T>(
  List<T> source,
  Func<T, string> stringSelector
)
{
  int count = source.Count;

  Func<int, string> prefixSelector = x => 
    x == 0 ? "" :
    x == count - 1 ? " and " :
    ", ";

  StringBuilder sb = new StringBuilder();

  for(int i = 0; i < count; i++)
  {
    sb.Append(prefixSelector(i));
    sb.Append(stringSelector(source[i]));
  }

  string result = sb.ToString();
  return result;
}

跟:

string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);

答案 2 :(得分:3)

只是为了好玩,这里真正使用功能性LINQ - 没有循环而没有StringBuilder。当然,这很慢。

var list = new[] { new { ID = 1, Name = "John" },
                   new { ID = 2, Name = "Mark" },
                   new { ID = 3, Name = "George" } };

var resultAggr = list
    .Select(item => item.ID + ":" + item.Name)
    .Aggregate(new { Sofar = "", Next = (string) null },
               (agg, next) => new { Sofar = agg.Next == null ? "" :
                                            agg.Sofar == "" ? agg.Next :
                                            agg.Sofar + ", " + agg.Next,
                                    Next = next });
var result = resultAggr.Sofar == "" ? resultAggr.Next :
             resultAggr.Sofar + " and " + resultAggr.Next;

// Prints 1:John, 2:Mark and 3:George
Console.WriteLine(result);

答案 3 :(得分:1)

与其他人一样,这并不比使用字符串构建器更好,但你可以去(忽略ID,你可以添加它):

IEnumerable<string> names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" };
int count = names.Count();
string s = String.Join(", ", names.Take(count - 2)
                 .Concat(new [] {String.Join(" and ", names.Skip(count - 2))}));

这种做法几乎滥用SkipTake取消负数的能力,String.Join愿意接受一个参数,因此适用于一,二或更多字符串。

答案 4 :(得分:1)

使用为您提供索引的Select操作,可以将其写为ONE LINE扩展方法:

public static string ToAndList<T>(this IEnumerable<T> list, Func<T, string> formatter)
{
   return string.Join(" ", list.Select((x, i) => formatter(x) + (i < list.Count() - 2 ? ", " : (i < list.Count() - 1 ? " and" : ""))));
}

e.g。

var list = new[] { new { ID = 1, Name = "John" },
                   new { ID = 2, Name = "Mark" },
                   new { ID = 3, Name = "George" } }.ToList();

Console.WriteLine(list.ToAndList(x => (x.ID + ": " + x.Name)));

答案 5 :(得分:0)

这可以是您实现目标的方式

var list = new[] { new { ID = 1, Name = "John" }, 
                   new { ID = 2, Name = "Mark" }, 
                   new { ID = 3, Name = "George" }
                 }.ToList();

int i = 0;

string str = string.Empty;

var k = list.Select(x => x.ID.ToString() + ":" + x.Name + ", ").ToList();

k.ForEach(a => { if (i < k.Count() - 1) { str = str +  a; } else { str = str.Substring(0, str.Length -2) + " and " + a.Replace("," , ""); } i++; });

答案 6 :(得分:0)

    public static string ToListingCommaFormat(this List<string> stringList)
    {
        switch(stringList.Count)
        {
            case 0:
                return "";
            case 1:
                return stringList[0];
            case 2:
                return stringList[0] + " and " + stringList[1];
            default:
                return String.Join(", ", stringList.GetRange(0, stringList.Count-1)) 
                    + ", and " + stringList[stringList.Count - 1];
        }
    }

这种方法比“高效”方法更快。 Gabe发布的加入方法。对于一个和两个项目,它的速度要快很多倍,对于5-6个字符串,速度要快10%。 LINQ没有任何依赖性。对于小型数组,String.Join比StringBuilder更快,这对于人类可读的文本来说是典型的。在语法中,这些被称为listing commas,并且应始终包括最后一个逗号以避免歧义。以下是生成的代码:

people.Select(x=> x.ID.ToString() + ":" + x.Name).ToList().ToListingCommaFormat();

答案 7 :(得分:0)

static public void Linq1()
{
    var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } };

    Func<string[], string> showPerson = p => p[0] + ": " + p[1];

    var res = k.Skip(1).Aggregate(new StringBuilder(showPerson(k.First())),
        (acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next)));

    Console.WriteLine(res);
}

可以通过将k.Last()计算移动到循环

之前进行优化

答案 8 :(得分:0)

这是一个使用我的answerEric Lippert's Challenge稍微修改过的版本,这是IMHO最简洁易懂的逻辑(如果您熟悉LINQ)。

static string CommaQuibblingMod<T>(IEnumerable<T> items)
{
    int count = items.Count();
    var quibbled = items.Select((Item, index) => new { Item, Group = (count - index - 2) > 0})
                        .GroupBy(item => item.Group, item => item.Item)
                        .Select(g => g.Key
                            ? String.Join(", ", g)
                            : String.Join(" and ", g));
    return String.Join(", ", quibbled);  //removed braces
}

//usage
var items = k.Select(item => String.Format("{0}:{1}", item.ID, item.Name));
string formatted = CommaQuibblingMod(items);

答案 9 :(得分:0)

这是一种不使用LINQ的方法,但可能效率最高:

public static string Join<T>(this IEnumerable<T> list,
                             string joiner,
                             string lastJoiner = null)
{
    StringBuilder sb = new StringBuilder();
    string sep = null, lastItem = null;
    foreach (T item in list)
    {
        if (lastItem != null)
        {
            sb.Append(sep);
            sb.Append(lastItem);
            sep = joiner;
        }
        lastItem = item.ToString();
    }
    if (lastItem != null)
    {
        if (sep != null)
            sb.Append(lastJoiner ?? joiner);
        sb.Append(lastItem);
    }
    return sb.ToString();
}

Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and "));

因为它永远不会创建一个列表,所以查看一个元素两次,或者向StringBuilder添加额外的东西,我认为你不能提高效率。它也适用于列表中的0,1和2个元素(以及更多,显然)。

答案 10 :(得分:0)

我已经完善了我以前的答案,我相信这是最优雅的解决方案 但是它只适用于不在集合中重复的引用类型(否则我们必须使用不同的方法来查找项目是第一个还是最后一个)。

享受!

var firstGuy = guys.First();
var lastGuy = guys.Last();

var getSeparator = (Func<Guy, string>)
    (guy => {
        if (guy == firstGuy) return "";
        if (guy == lastGuy) return " and ";
        return ", ";
    });

var formatGuy = (Func<Guy, string>)
    (g => string.Format("{0}:{1}", g.Id, g.Name));

// 1:John, 2:Mark and 3:George
var summary = guys.Aggregate("",
    (sum, guy) => sum + getSeparator(guy) + formatGuy(guy));

答案 11 :(得分:0)

这个怎么样?

var k = people.Select(x=>new{x.ID, x.Name});
var stringified = people
                  .Select(x => string.Format("{0} : {1}", x.ID, x.Name))
                  .ToList();
return string.Join(", ", stringified.Take(stringified.Count-1).ToArray())
       + " and " + stringified.Last();

答案 12 :(得分:0)

StringBuilder方法

这是Aggregate StringBuilder。有一些位置确定用于清理字符串并插入“和”,但它都是在StringBuilder级别完成的。

var people = new[]
{
    new { Id = 1, Name = "John" },
    new { Id = 2, Name = "Mark" },
    new { Id = 3, Name = "George" }
};

var sb = people.Aggregate(new StringBuilder(),
             (s, p) => s.AppendFormat("{0}:{1}, ", p.Id, p.Name));
sb.Remove(sb.Length - 2, 2); // remove the trailing comma and space

var last = people.Last();
// index to last comma (-2 accounts for ":" and space prior to last name)
int indexComma = sb.Length - last.Id.ToString().Length - last.Name.Length - 2;

sb.Remove(indexComma - 1, 1); // remove last comma between last 2 names
sb.Insert(indexComma, "and ");

// 1:John, 2:Mark and 3:George
Console.WriteLine(sb.ToString());

可以使用String.Join方法,但“and”插入和逗号删除会生成~2个新字符串。


正则表达式方法

这是使用正则表达式的另一种方法,这是非常容易理解的(没什么太神秘的)。

var people = new[]
{
    new { Id = 1, Name = "John" },
    new { Id = 2, Name = "Mark" },
    new { Id = 3, Name = "George" }
};
var joined = String.Join(", ", people.Select(p => p.Id + ":" + p.Name).ToArray());
Regex rx = new Regex(", ", RegexOptions.RightToLeft);
string result = rx.Replace(joined, " and ", 1); // make 1 replacement only
Console.WriteLine(result);

模式只是", "。神奇之处在于RegexOptions.RightToLeft,它使匹配从右边发生,从而使替换发生在最后一个逗号。没有静态Regex方法接受RegexOptions的替换次数,因此实例用法。

答案 13 :(得分:0)

你们都太复杂了:

var list = k.Select(x => x.ID + ":" + x.Name).ToList();
var str = list.LastOrDefault();
str = (list.Count >= 2 ? list[list.Count - 2] + " and " : null) + str;
str = string.Join(", ", list.Take(list.Count - 2).Concat(new[]{str}));

答案 14 :(得分:0)

这不是很好,但会使用L​​INQ

完成工作
string s = string.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name;

答案 15 :(得分:0)

改善(希望)KeithS的回答:

string nextBit = "";
var sb = new StringBuilder();
foreach(Person person in list)
{
    sb.Append(nextBit);
    sb.Append(", ");
    nextBit = String.Format("{0}:{1}", person.ID, person.Name);
}
sb.Remove(sb.Length - 3, 2);
sb.Append(" and ");
sb.Append(nextBit);

答案 16 :(得分:-1)

有很多方法可以优化它,因为它效率不高,但是这样的方法可能有效:

var k = people.Select(x => new {x.ID, x.Name}).ToList();

var last = k.Last();
k.Aggregate(new StringBuilder(), (sentence, item) => { 
    if (sentence.Length > 0)
    {
        if (item == last)
            sentence.Append(" and ");
        else
            sentence.Append(", ");
    }

    sentence.Append(item.ID).Append(":").Append(item.Name);
    return sentence;
});