根据另一个属性并置一个属性的值

时间:2019-11-06 07:35:11

标签: c# arrays string list linq

我有这堂课

public enum SubStatus { active, inactive, unset}
public class SubItem
{
    public SubStatus Status { get; set; }

    public string Value { get; set; }
}

该类中的值可能看起来像这样

List<SubItem> list = new List<SubItem>()
{
    new SubItem() { Status = SubStatus.active, Value = "1" },
    new SubItem() { Status = SubStatus.active, Value = "2" }, //active again, add "2" to the previous
    new SubItem() { Status = SubStatus.inactive, Value = "1" },
    new SubItem() { Status = SubStatus.active, Value = "2" },
    new SubItem() { Status = SubStatus.unset, Value = "2" },
    new SubItem() { Status = SubStatus.unset, Value = "1" }, //unset again, add "1" to the previous
    new SubItem() { Status = SubStatus.unset, Value = "1" } //unset again, add "1" to the previous
};

现在,我需要将字符串值合并在一起,以防某个项目的Status等于上一个项目的Status。所以在这个例子中,结果应该像这样

List <SubItem> output = new List<SubItem>()
{
    new SubItem() { Status = SubStatus.active, Value = "12" },
    new SubItem() { Status = SubStatus.inactive, Value = "1" },
    new SubItem() { Status = SubStatus.active, Value = "2" },
    new SubItem() { Status = SubStatus.unset, Value = "211" }
};

到目前为止我尝试过的方法都可以奏效,但是以某种方式我认为这种方法是错误的,并且使用linq会有更好的方法...

List<SubItem> result = new List<SubItem>();
SubItem temp = list.FirstOrDefault();
if (temp != null)
{
    foreach (SubItem item in list.Skip(1))
    {
        if (temp.Status == item.Status)
        {
            temp.Value += item.Value;
        }
        else if (temp.Status != item.Status)
        {
            result.Add(temp);
            temp = item;
        }
    }
}
result.Add(temp);   

3 个答案:

答案 0 :(得分:3)

标准Linq 不提供分组,但我们可以手动实现:

  public static partial class EnumerableExtensions {
    public static IEnumerable<T[]> ToBatch<T>(this IEnumerable<T> source,
                                              Func<ICollection<T>, T, bool> addToBatch) {

      if (null == source)
        throw new ArgumentNullException(nameof(source));
      else if (null == addToBatch)
        throw new ArgumentNullException(nameof(addToBatch));

      List<T> batch = new List<T>();

      foreach (T item in source) {
        // Shall we start a new batch?
        if (batch.Count > 0 && !addToBatch(batch, item)) {
          yield return batch.ToArray();

          batch.Clear();
        }

        batch.Add(item);
      }

      if (batch.Count > 0) // do we have a last batch?
        yield return batch.ToArray();
    }
  }

现在,我们可以将实现用作

  // Test Data
  List<SubItem> list = new List<SubItem>() {
    new SubItem() { Status = SubStatus.active,   Value = "1" },
    new SubItem() { Status = SubStatus.active,   Value = "2" },   
    new SubItem() { Status = SubStatus.inactive, Value = "1" },
    new SubItem() { Status = SubStatus.active,   Value = "2" },
    new SubItem() { Status = SubStatus.unset,    Value = "2" },
    new SubItem() { Status = SubStatus.unset,    Value = "1" }, 
    new SubItem() { Status = SubStatus.unset,    Value = "1" } 
  };

  ...

  List<SubItem> result = list
    .ToBatch((batch, item) => item.Status == batch.First().Status)
    .Select(batch => new SubItem() {
      Status = batch.First().Status,
      Value  = string.Concat(batch.Select(item => item.Value))
    })
    .ToList();

  // Let's have a look  
  Console.Write(string.Join(Environment.NewLine, result
    .Select(item => $"{item.Status,-8} : {item.Value}")));

结果:

active   : 12
inactive : 1
active   : 2
unset    : 211

答案 1 :(得分:2)

您可以使用Aggregate,但IMO并不比常规foreach好:

var result = list
    .Aggregate(
        (IEnumerable<SubItem>)Array.Empty<SubItem>(), 
        (result, item) => 
            result.LastOrDefault()?.Status == item.Status
                ? result.SkipLast(1).Concat(new [] { new SubItem { Status = item.Status, Value = result.Last().Value + item.Value } })
                : result.Concat(new [] { item })
    )
    .ToList();

这是另一种想法(使用TakeWhile),但更为复杂:

var result2 = list
    .Select((item, index) => list
        .Take(index + 1)
        .Reverse()
        .TakeWhile(x => x.Status == item.Status)
        .Select((x, i) => Tuple.Create(x, index - i))
        .Reverse()
    )
    .GroupBy(x => x.Select(y => y.Item2).Min())
    .Select(x => x.Last())
    .Select(x => new SubItem
    {
        Status = x.First().Item1.Status,
        Value = string.Join("", x.Select(y => y.Item1.Value))
    })
    .ToList();

答案 2 :(得分:1)

您不能使用标准LINQ来完成全部操作,但是可以利用GroupAdjacent中的MoreLINQ

var result = list
    .GroupAdjacent(item => item.Status)
    .Select(grp => new SubItem
    {
        Status = grp.Key,
        Value = string.Concat(grp.Select(item => item.Value))
    });

foreach (var item in result)
{
    Console.WriteLine(item.ToString());
}

通过Status对相邻项进行分组,使用Enumerable.Select选择新的SubItem,然后使用String.Concat组合分组的值。

上面的示例输出以下内容:

Status=active,Value=12
Status=inactive,Value=1
Status=active,Value=2
Status=unset,Value=211

注意:以上内容覆盖了ToString()的{​​{1}}方法:

SubItem