2010-02-23 34 views
27

phép nói rằng tôi có một chức năng máy phát điện mà trông như thế này:Index và Slice một Generator bằng Python

def fib(): 
    x,y = 1,1 
    while True: 
     x, y = y, x+y 
     yield x 

Lý tưởng nhất, tôi chỉ có thể sử dụng fib() [10] hoặc fib() [2 : 12: 2] để lấy chỉ mục và lát, nhưng hiện tại tôi phải sử dụng itertools cho những thứ này. Tôi không thể sử dụng máy phát điện để thay thế danh sách thả xuống.

tôi tin rằng các giải pháp sẽ được quấn fib() trong một lớp học:

class Indexable(object): 
    .... 

fib_seq = Indexable(fib()) 

gì nên Indexable nhìn muốn thực hiện công việc này?

Trả lời

33
import itertools 

class Indexable(object): 
    def __init__(self,it): 
     self.it = iter(it) 
    def __iter__(self): 
     return self.it 
    def __getitem__(self,index): 
     try: 
      return next(itertools.islice(self.it,index,index+1)) 
     except TypeError: 
      return list(itertools.islice(self.it,index.start,index.stop,index.step)) 

Bạn có thể sử dụng nó như thế này:

it = Indexable(fib()) 
print(it[10]) 
#144 
print(it[2:12:2]) 
#[610, 1597, 4181, 10946, 28657] 

ý rằng it[2:12:2] không trả lại [3, 8, 21, 55, 144] kể từ khi iterator đã tiến 11 yếu tố vì cuộc gọi đến it[10].

Edit: Nếu bạn muốn it[2:12:2] trở [3, 8, 21, 55, 144] thì có lẽ sử dụng này để thay thế:

class Indexable(object): 

    def __init__(self, it): 
     self.it = iter(it) 
     self.already_computed = [] 

    def __iter__(self): 
     for elt in self.it: 
      self.already_computed.append(elt) 
      yield elt 

    def __getitem__(self, index): 
     try: 
      max_idx = index.stop 
     except AttributeError: 
      max_idx = index 
     n = max_idx - len(self.already_computed) + 1 
     if n > 0: 
      self.already_computed.extend(itertools.islice(self.it, n)) 
     return self.already_computed[index] 

phiên bản này giúp tiết kiệm các kết quả trong self.already_computed và sử dụng những kết quả nếu có thể. Nếu không, nó tính toán nhiều kết quả hơn cho đến khi nó có đủ số để trả về phần tử hoặc lát được lập chỉ mục.

+0

Triển lãm các hành vi tương tự như một danh sách, __getitem__ sẽ cần phải tua máy phát điện. Có cách nào dễ dàng để làm điều đó không? –

+0

Tôi không biết nếu có cách nào để làm điều đó, dễ dàng hay không, nhưng lớp Indexable chỉ có thể lưu trữ tất cả các mục đã tạo ra trong một danh sách. Sau đó '__getitem__' sẽ kéo các số trực tiếp từ danh sách, sau khi đầu tiên tiến hành bộ tạo nếu cần thiết. – MatrixFrog

+0

Cảm ơn MatrixFrog; đó là những gì tôi đã làm. – unutbu

0

Vì vậy, dựa trên mã từ ~ unutbu và thêm vào một chút itertools.tee:

import itertools 

class Indexable(object): 
    def __init__(self, it): 
     self.it = it 

    def __iter__(self): 
     self.it, cpy = itertools.tee(self.it) 
     return cpy 

    def __getitem__(self, index): 
     self.it, cpy = itertools.tee(self.it) 
     if type(index) is slice: 
      return list(itertools.islice(cpy, index.start, index.stop, index.step)) 
     else: 
      return next(itertools.islice(cpy, index, index+1)) 
+0

Phải, nhưng tùy chọn này sẽ đốt cháy CPU mỗi lần bạn sử dụng nó. Đối với các danh sách quá dài để lưu trữ trong ~ unutbu's 'already_computed', thời gian CPU được yêu cầu ở đây có thể là quá mức. –

+0

Tôi nghĩ rằng nó cũng có thể đi xuống đến sự phức tạp của máy phát điện, nếu mã bên trong máy phát điện là CPU nặng bằng cách sử dụng has_computed là lựa chọn tốt nhất nhưng có lẽ nếu nó là đơn giản như xrange chỉ recomputing nó là tốt hơn. –

+0

Không itertools.tee nội bộ chỉ lưu trữ kết quả để nó có thể phát lại chúng sau này? Nó không có cách nào để biết liệu nó có thể sao chép trình lặp hay không; xem xét một iterator qua các yêu cầu mạng đến, hoặc một cái gì đó. – Ben

0

Nếu đó là một lát 1 sử dụng hơn bạn chỉ có thể sử dụng phương pháp được viết bởi ~ unutbu. Nếu bạn cần cắt nhiều lần, bạn sẽ phải lưu trữ tất cả các giá trị trung gian để bạn có thể "tua lại" trình lặp. Vì các trình lặp có thể lặp lại bất cứ thứ gì, nó sẽ không có phương thức tua lại theo mặc định.

Ngoài ra, vì một iterator tua có để lưu trữ tất cả các kết quả trung gian nó sẽ (trong hầu hết các trường hợp) không có lợi ích hơn chỉ đơn giản là làm list(iterator)

Về cơ bản ... bạn hoặc là không cần một iterator, hoặc bạn' không đủ cụ thể về tình hình.

+3

'danh sách (fib())' sẽ cung cấp cho bạn một MemoryError cuối cùng –

+0

Làm thế nào về trường hợp mà tôi muốn số lượng 6.000. Cách rõ ràng nhất để diễn đạt điều này là fib [6000] mặc dù có một số cách để có được nó, chúng không được thể hiện một cách tao nhã. –

+0

Quan điểm của tôi là, trong gần như tất cả các tình huống thực tế, bạn sẽ làm cho đối tượng có thể cắt lát thay vì thực hiện cắt tại máy phát. Nếu không, bạn sẽ không thể tối ưu hóa chức năng gọi đúng cách. Đối với bất kỳ số lượng lớn số lượng lớn bạn sẽ không muốn tạo ra toàn bộ danh sách chỉ để có được một số, bạn sẽ sử dụng một phương pháp để "đoán" số. – Wolph

0

Đây là câu trả lời của ~ unutbu được sửa đổi thành danh sách phân lớp. Rõ ràng là lạm dụng như append, insert v.v. sẽ tạo ra kết quả lạ!

bạn nhận được __str____repr__ phương pháp miễn phí mặc dù

import itertools 
class Indexable(list): 
    def __init__(self,it): 
     self.it=it 
    def __iter__(self): 
     for elt in self.it: 
      yield elt 
    def __getitem__(self,index): 
     try: 
      max_idx=index.stop 
     except AttributeError: 
      max_idx=index 
     while max_idx>=len(self): 
      self.append(next(self.it)) 
     return list.__getitem__(self,index) 
+1

Danh sách quá khác so với những gì chúng tôi muốn để điều này hữu ích. Đừng làm thế. – fletom

+0

Ok, nhưng ai là "_we_"? –

+3

Những người có vấn đề này, tôi cho là vậy. – fletom

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