LINQ를 사용하여 시퀀스에서 마지막 요소를 제외한 모든 요소를 가져 오는 방법은 무엇입니까?
시퀀스가 있다고 가정 해 봅시다.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
시퀀스를 얻는 것은 저렴하지 않고 동적으로 생성되므로 한 번만 반복하고 싶습니다.
0-999999를 얻고 싶습니다 (즉, 마지막 요소를 제외한 모든 것)
나는 다음과 같은 것을 할 수 있음을 알고 있습니다.
sequence.Take(sequence.Count() - 1);
그러나 그 결과 큰 시퀀스에 대해 두 가지 열거가 발생합니다.
내가 할 수있는 LINQ 구문이 있습니까?
sequence.TakeAllButTheLastElement();
Linq 솔루션을 모르겠습니다. 그러나 생성기를 사용하여 알고리즘을 쉽게 코딩 할 수 있습니다 (수율 반환).
public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source) {
var it = source.GetEnumerator();
bool hasRemainingItems = false;
bool isFirst = true;
T item = default(T);
do {
hasRemainingItems = it.MoveNext();
if (hasRemainingItems) {
if (!isFirst) yield return item;
item = it.Current;
isFirst = false;
}
} while (hasRemainingItems);
}
static void Main(string[] args) {
var Seq = Enumerable.Range(1, 10);
Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
Console.WriteLine(string.Join(", ", Seq.TakeAllButLast().Select(x => x.ToString()).ToArray()));
}
또는 마지막 n 개의 항목을 폐기하는 일반 솔루션으로 (댓글에서 제안한 것과 같은 대기열 사용) :
public static IEnumerable<T> SkipLastN<T>(this IEnumerable<T> source, int n) {
var it = source.GetEnumerator();
bool hasRemainingItems = false;
var cache = new Queue<T>(n + 1);
do {
if (hasRemainingItems = it.MoveNext()) {
cache.Enqueue(it.Current);
if (cache.Count > n)
yield return cache.Dequeue();
}
} while (hasRemainingItems);
}
static void Main(string[] args) {
var Seq = Enumerable.Range(1, 4);
Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
Console.WriteLine(string.Join(", ", Seq.SkipLastN(3).Select(x => x.ToString()).ToArray()));
}
자체 메소드를 작성하는 대신 요소 순서가 중요하지 않은 경우 다음이 작동합니다.
var result = sequence.Reverse().Skip(1);
나는 명시 적으로 사용하는 팬이 아니기 때문에 Enumerator
대안이 있습니다. 래퍼 메소드는 순서가 실제로 열거 될 때까지 점검을 연기하는 대신 유효하지 않은 인수가 일찍 발생하도록해야합니다.
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException("source");
return InternalDropLast(source);
}
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source)
{
T buffer = default(T);
bool buffered = false;
foreach (T x in source)
{
if (buffered)
yield return buffer;
buffer = x;
buffered = true;
}
}
Eric Lippert의 제안에 따라 n 개의 항목으로 쉽게 일반화됩니다.
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n)
{
if (source == null)
throw new ArgumentNullException("source");
if (n < 0)
throw new ArgumentOutOfRangeException("n",
"Argument n should be non-negative.");
return InternalDropLast(source, n);
}
private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
{
Queue<T> buffer = new Queue<T>(n + 1);
foreach (T x in source)
{
buffer.Enqueue(x);
if (buffer.Count == n + 1)
yield return buffer.Dequeue();
}
}
여기서는 항복 후가 아니라 항복 전에 버퍼링 하므로 n == 0
특별한 처리가 필요하지 않습니다.
BCL (또는 내가 생각하는 MoreLinq)에는 아무것도 없지만 자신의 확장 방법을 만들 수 있습니다.
public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source)
{
using (var enumerator = source.GetEnumerator())
bool first = true;
T prev;
while(enumerator.MoveNext())
{
if (!first)
yield return prev;
first = false;
prev = enumerator.Current;
}
}
}
최신 버전의 .net을 사용하는 사람들을 위해이 Enumerable.SkipLast(IEnumerable<TSource>, Int32)
방법은 .NET Core 2.0에 추가되었습니다.
var sequence = GetSequence();
var allExceptLast = sequence.SkipLast(1);
출처 : https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
.NET Framework에 이와 같은 확장 방법이 제공되면 도움이 될 것입니다.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
var enumerator = source.GetEnumerator();
var queue = new Queue<T>(count + 1);
while (true)
{
if (!enumerator.MoveNext())
break;
queue.Enqueue(enumerator.Current);
if (queue.Count > count)
yield return queue.Dequeue();
}
}
확장 프로그램을 출시 할 시간이없는 경우 다음과 같은 방법을 사용하십시오.
var next = sequence.First();
sequence.Skip(1)
.Select(s =>
{
var selected = next;
next = s;
return selected;
});
Joren의 우아한 솔루션에 대한 약간의 확장 :
public static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right)
{
int i = 0;
var buffer = new Queue<T>(right + 1);
foreach (T x in source)
{
if (i >= left) // Read past left many elements at the start
{
buffer.Enqueue(x);
if (buffer.Count > right) // Build a buffer to drop right many elements at the end
yield return buffer.Dequeue();
}
else i++;
}
}
public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1)
{
return source.Shrink(0, n);
}
public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1)
{
return source.Shrink(n, 0);
}
축소는 첫 번째 left
많은 요소 를 삭제하기 위해 간단한 카운트 포워드를 구현 하고 마지막 right
많은 요소 를 삭제하기 위해 동일한 버려진 버퍼를 구현 합니다.
열거 형 Count
또는 Length
열거 형을 얻을 수 있다면 대부분의 경우 가능합니다.Take(n - 1)
배열이있는 예
int[] arr = new int[] { 1, 2, 3, 4, 5 };
int[] sub = arr.Take(arr.Length - 1).ToArray();
예 IEnumerable<T>
IEnumerable<int> enu = Enumerable.Range(1, 100);
IEnumerable<int> sub = enu.Take(enu.Count() - 1);
허용 된 답변에 약간의 변형이 있습니다. 내 취향에 따라 조금 더 간단합니다.
public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
{
// for efficiency, handle degenerate n == 0 case separately
if (n == 0)
{
foreach (var item in enumerable)
yield return item;
yield break;
}
var queue = new Queue<T>(n);
foreach (var item in enumerable)
{
if (queue.Count == n)
yield return queue.Dequeue();
queue.Enqueue(item);
}
}
왜 .ToList<type>()
순서를 정하지 않았고 count를 호출하고 원래했던 것처럼 가져 가십시오.하지만 목록으로 가져 오기 때문에 값 비싼 열거를 두 번 수행 해서는 안됩니다 . 권리?
이 문제에 사용하는 솔루션은 약간 더 정교합니다.
내 util 정적 클래스에는 -items를 -items MarkEnd
로 변환 하는 확장 메소드 가 포함되어 있습니다 . 각 요소는 추가로 표시되는 어느 하나이고, 0 ; 또는 (마지막 3 개 항목에 특히 관심이있는 경우) -3 , -2 또는 -1 마지막 3 개 항목.T
EndMarkedItem<T>
int
이는, 자신에 유용 할 수있다 예를 들어 간단한에서 목록을 만들 때 foreach
마지막 두 제외한 각 요소 다음 쉼표로 -loop을 (같은 관련 단어 다음에 마지막에서 두 번째 항목 " 및 " 또는 " 또는 " "), 마지막 요소 다음에 점이옵니다.
마지막 n 개의 항목 없이 전체 목록을 생성하기 위해 확장 메소드는 ButLast
단순히 EndMarkedItem<T>
s 동안 반복합니다 EndMark == 0
.
을 지정하지 않으면 tailLength
마지막 항목 만 표시 ( MarkEnd()
)되거나 삭제 ( ButLast()
)됩니다.
다른 솔루션과 마찬가지로 버퍼링을 통해 작동합니다.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Adhemar.Util.Linq {
public struct EndMarkedItem<T> {
public T Item { get; private set; }
public int EndMark { get; private set; }
public EndMarkedItem(T item, int endMark) : this() {
Item = item;
EndMark = endMark;
}
}
public static class TailEnumerables {
public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts) {
return ts.ButLast(1);
}
public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts, int tailLength) {
return ts.MarkEnd(tailLength).TakeWhile(te => te.EndMark == 0).Select(te => te.Item);
}
public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts) {
return ts.MarkEnd(1);
}
public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts, int tailLength) {
if (tailLength < 0) {
throw new ArgumentOutOfRangeException("tailLength");
}
else if (tailLength == 0) {
foreach (var t in ts) {
yield return new EndMarkedItem<T>(t, 0);
}
}
else {
var buffer = new T[tailLength];
var index = -buffer.Length;
foreach (var t in ts) {
if (index < 0) {
buffer[buffer.Length + index] = t;
index++;
}
else {
yield return new EndMarkedItem<T>(buffer[index], 0);
buffer[index] = t;
index++;
if (index == buffer.Length) {
index = 0;
}
}
}
if (index >= 0) {
for (var i = index; i < buffer.Length; i++) {
yield return new EndMarkedItem<T>(buffer[i], i - buffer.Length - index);
}
for (var j = 0; j < index; j++) {
yield return new EndMarkedItem<T>(buffer[j], j - index);
}
}
else {
for (var k = 0; k < buffer.Length + index; k++) {
yield return new EndMarkedItem<T>(buffer[k], k - buffer.Length - index);
}
}
}
}
}
}
public static IEnumerable<T> NoLast<T> (this IEnumerable<T> items) {
if (items != null) {
var e = items.GetEnumerator();
if (e.MoveNext ()) {
T head = e.Current;
while (e.MoveNext ()) {
yield return head; ;
head = e.Current;
}
}
}
}
나는 이것보다 간결해질 수 있다고 생각하지 않는다 IEnumerator<T>
.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
using (var it = source.GetEnumerator())
{
if (it.MoveNext())
{
var item = it.Current;
while (it.MoveNext())
{
yield return item;
item = it.Current;
}
}
}
}
편집 : 이 답변 과 기술적으로 동일합니다 .
당신은 쓸 수 있습니다 :
var list = xyz.Select(x=>x.Id).ToList();
list.RemoveAt(list.Count - 1);
이것은 모든 경우를 올바르게 처리하는 일반적인 IMHO 고급 솔루션입니다.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
IEnumerable<int> r = Enumerable.Range(1, 20);
foreach (int i in r.AllButLast(3))
Console.WriteLine(i);
Console.ReadKey();
}
}
public static class LinqExt
{
public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
{
using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
{
Queue<T> queue = new Queue<T>(n);
for (int i = 0; i < n && enumerator.MoveNext(); i++)
queue.Enqueue(enumerator.Current);
while (enumerator.MoveNext())
{
queue.Enqueue(enumerator.Current);
yield return queue.Dequeue();
}
}
}
}
나의 전통적인 IEnumerable
접근 방식 :
/// <summary>
/// Skips first element of an IEnumerable
/// </summary>
/// <typeparam name="U">Enumerable type</typeparam>
/// <param name="models">The enumerable</param>
/// <returns>IEnumerable of type skipping first element</returns>
private IEnumerable<U> SkipFirstEnumerable<U>(IEnumerable<U> models)
{
using (var e = models.GetEnumerator())
{
if (!e.MoveNext()) return;
for (;e.MoveNext();) yield return e.Current;
yield return e.Current;
}
}
/// <summary>
/// Skips last element of an IEnumerable
/// </summary>
/// <typeparam name="U">Enumerable type</typeparam>
/// <param name="models">The enumerable</param>
/// <returns>IEnumerable of type skipping last element</returns>
private IEnumerable<U> SkipLastEnumerable<U>(IEnumerable<U> models)
{
using (var e = models.GetEnumerator())
{
if (!e.MoveNext()) return;
yield return e.Current;
for (;e.MoveNext();) yield return e.Current;
}
}
간단한 방법은 건너 뛰고 싶은 항목 수가 남을 때까지 대기열로 변환하고 대기열에서 꺼내는 것입니다.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
{
var queue = new Queue<T>(source);
while (queue.Count() > n)
{
yield return queue.Dequeue();
}
}
될 수 있습니다 :
var allBuLast = sequence.TakeWhile(e => e != sequence.Last());
I guess it should be like de "Where" but preserving the order(?).
If speed is a requirement, this old school way should be the fastest, even though the code doesn't look as smooth as linq could make it.
int[] newSequence = int[sequence.Length - 1];
for (int x = 0; x < sequence.Length - 1; x++)
{
newSequence[x] = sequence[x];
}
This requires that the sequence is an array since it has a fixed length and indexed items.
I would probably do something like this:
sequence.Where(x => x != sequence.LastOrDefault())
This is one iteration with a check that it isn't the last one for each time though.
'programing tip' 카테고리의 다른 글
EditText에서 numberDecimal inputType이있는 10 진 구분 기호 쉼표 ( ',') (0) | 2020.07.17 |
---|---|
Java 제네릭 클래스를 만들 때 꺾쇠 괄호로 묶은 의미는 무엇입니까? (0) | 2020.07.17 |
CSS에서 display : inline-block vs float : left 사용의 장점 (0) | 2020.07.17 |
PermGen은 실제로 무엇을 의미합니까? (0) | 2020.07.17 |
단일 객체 []를 params 객체에 전달하는 방법 [] (0) | 2020.07.17 |