2011-11-08 12 views
6

Tôi có một lớp được gọi là GenericPermutations vừa là đếm được và vừa là điều tra viên. Công việc của nó là lấy một danh sách thứ tự các đối tượng và lặp qua từng hoán vị của chúng theo thứ tự.C# Lớp là IEnumerable AND IEnumerator cùng một lúc. Các vấn đề với điều này là gì?

Ví dụ, một số nguyên implemenation của lớp này có thể lặp qua như sau:

GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 }); 
p.nextPermutation(); // 123 
p.nextPermutation(); // 132 
p.nextPermutation(); // 213 
// etc. 

Vì vậy, đếm được của nó theo nghĩa là nó chứa một 'danh sách' điều bạn có thể liệt kê trên. Nó cũng là một điều tra viên, bởi vì công việc của nó liên quan đến việc tìm kiếm hoán vị tiếp theo.

ISSUE: Tôi hiện đang cố gắng tích hợp IEnumerator và IEnumerable với lớp này, và có vẻ như tôi thích cả hai (thay vì sử dụng lớp phụ như IEnumerable). Vì vậy, đến nay tôi đã tránh được vấn đề với cố gắng để có được hai điều tra từ nó bằng cách đi qua một đối tượng GenericPermutation mới trong phương pháp GetEnumerator.

Đây có phải là ý tưởng tồi không? Bất cứ điều gì khác tôi nên xem xét?

+0

Nếu trường hợp liệt kê của bạn cũng là một ví dụ cho tất cả các điều tra viên được yêu cầu, bạn sẽ giữ trạng thái giữa chúng như thế nào? –

+0

Ý tưởng của tôi là tôi sẽ tạo một bản sao mới của đối tượng của mình mỗi lần điều tra được yêu cầu và chuyển bản sao đó làm điều tra viên. Vì vậy, nếu một điều tra viên mới được yêu cầu, một bản sao của điều tra viên hiện tại trong tình trạng ban đầu của nó được thông qua. – jtfairbank

Trả lời

8

Giảm sự nhầm lẫn của bạn (?) Bằng cách sử dụng các phiên bản chung của IEnumerableIEnumerator.

Một hoán vị được liệt kê là IEnumerable<IEnumerable<T>>. Vì vậy, bạn có thể có một cái gì đó giống như

IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence) 
{ 
    return new Permuter<T>(sequence); 
} 

public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... } 

Hơn nữa, tôi đã nhìn thấy nhiều hơn một trường hợp một loại duy nhất thực hiện cả hai IEnumerable<T>IEnumerator<T>; phương pháp GetEnumerator của nó chỉ đơn giản là return this;. Tôi nghĩ rằng một loại như vậy sẽ cần phải là một cấu trúc, bởi vì nếu nó là một lớp bạn sẽ có tất cả các loại vấn đề nếu bạn gọi GetEnumerator() một lần thứ hai trước khi điều tra đầu tiên được hoàn thành.

EDIT: Tiêu thụ các permuter

var permuter = GetPermutations(sequence); 
foreach (var permutation in permuter) 
{ 
    foreach (var item in permutation) 
     Console.Write(item + "; "); 
    Console.WriteLine(); 
} 

Giả sử chuỗi đầu vào là {1, 2, 3}, đầu ra là

1; 2; 3; 
1; 3; 2; 
2; 1; 3; 
2; 3; 1; 
3; 1; 2; 
3; 2; 1; 

EDIT:

Dưới đây là một siêu hiệu quả triển khai để minh họa đề xuất:

public class Permuter<T> : IEnumerable<IEnumerable<T>> 
{ 
    private readonly IEnumerable<T> _sequence; 

    public Permuter(IEnumerable<T> sequence) 
    { 
     _sequence = sequence; 
    } 

    public IEnumerator<IEnumerable<T>> GetEnumerator() 
    { 
     foreach(var item in _sequence) 
     { 
      var remaining = _sequence.Except(Enumerable.Repeat(item, 1)); 
      foreach (var permutation in new Permuter<T>(remaining)) 
       yield return Enumerable.Repeat(item, 1).Concat(permutation); 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 
+0

Tôi không thích chỉ truyền 'this' trong phương thức GetEnumerator vì có thể có các vấn đề trong lồng nhau cho mỗi vòng lặp và các tình huống khác. Đề xuất đầu tiên của bạn trông thú vị mặc dù, bạn có thể mở rộng nó nhiều hơn một chút? Tôi bị nhầm lẫn bởi «IEnumerable >'. – jtfairbank

+1

Bạn đang bối rối về điều gì? Các hoán vị của chuỗi {1, 2, 3} bao gồm 6 trình tự ({1, 2, 3}, {1, 3, 2}, v.v.); điều này có thể được coi là một chuỗi các chuỗi. Tôi sẽ thêm một ví dụ mã. – phoog

+0

Ah Tôi hiểu ý của bạn là gì. Tuy nhiên, tôi không bao giờ phải liệt kê các trình tự, chỉ là các hoán vị tạo ra chúng. – jtfairbank

0

Có thể cho một đối tượng cư xử như là cả hai IEnumerator<T>IEnumerable<T>, nhưng thường khó có thể làm một đối tượng theo cách như vậy để tránh ngữ nghĩa kỳ quặc; trừ khi IEnumerator<T> sẽ trở thành trạng thái không trạng thái (ví dụ: một số đếm trống, trong đó MoveNext() luôn trả về false hoặc số đếm lặp lại vô tận, trong đó MoveNext() không có gì nhưng luôn trả về true và Current luôn trả về cùng một giá trị), mỗi cuộc gọi đến GetEnumerator() phải trả về một cá thể đối tượng riêng biệt và có thể có ít giá trị trong trường hợp đó thực hiện IEnumerable<T>.

Có một loại giá trị thực hiện IEnumerable<T>IEnumerator<T>, và có GetEnumerator() phương pháp của nó trở lại this, sẽ đáp ứng các yêu cầu rằng mỗi cuộc gọi đến GetEnumerator trở lại một trường hợp đối tượng riêng biệt, nhưng có kiểu giá trị thực hiện các giao diện có thể thay đổi nói chung là nguy hiểm. Nếu một loại giá trị được đóng hộp thành IEnuerator<T> và không bao giờ được mở hộp, nó sẽ hoạt động như một đối tượng kiểu lớp, nhưng không có lý do thực sự nào tại sao nó không phải đơn giản là đối tượng kiểu lớp.

Bộ lặp trong C# được triển khai như đối tượng lớp thực hiện cả hai IEnumerable<T>IEnumerator<T>, nhưng chúng bao gồm một chút logic hợp lý để đảm bảo tính chính xác ngữ nghĩa. Hiệu ứng ròng là có một đối tượng thực hiện cả hai giao diện cung cấp một cải tiến nhỏ về hiệu năng, để đổi lấy một chút phức tạp trong mã được tạo ra, và một số quirkiness ngữ nghĩa trong hành vi IDisposable của chúng. Tôi sẽ không đề nghị phương pháp này trong bất kỳ mã nào cần phải có thể đọc được; vì các lớp IEnumerator<T>IEnumerable<T> của lớp chủ yếu sử dụng các trường khác nhau và vì lớp kết hợp cần phải có trường "thread-id", không cần thiết nếu sử dụng các lớp riêng biệt, có thể đạt được hiệu suất cải thiện hiệu suất bằng cách sử dụng đối tượng để thực hiện cho cả hai giao diện bị hạn chế. Đáng làm có lẽ nếu thêm sự phức tạp vào trình biên dịch sẽ cung cấp cải thiện hiệu suất nhỏ cho hàng triệu thói quen lặp, nhưng không đáng làm để cải thiện hiệu năng của một thường trình.