2011-05-17 61 views
2

假設我有一個inifite生成器A()。我想要的是獲得由A返回的所有數字的總和,使得總和不超過值N只有一個LINQ表達式只用一個LINQ表達式獲取IEnumerable集合的總和

我想知道是否有一個擴展方法,將幫助我呢?

經典的方法是:

int sum = 0; 
foreach (int x in A()) { 
    sum += x; 
    if (sum > N) { 
     break; 
    } 
} 

return sum; 

,但我一直在思考如何做到這一點的只有一個表達式沒有成功...

+0

您是指單個方法調用還是單個語句? – SLaks 2011-05-17 23:04:54

+2

你的意思是'''? – SLaks 2011-05-17 23:06:31

+0

@SLaks正確:) – 2011-05-17 23:14:26

回答

0

讓我們看看我是否有正確的要求。

A()是一個無限生成器。按照定義,它會永遠生成值(在這種情況下是整數)。

您想要查找所有小於N的值的全部並將它們相加。

Linq不是問題。在A()完成生成之前,您將不會完成添加......並且這種情況從未發生過。

順便說一句,你發佈的代碼並沒有全部小於N的所有值...它將所有值加起來,直到找到一個小於N的值,然後退出查找。這是你的意思嗎?

+2

他想繼續添加,直到** sum **超過N. – SLaks 2011-05-17 23:08:05

+0

我修復了代碼。我在飛行中寫了它。對此我很抱歉。 – 2011-05-17 23:16:34

-1

int sum = A()。其中​​(x => x < N).Sum();

+0

總和必須小於N.我錯過了我的第一個runthrough也。 – 2011-05-17 23:25:58

+0

@ Ritch,你爲什麼這麼想?奧斯卡提出的代碼提出的問題也產生了大於N的值。 – JBSnorro 2011-05-18 00:00:42

+0

@JBSnorro OP編輯了一下。總和必須大於N,不是每個個體x < N, or x > N. – 2011-05-18 00:02:55

3

如果A是一個無限生成器,那麼在使用僅內置LINQ方法的單個語句中無法做到這一點。

要做到這一點,在一個聲明中,沒有任何副作用,您可能需要使用某種Scan方法來計算輸入序列的prefix sum。那麼你只需要大於N的第一個元素。簡單!

int sum = A().Scan((s, x) => s + x).First(s => s > N); 

// ... 

public static class EnumerableExtensions 
{ 
    public static IEnumerable<T> Scan<T>(
     this IEnumerable<T> source, Func<T, T, T> func) 
    { 
     if (source == null) throw new ArgumentNullException("source"); 
     if (func == null) throw new ArgumentNullException("func"); 

     using (var e = source.GetEnumerator()) 
     { 
      if (e.MoveNext()) 
      { 
       T accumulator = e.Current; 
       yield return accumulator; 

       while (e.MoveNext()) 
       { 
        accumulator = func(accumulator, e.Current); 
        yield return accumulator; 
       } 
      } 
     } 
    } 
} 
+0

也許'PrefixAggregate'可能比'Scan'更好的名字? – Timwi 2011-05-18 00:01:20

+0

@Timwi:'Scan'是這個函數族函數式編程中似乎使用的名稱:請參見[F#](http://msdn.microsoft.com/zh-cn/library/ee340364.aspx)和例如,[Haskell](http://zvon.org/other/haskell/Outputprelude/scanl1_f.html)。我相信Reactive Extensions中也包含了一個掃描實現(但我沒有在這裏安裝RX,所以我無法確定。) – LukeH 2011-05-18 00:10:42

+0

@LukeH,這不會在頂行中使用兩個LINQ表達式嗎? ? (包括Scan(...)和First(...)) – JBSnorro 2011-05-18 00:11:33

3

使用標準的慣用LINQ,這將是不可能的。您需要的語義是Aggregate()TakeWhile()的組合。否則,你需要在LINQ中有副作用,這是不允許的。

這裏是有副作用的,以做這件事的例子:

var temp = 0; 
var sum = A().TakeWhile(i => 
{ 
    var res = !(temp > N); 
    temp += i; 
    return res; 
}).Sum(); 
3

肯定有辦法用一個單一的LINQ表達式來做到這一點。我能想出仍然有一定的通用性和優雅最簡單的是:

public static int SumWhile(this IEnumerable<int> collection, Func<int, bool> condition) 
{ 
    int sum = 0; 
    foreach (int i in collection) 
    { 
     sum += i; 
     if (!condition(sum)) 
      break; 
    } 
    return sum; 
} 

可以這樣調用:

int sum = A().SumWhile(i => i <= N); 

呀,只是一個單一的LINQ表達!玩得開心

+0

搞笑。最佳解決方案 – 2011-05-18 01:06:44

+1

這個問題有一點不一致:奧斯卡要求總和不要超過N,而在他的代碼中(我已經實現)。這使得我的方法的名稱有點令人討厭,因爲它不會在某些條件_時產生_sum,而在某些條件和下一個_sum時我會覺得我應該提及這一點 – JBSnorro 2011-05-19 01:43:14

0

我相信下面的可怕代碼可以滿足您的要求。:-)

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

namespace ConsoleApplication12 { 
    public class Program { 
    public static void Main(string[] args) { 
     const int N=100; 

     int sum; 
     try { 
     sum=A().Aggregate((self, next) => { 
      if(self+next<=N) 
      return self+next; 
      else 
      throw new ResultException(self); 
     }); 
     } catch(ResultException re) { 
     sum=re.Value; 
     } 
     Debug.Print("Sum="+sum); 
    } 

    private class ResultException : Exception { 
     public readonly int Value; 

     public ResultException(int value) { 
     Value=value; 
     } 
    } 

    private static IEnumerable<int> A() { 
     var i=0; 
     while(true) { 
     yield return i++; 
     } 
    } 
    } 
} 
1

也許最靠近你最初的想法:

int sum = 0; 
int limit = 500; 
A().TakeWhile(i => (sum += i) < limit).Count(); 
//Now the variable named sum contains the smaller sum of elements being >= limit 

伯爵()不能用於它的返回值,但給力的實際枚舉。