2014-07-08 9 views
1

SelectMany의 파생 된 LINQ 쿼리를 만들려고합니다. 나는 List<List<object>>(),로 결과를 싶습니다속성 합계로 LINQ 그룹

new { 
    { Text = "Hello", Width = 2 }, 
    { Text = "Something else", Width = 1 }, 
    { Text = "Another", Width = 1 }, 
    { Text = "Extra-wide", Width = 3 }, 
    { Text = "Random", Width = 1 } 
} 

: 나는 N 항목이

List<List<object>> = new { 
    // first "row" 
    { 
     { Text = "Hello", Width = 2 }, 
     { Text = "Something else", Width = 1 }, 
     { Text = "Another", Width = 1 } 
    }, 
    // second "row" 
    { 
     { Text = "Extra-wide", Width = 3 }, 
     { Text = "Random", Width = 1 } 
    } 
} 

그래서 항목 "행"으로 분류됩니다 경우 내부 목록에서 합계 (폭) 숫자보다 작거나 같습니다 (maxWidth - 내 인스턴스에서 4). 그것은 다소 GroupBy의 파생물이지만 GroupBy는 배열의 초기 값에 의존합니다.

모든 아이디어를 얻을 수 있습니다.

답변

2

조건이 집계를 구축 충족하는 동안 우리는 그룹 연속 항목에 GroupWhile 방법으로 LINQ의 집계 방법의 아이디어를 결합 할 수 있습니다 술어에서 사용할 현재 그룹의 값 :

public static IEnumerable<IEnumerable<T>> GroupWhileAggregating<T, TAccume>(
    this IEnumerable<T> source, 
    TAccume seed, 
    Func<TAccume, T, TAccume> accumulator, 
    Func<TAccume, T, bool> predicate) 
{ 
    using (var iterator = source.GetEnumerator()) 
    { 
     if (!iterator.MoveNext()) 
      yield break; 

     List<T> list = new List<T>() { iterator.Current }; 
     TAccume accume = accumulator(seed, iterator.Current); 
     while (iterator.MoveNext()) 
     { 
      accume = accumulator(accume, iterator.Current); 
      if (predicate(accume, iterator.Current)) 
      { 
       list.Add(iterator.Current); 
      } 
      else 
      { 
       yield return list; 
       list = new List<T>() { iterator.Current }; 
       accume = accumulator(seed, iterator.Current); 
      } 
     } 
     yield return list; 
    } 
} 

우리는 이제 다음과 같이 작성할 수 있습니다 :

var query = data.GroupWhileAggregating(0, 
    (sum, item) => sum + item.Width, 
    (sum, item) => sum <= 4); 
+0

좋지만 더 많거나 적습니다 (필자는 훨씬 더 우아하고 융통성있게 동의해야합니다!) 아래 답변을 구현하십시오. – derpirscher

+0

@derpirscher 슬프게도 우아함이이 라운드에서 우승합니다. –

+0

물론 @codegecko! 나는 너무 upvoted;) – derpirscher

0

저는 LINQ로 그렇게 할 수 있다고 생각하지 않습니다.

var data = ... // original data 
var newdata = new List<List<object>>(); 
int csum = 0; 
var crow = new List<object>(); 
foreach (var o in data) { 
    if (csum + o.Width > 4) { //check if the current element fits into current row 
     newdata.Add(crow); //if not add current row to list 
     csum = 0; 
     crow = new List<object>(); //and create new row 
    } 
    crow.Add(o); //add current object to current row 
    csum += o.Width; 
} 

if (crow.Count() > 0) //last row 
    newData.Add(c); 

편집 : 하나의 대안 방법은 다음을하여야 할 다른 대답은 MoreLinq 라이브러리에서 배치를 사용하는 제안합니다. 사실 위의 소스 코드는 Batch가하는 것과 거의 같지만 각 배치의 요소를 세는 것이 아니라 원하는 속성을 합산하는 것입니다. 사용자 정의 선택기로 코드를 일반화하면 "배치 크기"측면에서보다 융통성을 발휘할 수 있습니다.

1

NuGet 패키지로 제공되는 MoreLinq 라이브러리에서 Batch 메소드로 정렬 할 수 있습니다. 결과는 List<IEnumerable<object>>입니다. 여기에 코드입니다 :

class Obj 
{ 
    public string Text {get;set;} 
    public int Width {get;set;} 
} 

void Main() 
{ 

    var data = new [] { 
     new Obj { Text = "Hello", Width = 2 }, 
     new Obj { Text = "Something else", Width = 1 }, 
     new Obj { Text = "Another", Width = 1 }, 
     new Obj { Text = "Extra-wide", Width = 3 }, 
     new Obj { Text = "Random", Width = 1 } 
    }; 

    var maxWidth = data.Max (d => d.Width); 
    var result = data.Batch(maxWidth).ToList(); 
    result.Dump(); // Dump is a linqpad method 

출력

enter image description here

+1

[이 답변] (http://stackoverflow.com/a/13731854/1277156)의 작업 일괄 처리 기능도 있습니다. – Measuring

+2

그건 상당히 영리한 구현입니다. –

+2

배치 (maxSize)는 각 배치가 최대 maxSize 요소를 갖는 배치를 만듭니다. 요소의 속성을 고려하지 않습니다. 나는. "Hello"의 너비를 4로 변경하면 첫 번째 일괄 처리에는 "Something else"와 "another"및 "Extra-wide"도 포함됩니다. 원래 질문에서는 "Hello"로만 구성되어야합니다. – derpirscher