LINQ list to sentence format (insert commas & “and”)

后端 未结 17 1306
天命终不由人
天命终不由人 2021-01-12 23:33

I have a linq query that does something simple like:

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

I then want a function or linq lambda,

相关标签:
17条回答
  • 2021-01-13 00:32

    Using the Select operation that gives you an index, this can be written as a ONE LINE extension method:

    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)));
    
    0 讨论(0)
  • 2021-01-13 00:34

    StringBuilder Approach

    Here's an Aggregate with a StringBuilder. There's some position determinations that are made to clean up the string and insert the "and" but it's all done at the StringBuilder level.

    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());
    

    A String.Join approach could have been used instead but the "and" insertion and comma removal would generate ~2 new strings.


    Regex Approach

    Here's another approach using regex that is quite understandable (nothing too cryptic).

    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);
    

    The pattern is simply ", ". The magic lies in the RegexOptions.RightToLeft which makes the match occur from the right and thereby makes the replacement occur at the last comma occurrence. There is no static Regex method that accepts the number of replacements with the RegexOptions, hence the instance usage.

    0 讨论(0)
  • 2021-01-13 00:35
    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;
    }
    

    Called with:

    string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);
    
    0 讨论(0)
  • 2021-01-13 00:37

    This can be the way you can achieve your goal

    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++; });
    
    0 讨论(0)
  • 2021-01-13 00:37
        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];
            }
        }
    

    This is the method is faster than the 'efficient' Join method posted by Gabe. For one and two items, it is many times faster, and for 5-6 strings, it is about 10% faster. There is no dependency on LINQ. String.Join is faster than StringBuilder for small arrays, which are typical for human-readable text. In grammar, these are called listing commas, and the last comma should always be included to avoid ambiguity. Here is the resulting code:

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

    0 讨论(0)
提交回复
热议问题