我有一个电子邮件地址的字符串数组。假设该字符串数组长度是任意的——它可以只有几个项目,也可以有很多项。我希望构建另一个字符串,其中包含来自字符串数组的50个电子邮件地址,直到数组结束,并在每50个地址后调用Send()方法发送邮件。
更普遍的问题是怎样最清晰地完成这种操作。我有一个解决方案,那是我从VBScript学习遗留下来的,但我相信在C#中会有更好的方法。
你想要优雅简洁,我将给你优雅简洁:
var fifties = from index in Enumerable.Range(0, addresses.Length)
group addresses[index] by index/50;
foreach(var fifty in fifties)
Send(string.Join(";", fifty.ToArray());
如果可以不必用循环代码就能完成,为什么还要麻烦呢?如果你想把东西分成五十个一组,那就把它们分成五十个一组。 这就是group操作符的作用!
更新:评论者MoreCoffee问如何使用此操作符。假设我们想按三个一组进行分组,因为这样更容易打字。
var threes = from index in Enumerable.Range(0, addresses.Length)
group addresses[index] by index/3;
假设有从0到8的九个地址。
这个查询是什么意思呢?
Enumerable.Range
是一个从零开始的九个数字的范围,即 0, 1, 2, 3, 4, 5, 6, 7, 8
。
范围变量 index
依次取这些值。
然后我们遍历每个对应的 addresses[index]
并将其分配到一个组中。
我们将它分配到哪个组呢?是分配到 index/3
组。在 C# 中整数算术向零舍入,所以索引 0、1 和 2 在除以 3 时变为 0。索引 3、4、5 在除以 3 时变为 1。索引 6、7、8 在除以 3 时变为 2。
因此,我们将 addresses[0]
,addresses[1]
和 addresses[2]
分配到第 0 组,将 addresses[3]
,addresses[4]
和 addresses[5]
分配到第 1 组,以此类推。
查询的结果是一个由三个组成的序列,每个组都是由三个项目组成的序列。
有意义吗?
请记住,查询表达式的结果是代表此操作的查询。它在 foreach
循环执行之前不会执行该操作。
这似乎类似于这个问题:如何使用LINQ将集合拆分成n个部分?
那里的Hasan Khan的答案的修改版应该可以解决问题:
public static IEnumerable<IEnumerable<T>> Chunk<T>(
this IEnumerable<T> list, int chunkSize)
{
int i = 0;
var chunks = from name in list
group name by i++ / chunkSize into part
select part.AsEnumerable();
return chunks;
}
使用示例:
var addresses = new[] { "a@example.com", "b@example.org", ...... };
foreach (var chunk in Chunk(addresses, 50))
{
SendEmail(chunk.ToArray(), "Buy V14gr4");
}
string[] allAddresses = GetLongArrayOfAddresses();
const int batchSize = 50;
for (int n = 0; n < allAddresses.Length; n += batchSize)
{
string batch = string.Join(";", allAddresses, n,
Math.Min(batchSize, allAddresses.Length - n));
// use batch somehow
}
string[] s = new string[] {"1", "2", "3", "4"....};
for (int i = 0; i < s.Count(); i = i + 50)
{
string s = string.Join(";", s.Skip(i).Take(50).ToArray());
DoSomething(s);
}
void Foo(string[] addresses)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < addresses.Length; i++)
{
sb.Append(addresses[i]);
if ((i + 1) % 50 == 0 || i == addresses.Length - 1)
{
Send(sb.ToString());
sb = new StringBuilder();
}
else
{
sb.Append("; ");
}
}
}
void Send(string addresses)
{
}
我认为这很简单且足够快速。下面的示例将长句分成了15个部分,但您可以传递批处理大小作为参数以使其动态化。在这里,我仅使用“/n”进行划分。
private static string Concatenated(string longsentence)
{
const int batchSize = 15;
string concatanated = "";
int chanks = longsentence.Length / batchSize;
int currentIndex = 0;
while (chanks > 0)
{
var sub = longsentence.Substring(currentIndex, batchSize);
concatanated += sub + "/n";
chanks -= 1;
currentIndex += batchSize;
}
if (currentIndex < longsentence.Length)
{
int start = currentIndex;
var finalsub = longsentence.Substring(start);
concatanated += finalsub;
}
return concatanated;
}
这显示了分割操作的结果。
var parts = Concatenated(longsentence).Split(new string[] { "/n" }, StringSplitOptions.None);
基于Eric的回答的扩展方法:
public static IEnumerable<IEnumerable<T>> SplitIntoChunks<T>(this T[] source, int chunkSize)
{
var chunks = from index in Enumerable.Range(0, source.Length)
group source[index] by index / chunkSize;
return chunks;
}
public static T[][] SplitIntoArrayChunks<T>(this T[] source, int chunkSize)
{
var chunks = from index in Enumerable.Range(0, source.Length)
group source[index] by index / chunkSize;
return chunks.Select(e => e.ToArray()).ToArray();
}
我认为我们需要更多关于这个列表的具体信息才能给出明确的答案。目前我假设它是一个由分号分隔的电子邮件地址列表。如果是这样,您可以执行以下操作来获取分块列表。
public IEnumerable<string> DivideEmailList(string list) {
var last = 0;
var cur = list.IndexOf(';');
while ( cur >= 0 ) {
yield return list.SubString(last, cur-last);
last = cur + 1;
cur = list.IndexOf(';', last);
}
}
public IEnumerable<List<string>> ChunkEmails(string list) {
using ( var e = DivideEmailList(list).GetEnumerator() ) {
var list = new List<string>();
while ( e.MoveNext() ) {
list.Add(e.Current);
if ( list.Count == 50 ) {
yield return list;
list = new List<string>();
}
}
if ( list.Count != 0 ) {
yield return list;
}
}
}