2010-08-28 27 views
5

Tôi đang gặp khó khăn với một vấn đề dường như dễ dàng và đáng xấu hổ. Tất cả những gì tôi muốn là phần tử tiếp theo trong IEnumberable mà không cần sử dụng Skip (1) .Take (1) .Single(). Ví dụ này minh họa vấn đề cơ bản.Thay thế cho IEnumerable <T> .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; 
     } 
    } 
} 

Khác hơn là xấu xí, ví dụ này hoạt động. Nhưng chúng ta hãy nói rằng IEnumerable chứa 2 triệu phần tử, sau đó câu lệnh LINQ làm cho chương trình thực thi không chậm chạp. Những gì tôi muốn là đơn giản. Tôi chỉ muốn phần tử tiếp theo trong một IEnumberable <>. Tất cả các vấn đề của tôi sẽ được giải quyết nếu có một chức năng như:

_nextChar = getAlphabet().moveNext() //or getNext() 

Nó được nhiều ưu đãi nếu các giải pháp giữ cùng cấu trúc/layout/chức năng của ví dụ tuy nhiên, tôi linh hoạt. Chương trình của tôi là trình phân tích cú pháp tệp và trong số 2 triệu dòng văn bản là một số khóa như "tiền = 324" trong đó "tiền" và "324" là các yếu tố hàng xóm trong IEnumberable và khi trình phân tích cú pháp xuất hiện "tiền" tôi muốn " 324 ". (Ai không:?. D Xin lỗi vì chơi chữ xấu)

+0

Điều này nghe giống như trường hợp FSM. – Necros

+2

Mã này sẽ luôn truy lục phần tử thứ hai của chuỗi, tức là 'B', tôi không nghĩ đó là những gì bạn muốn ... –

+0

Câu trả lời được bình chọn hàng đầu đã trả lời câu hỏi của bạn. Tuy nhiên, để tham khảo trong tương lai, thay vì '.Take (1) .Single()' bạn có thể sử dụng '.irst()' làm điều tương tự. – Timwi

Trả lời

13

Tất cả các vấn đề của tôi sẽ được giải quyết nếu có một chức năng như:

_nextChar = getAlphabet().moveNext() //or getNext()

Có một chức năng chính xác như thế. Nó chỉ thuộc về IEnumerator<T>, không phải là 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; 
      } 
     } 
    } 
} 

Đây là câu hỏi dành cho bạn. Có cần phải sử dụng mã này IEnumerable<char>, chứ không phải là IList<char>? Tôi hỏi vì, như thể điều này không rõ ràng, mã sẽ đơn giản hơn nhiều nếu bạn có quyền truy cập ngẫu nhiên vào các mục được trả về bởi getAlphabet theo chỉ mục (và nếu ai đó bị cám dỗ chỉ ra rằng bạn có thể làm điều này với ElementAt, vui lòng, chỉ cần lấy ý tưởng đó ra khỏi đầu của bạn ngay bây giờ).

Ý tôi là, xem xét những gì mã sẽ như thế nào trong trường hợp này:

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; 
     } 
    } 
} 

Mà không phải là dễ dàng hơn nhiều?

+0

Câu trả lời đầu tiên của bạn là chính xác những gì tôi đang tìm kiếm (đôi khi một cái nhìn mới về một cái gì đó là những gì người ta đang tìm kiếm). Tuy nhiên, tôi hơi bối rối bởi gợi ý của bạn. Tôi không muốn truy cập ngẫu nhiên. Tôi muốn, trong trường hợp này, các chữ cái khi chúng đến. Và, tôi có thể sai, không phải là getAlphabet() giữ toàn bộ nội dung trong bộ nhớ? Tôi không muốn điều đó, như trong trường hợp của tôi getAlphabet() sẽ giữ 2 triệu dòng. –

+0

Ah Tôi mới nhận ra rằng có thể có một số nhầm lẫn. Điều bảng chữ cái chỉ là một ví dụ về vấn đề của tôi. Trong thực tế tôi không làm bất cứ điều gì với bảng chữ cái hoặc char cho vấn đề đó. Nếu muốn tôi có thể cung cấp mã có liên quan, nhưng bạn đã trả lời câu hỏi của tôi. –

+0

@Nick: Không cần làm rõ thêm. Ngay cả sau khi gửi đề xuất của tôi tôi nhớ rằng bạn đã nói 2 triệu mặt hàng; vì vậy lưu trữ nội dung trong bộ nhớ sẽ không có ý nghĩa trong trường hợp của bạn. Mặc dù vậy, tôi để ý tưởng ở đó, trong trường hợp bất kỳ ai khác tìm thấy anh ta/mình phải đối mặt với một vấn đề tương tự trong một kịch bản mà việc truy cập vào các thành viên của bộ sưu tập theo chỉ mục có thể thực sự là một lựa chọn. –

4

Tôi nghĩ bạn muốn điều này:

public void sortAlphabet() { 
     using (var enu = getAlphabet().GetEnumerator()) { 
      while (enu.MoveNext()) { 
       switch (enu.Current) { 
        case 'A': 
         enu.MoveNext(); 
         _nextChar = enu.Current; 
         break; 
       } 
      } 
     } 
    } 

Lưu ý rằng điều này tiêu thụ các phần tử tiếp theo, chỉ là những gì bạn muốn nếu tôi đọc câu hỏi của bạn ngay.

+1

Thực sự nên sử dụng 'using' xung quanh' GetEnumerator' ... – Timwi

+1

Có, bo mạch chủ sẽ bắt lửa nếu bạn quên điều đó. –

+2

Luôn luôn. Giao diện IEnumerable <> chung kế thừa từ IDisposable. –

1

Như đã chỉ ra trong câu trả lời khác, có một phương pháp MoveNext(), và bạn có quyền truy cập vào nó cho tất cả enumerables thông qua giao diện IEnumerator<T> trả về bởi một cuộc gọi đến IEnumerable<T>.GetEnumerator(). Tuy nhiên, làm việc với MoveNext()Current có thể cảm thấy phần nào "mức thấp".

Nếu bạn muốn một vòng lặp foreach để xử lý bộ sưu tập getAlphabet() của bạn, bạn có thể viết một phương pháp mở rộng trả về các yếu tố từ bất kỳ thể liệt kê trong cặp hai:

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; 
    } 
} 

Bạn muốn sử dụng nó như sau:

foreach (char[] letterPair in getAlphabet().InPairsOfTwo()) 
{ 
    char currentLetter = letterPair[0], 
     nextLetter = letterPair[1];   

    Console.WriteLine("# {0}, {1}", currentLetter, nextLetter); 
} 

Và bạn sẽ nhận được kết quả như sau:

# A, B 
# B, C 

(Lưu ý rằng mặc dù phương pháp mở rộng ở trên trả về hai cặp mỗi cặp, các cặp trùng lặp bởi một mục! Bạn về cơ bản có được mỗi mục cũng như nhìn về phía trước. Nếu bạn muốn phương thức mở rộng tự trả lại mục cuối cùng, bạn có thể điều chỉnh nó bằng cách điều chỉnh phương thức đệm được sử dụng.)

0

Như đã nói ở trên, máy trạng thái là lý tưởng cho những trường hợp này. Nó cũng phù hợp với cách chúng ta nghĩ về vấn đề, vì vậy khả năng đọc là tuyệt vời. Tôi không chắc chắn chính xác những gì bạn muốn làm với mã, vì vậy ví dụ bên dưới trả về ký tự tiếp theo khi nó gặp phải. Máy trạng thái cân đối tốt với các nhiệm vụ phức tạp và có thể được lập mô hình và kiểm tra thủ công trên giấy.

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; 
     } 
    } 
} 
0

Mã của bạn sẽ trở lại 'B' mọi thời gian, bởi vì bạn gọi getAlphabet(), mà trả về một mới IEnumerable mỗi lần.

Dựa trên những gì bạn đang cố gắng làm, tôi có thể đề xuất sử dụng lặp dựa trên chỉ mục thay vì một điều tra viên. Nếu bạn đã sử dụng MoveNext để nhận phần tử tiếp theo, bạn sẽ làm hỏng vòng lặp của mình, do đó, việc sử dụng truy xuất dựa trên chỉ mục sẽ hoạt động hiệu quả hơn với chi phí thấp hơn nhiều.

0

Nếu bạn đang sử dụng .NET 4.0 thì những gì bạn đang cố gắng để đạt được là rất đơn giản:

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 

Nếu bạn sử dụng bất cứ điều gì ít hơn .NET 4.0, nó vẫn rất dễ dàng để define your own Zip function và lớp tuple .

Các vấn đề liên quan