IEnumerable:在匹配谓词

时间:2016-11-25 10:07:53

标签: c# linq

我有IEnumerable<int>这样,只有更长时间:

5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 6, 0, 0, 0, 0, 0

现在我想在最后一个非零值之前返回所有元素:

5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0

似乎sequence.Last()在这里没有帮助,因为它返回最后一次出现,而不是最后一次出现的索引。

我曾想过要用

var lastIndex = sequence.LastIndex(x=>x!=0);
subsequence = sequence.Take(lastIndex);

这适用于一般情况,但LastIndex不存在,或

var last = sequence.Last(y=>y!=0);
subsequence = sequence.TakeWhile(x=>x!=last)

哪个适用于该示例,但不适用于一般情况,可能存在重复的非零值。

有什么想法吗?

5 个答案:

答案 0 :(得分:5)

你可以试试这个

var allDataBeforeLastNonZero= sequence.GetRange(0,sequence.FindLastIndex(x=>x!=0));

答案 1 :(得分:5)

  

适用于一般情况,但LastIndex不存在

不,但您可以通过以下方式找到它:

var lastIndex = sequence
  .Select((x, i) => new {El = x, Idx = i})
  .Where(x => x.El != 0)
  .Select(x => x.Idx).Last();

如果您需要与IQueryable<T>合作,那就是尽可能好。

它有一些问题。一方面,它扫描序列两次,谁说序列甚至允许。我们可以做得更好,但我们必须缓冲,但不一定缓冲整个事情:

public static IEnumerable<T> BeforeLastMatch<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
  if (source == null) throw new ArgumentNullException(nameof(source));
  if (predicate == null) throw new ArgumentNullException(nameof(predicate));
  return BeforeLastMatchImpl(source, predicate);
}

public static IEnumerable<T> BeforeLastMatchImpl<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
  var buffer = new List<T>();
  foreach(T item in source)
  {
    if (predicate(item) && buffer.Count != 0)
    {
      foreach(T allowed in buffer)
      {
          yield return allowed;
      }
      buffer.Clear();
    }
    buffer.Add(item);
  }
}

致电sequence.BeforeLastMatch(x => x != 0),即可获得5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0

如果您真的需要它同时处理IEnumerableIQueryable,这也可以处理,但它有点复杂。如果你知道自己只有内存IEnumerable,请不要打扰。 (此外,一些提供商对不同功能有不同的支持,因此您可能无论如何都被迫执行上面的内存版本):

private class ElementAndIndex<T>
{
  public T Element { get; set; }
  public int Index { get; set; }
}

public static IQueryable<T> BeforeLastMatch<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
{
  if (source == null) throw new ArgumentNullException(nameof(source));
  if (predicate == null) throw new ArgumentNullException(nameof(predicate));
  // If source is actually an in-memory enumerable, the other method will be more efficient,
  // so use it instead.
  var asEnum = source as EnumerableQuery<T>;
  if (asEnum != null && asEnum.Expression.NodeType == ExpressionType.Constant)
  {
    // On any other IQueryable calling `AsEnumerable()` will force it
    // to be loaded into memory, but on an EnumerableQuery it just
    // unwraps the wrapped enumerable this will chain back to the
    // contained GetEnumerator.
    return BeforeLastMatchImpl(source.AsEnumerable(), predicate.Compile()).AsQueryable();
  }

  // We have a lambda from (T x) => bool, and we need one from
  // (ElementAndIndex<T> x) => bool, so build it here.

  var param = Expression.Parameter(typeof(ElementAndIndex<T>));
  var indexingPredicate = Expression.Lambda<Func<ElementAndIndex<T>, bool>>(
    Expression.Invoke(predicate, Expression.Property(param, "Element")),
    param
  );

  return source.Take( // We're going to Take based on the last index this finds.
    source
      // Elements and indices together
      .Select((x, i) => new ElementAndIndex<T>{ Element = x, Index = i}) 
      // The new predicate we created from that passed to us.
      .Where(indexingPredicate)
      // The last matching element.
      .Select(x => x.Index).Last());
}

答案 2 :(得分:4)

也许有更有效的方法,但这个方法是可读的,不是吗?

var allBeforeLastNonZero = sequence
    .Reverse()                // look from the end
    .SkipWhile(i => i == 0)   // skip the zeros
    .Skip(1)                  // skip last non-zero
    .Reverse();               // get original order

答案 3 :(得分:1)

您可以将列表转换为字符串并使用String.Trim

var str = String.Join(",", myInputArray);
var result = str.TrimEnd(',', '0').Split(',').Select(x => Convert.ToInt32(x)).ToList();
result.RemoveAt(result.Count - 1);

不得不承认看起来有点难看,但它应该有用。

答案 4 :(得分:1)

IEnumerable<int> source = new List<int> {5,0,0, 4,0,0,3, 0, 0};
List<int> result = new List<int>();
List<int> buffer = new List<int>();
foreach (var i in source)
{
    buffer.Add(i);
    if (i != 0)
    {
        result.AddRange(buffer);
        buffer.Clear();
    }
}