我遇到了一个看似简单且令人尴尬的困难时期。我想要的只是IEnumberable中的下一个元素而不使用Skip(1).Take(1).Single()。这个例子说明了基本问题。
private char _nextChar;
private IEnumerable<char> getAlphabet()
{
yield return 'A';
yield return 'B';
yield return 'C';
}
public void sortAlphabet()
{
foreach (char alpha in getAlphabet())
{
switch (alpha)
{
case 'A': //When A pops up, I want to get the next element, ie 'B'
_nextChar = getAlphabet().Skip(1).Take(1).Single();
break;
case 'B': //When B pops up, I want 'C' etc
_nextChar = getAlphabet().Skip(1).Take(1).Single();
break;
}
}
}
除了丑陋之外,这个例子有效。但是,让我们说IEnumerable包含200万个元素,然后LINQ语句使程序执行速度难以忍受。我想要的很简单。我只想要IEnumberable&lt;&gt;中的下一个元素。如果有如下函数,我的所有问题都将得到解决:
_nextChar = getAlphabet().moveNext() //or getNext()
如果解决方案保持与示例相同的结构/布局/功能,则更为优选,但我很灵活。我的程序是一个文件解析器,在200万行文本中有一些键,如“money = 324”,其中“money”和“324”是IEnumberable中的邻居元素,当解析器出现“money”时,我想“ 324" 。 (谁没有?:D对不起的双关语。)
答案 0 :(得分:13)
如果,我的所有问题都将得到解决 有一个功能,如:
_nextChar = getAlphabet().moveNext() //or getNext()
这样的功能完全。它只属于IEnumerator<T>
,而不是IEnumerable<T>
!
private char _nextChar;
private IEnumerable<char> getAlphabet()
{
yield return 'A';
yield return 'B';
yield return 'C';
}
public void sortAlphabet()
{
using (var enumerator = getAlphabet().GetEnumerator())
{
while (enumerator.MoveNext())
{
char alpha = enumerator.Current;
switch (alpha)
{
case 'A':
if (enumerator.MoveNext())
{
_nextChar = enumerator.Currrent;
}
else
{
// You decide what to do in this case.
}
break;
case 'B':
// etc.
break;
}
}
}
}
但是,这是给你的问题。此代码是否必须使用IEnumerable<char>
而不是IList<char>
?我问,因为,好像这不是很明显,如果您通过索引随机访问getAlphabet
返回的项目,代码会更简单(如果有人想要指出您可以使用ElementAt
,拜托,现在就把这个想法从头脑中解脱出来。)
我的意思是,考虑一下这种情况下的代码:
private char _nextChar;
private IList<char> getAlphabet()
{
return Array.AsReadOnly(new[] { 'A', 'B', 'C' });
}
public void sortAlphabet()
{
IList<char> alphabet = getAlphabet();
for (int i = 0; i < alphabet.Count - 1; ++i)
{
char alpha = alphabet[i];
switch (alpha)
{
case 'A':
_nextChar = alphabet[i + 1];
break;
case 'B':
// etc.
break;
}
}
}
不是那么容易吗?
答案 1 :(得分:4)
我估计你想要这个:
public void sortAlphabet() {
using (var enu = getAlphabet().GetEnumerator()) {
while (enu.MoveNext()) {
switch (enu.Current) {
case 'A':
enu.MoveNext();
_nextChar = enu.Current;
break;
}
}
}
}
请注意,如果我正确地阅读您的问题,这会消耗下一个元素。
答案 2 :(得分:1)
正如在另一个答案中指出的那样,是一个MoveNext()
方法,并且您可以通过调用返回的IEnumerator<T>
接口访问所有可用的枚举。 IEnumerable<T>.GetEnumerator()
。但是,使用MoveNext()
和Current
可能会感觉有点“低级别”。
如果你更喜欢foreach
循环来处理你的getAlphabet()
集合,你可以编写一个扩展方法,以任意两种方式返回任何可枚举的元素:
public static IEnumerable<T[]> InPairsOfTwo<T>(this IEnumerable<T> enumerable)
{
if (enumerable.Count() < 2) throw new ArgumentException("...");
T lastItem = default(T);
bool isNotFirstIteration = false;
foreach (T item in enumerable)
{
if (isNotFirstIteration)
{
yield return new T[] { lastItem, item };
}
else
{
isNotFirstIteration = true;
}
lastItem = item;
}
}
您可以按如下方式使用它:
foreach (char[] letterPair in getAlphabet().InPairsOfTwo())
{
char currentLetter = letterPair[0],
nextLetter = letterPair[1];
Console.WriteLine("# {0}, {1}", currentLetter, nextLetter);
}
你会得到以下输出:
# A, B
# B, C
(注意,虽然上面的扩展方法每个都返回两个项目对,但是重叠一个项目!你基本上得到每个项目以及前瞻。如果你想要扩展名方法返回最后一项,你可以通过调整使用的缓冲方法来调整它。)
答案 3 :(得分:0)
如前所述,状态机非常适合这些情况。 它也与我们对问题的思考方式相匹配,因此可读性非常出色。 我不确定你想要对代码做什么,所以下面的例子在遇到它时会返回下一个字符。状态机可以很好地扩展到复杂的任务,并且可以在纸上进行建模和手工检查。
enum State
{
Scan,
SaveAndExit
};
public void SortAlphabet()
{
State state = State.Scan; // initialize
foreach(char c in getAlphabet())
{
switch (state):
{
case State.Scan:
if (c == 'A' ||
c == 'B')
state = State.SaveAndExit;
break;
case State.SaveAndExit:
return (c);
break;
}
}
}
答案 4 :(得分:0)
您的代码每次都会返回'B'
,因为您致电getAlphabet()
,每次都会返回一个新的IEnumerable
。
根据您的尝试,我可能建议使用基于索引的迭代而不是枚举器。如果你使用MoveNext
获取下一个元素,那么你就会搞乱你的循环,所以使用基于索引的检索可以更加干净地工作,而且开销更少。
答案 5 :(得分:0)
如果您使用的是.NET 4.0,那么您要实现的目标非常简单:
var alphabet = getAlphabet();
var offByOneAlphabet = alphabet.Skip(1);
foreach (var pair in alphabet.Zip(offByOneAlphabet, (a, b) => Tuple.Create(a, b)))
Console.WriteLine("Letter: {0}, Next: {1}", pair.Item1, pair.Item2);
// prints:
// Letter: A, Next: B
// Letter: B, Next: C
如果你使用的东西少于.NET 4.0,它仍然很容易define your own Zip function和Tuple类。