Xóa nhiều mục khỏi bộ Python

Cấu trúc dữ liệu danh sách tích hợp sẵn của Python có nhiều phương thức mạnh mẽ mà bất kỳ lập trình viên Python nâng cao nào cũng phải quen thuộc. Tuy nhiên, một số thao tác trên danh sách không thể được thực hiện đơn giản bằng cách gọi đúng phương thức

Bạn có thể thêm một mục vào danh sách bằng cách sử dụng phương pháp

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
0 trên danh sách. Nếu bạn muốn thêm một danh sách các mục vào một danh sách khác, có phương pháp
indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
1 sẽ thực hiện công việc đó cho bạn

Điều tương tự cũng xảy ra nếu bạn muốn xóa một mục khỏi danh sách, bạn chỉ cần gọi phương thức

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
2 và bạn sẽ nhận được kết quả mong muốn

Tuy nhiên, bạn đã bao giờ tự hỏi làm cách nào để xóa danh sách các mục khỏi danh sách đã cho chưa?

Đây là những câu hỏi tôi đã tự hỏi mình trong một trong những dự án sở thích mới nhất của tôi. Vì vậy, tôi quyết định tìm ra cách Pythonic nhất để làm điều đó

Vấn đề

Hãy đóng khung vấn đề của chúng ta như thế này. Đưa ra một danh sách các mục Nhiệm vụ, làm cách nào chúng tôi có thể xóa tất cả các mục khỏi danh sách được đánh dấu là xong?

Hiện tại việc thực hiện trông như sau

class Task:
    def __init__(self, title):
        self.title = title
        self.done = False
        self.done_by = None
        
    def is_done(self):
        return self.done
    
    def set_done(self, name):
        self.done = True
        self.done_by = name
    
    def __repr__(self):
        state = f'was done by {self.done_by}' if self.done else 'is not done'
        s = f'Task: {self.title} {state}'
        return s
    
    
todo_list = [
    Task('Clean House'),
    Task('Walk Dog'),
    Task('Buy Bread'),
    Task('Repair Car'),
    Task('Plant Tree'),
    Task('Water Flowers'),
    Task('Bake Cake')
]


todo_list[0].set_done('Bob')
todo_list[2].set_done('Alice')
todo_list[5].set_done('Bob')

# print the whole list
print(todo_list)

Vì vậy, làm thế nào chúng ta có thể dọn sạch danh sách việc cần làm của mình để nó chỉ chứa những nhiệm vụ chưa được thực hiện?

Các giải pháp

Các giải pháp sau đây có thể được chia thành hai nhóm

  1. Xóa các phần tử của các chỉ số đã cho
  2. Xóa các phần tử theo một điều kiện nhất định

Bất kỳ giải pháp nào thuộc loại đầu tiên cũng có thể được sử dụng để xóa các phần tử theo một điều kiện nhất định. Để thực hiện điều này, tất cả những gì chúng ta phải làm là lặp lại một lần trên danh sách đầu vào, kiểm tra điều kiện và lưu trữ các chỉ số của các phần tử có điều kiện là

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
3. Điều này có thể được thực hiện như sau

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)

Vì phải mất một lần lặp lại danh sách để tìm các chỉ mục, điều này thêm O(n) vào độ phức tạp thời gian chạy. Tuy nhiên, vì bất kỳ nghiệm nào cũng có độ phức tạp thời gian ít nhất là O(n), nên chúng ta có thể bỏ qua bước đầu tiên này

Phương pháp 1. Xóa một mục khỏi danh sách và lặp lại trong một vòng lặp

Như đã đề cập trước đây, có các phương pháp để xóa một mục khỏi danh sách, theo giá trị hoặc theo chỉ mục

Do đó, một giải pháp để loại bỏ một số mục là sử dụng phương pháp loại bỏ một mục và thực hiện nó trong một vòng lặp. Mặc dù, có một cạm bẫy đối với giải pháp này. Sau khi chúng tôi xóa phần tử ở chỉ mục 0, tất cả các phần tử khác sẽ dịch chuyển và chỉ số của chúng thay đổi vì phần tử ở chỉ mục 1 hiện đang ở chỉ mục 0, v.v.

Đây là cách giải pháp sẽ trông giống như mã

1. 1. Xóa bằng pop()

Phương thức

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
4 loại bỏ và trả về phần tử cuối cùng từ một
indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
5 hiện có. Phương thức
indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
6 với đối số tùy chọn
indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
7 loại bỏ và trả về phần tử ở vị trí
indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
7

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1

Chà, có lẽ điều này có vẻ hơi khó xử đối với bạn và hãy yên tâm, đó không phải là cách bạn sẽ làm trong Python

Để tránh dịch chuyển, chúng ta có thể sắp xếp đảo ngược danh sách các chỉ số để chúng ta có thể loại bỏ các mục từ đầu đến cuối

indices = [0, 2, 5]
for i in sorted(indices, reverse=True):
    todo_list.pop(i)

1. 2. Xóa bằng cách xóa ()

Một giải pháp đơn giản hơn một chút, nhưng vẫn không phải là giải pháp tốt nhất, sử dụng phương pháp

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
9
indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
0

Chúng tôi lặp lại danh sách và kiểm tra từng mục nếu nó thỏa mãn điều kiện để có thể xóa nó. Giải pháp này sẽ trông như thế này

for task in todo_list:
    if task.is_done():
        todo_list.remove(task)

Hãy cẩn thận nếu bạn sử dụng

indices = []
for idx, task in enumerate(todo_list):
    if task.is_done():
        indices.append(idx)
2 trong danh sách các kiểu dữ liệu đơn giản như số nguyên. Hàm
indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
2 xóa lần xuất hiện đầu tiên của giá trị đã cho khỏi danh sách

Trong tất cả các giải pháp trên, chúng tôi đã thực hiện xóa tại chỗ, có nghĩa là chúng tôi đã giữ phiên bản ban đầu của danh sách

Bây giờ bạn sẽ thấy, một giải pháp tốt cho vấn đề không quá rõ ràng

1. 3. Xóa bằng cách sử dụng itemgetter() và remove()

Nếu bạn sử dụng hàm

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
3 từ mô-đun
indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
4 thì có một giải pháp thú vị khác về cơ bản là cải tiến của giải pháp 1. 1

Hàm

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
3 nhận vào một số lượng chỉ mục tùy ý và trả về tất cả các phần tử từ các chỉ mục đó trong một bộ. Đây là việc thực hiện các giải pháp đề xuất

from operator import itemgetter

indices = [0, 2, 5]
for item in (itemgetter(*idx)(todo_list)):
    xs.remove(item)

Tuy nhiên, mã phức tạp hơn mức cần thiết

Phương pháp 2. Xóa nhiều mục khỏi danh sách

Trong các giải pháp trước, chúng tôi chỉ đơn giản điều chỉnh chức năng xóa một phần tử để có thể sử dụng nó bên trong một vòng lặp. Trong phần này, chúng ta xem xét thêm các giải pháp Pythonic cho vấn đề

2. 1. Xóa tất cả các phần tử khỏi danh sách

Nếu bạn muốn xóa tất cả các phần tử khỏi danh sách, có một giải pháp rất đơn giản. Sử dụng phương thức của lớp danh sách

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
6. Nó xóa tất cả các phần tử khỏi danh sách tại chỗ

2. 2. Xóa một lát khỏi danh sách

Nếu các phần tử của bạn nằm trong một phạm vi liên tục hoặc nếu chúng có khoảng cách ít nhất bằng nhau với nhau thì một cách đơn giản để xóa nhiều phần tử khỏi danh sách là sử dụng từ khóa

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
7 cùng với việc cắt

Điều này có thể trông như thế này

del todo_list[1::2]

Nó xóa các phần tử tại chỗ, tuy nhiên, sẽ không hữu ích nếu chúng tôi muốn xóa các phần tử được phân phối ngẫu nhiên khỏi danh sách của mình

2. 3. Xóa các phần tử được phân phối ngẫu nhiên khỏi danh sách bằng các thao tác thiết lập

Đầu tiên, chúng tôi lặp lại danh sách một lần và trích xuất tất cả các mục sẽ bị xóa. Sau đó, chúng tôi chuyển đổi cả hai danh sách thành tập hợp và thực hiện xóa bằng thao tác tập hợp. Điều này trông như sau

done = []
for task in todo_list:
    if task.is_done():
        done.append(task)
        
todo_list = list(set(todo_list) - set(done))

Tóm lại, set trong Python là một hashmap cho phép thực hiện một số thao tác nhất định trên set rất nhanh (O(1)). Thật không may, chúng tôi phải chuyển đổi từ một danh sách sang một tập hợp và ngược lại, do đó chúng tôi mất lợi thế về tốc độ. Và một lần nữa, chúng tôi kết thúc với một giải pháp O(n)

Để biết thêm thông tin về độ phức tạp tính toán của các phép toán Python, hãy xem bài viết chi tiết của chúng tôi về chủ đề này

Giải pháp này không hoạt động tại chỗ và hơi khó đọc do có nhiều chuyển đổi giữa các cấu trúc dữ liệu

2. 4. Xóa các phần tử được phân phối ngẫu nhiên khỏi danh sách bằng cách sử dụng tính năng hiểu danh sách

Cách tốt nhất để làm điều này trong Python thực sự rất gần với những gì chúng ta đã thấy trong phần đầu tiên của bài viết này, nơi chúng ta lặp lại danh sách và loại bỏ các phần tử có một điều kiện nhất định là Đúng

Tuy nhiên, trong giải pháp này, chúng tôi sẽ tiến hành theo cách khác. Chúng tôi lặp lại danh sách cũ và tạo một danh sách mới mà chúng tôi thêm tất cả các thành phần mà chúng tôi muốn giữ lại. Rõ ràng, chúng ta phải tạo một danh sách mới để đạt được điều này, vì vậy giải pháp sẽ không hoạt động tại chỗ

Python chỉ cung cấp những gì chúng ta cần để có được kết quả mong muốn trong một dòng mã. hiểu danh sách

todo_list = [task for task in todo_list if not task.is_done()]

Nếu chúng ta gán kết quả của việc hiểu danh sách trở lại biến

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
8 ban đầu của chúng ta, biến này sẽ trỏ đến một danh sách chỉ chứa các tác vụ chưa hoàn thành

Sau dòng mã trên, địa chỉ bộ nhớ mà biến

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
8 điểm đã thay đổi

Tuy nhiên, đó là cách bạn nên xóa một số phần tử khỏi danh sách trong Python. Tuy nhiên, nếu bạn muốn thực hiện việc này tại chỗ, cũng có một giải pháp một dòng cho vấn đề, cá nhân tôi không khuyên bạn nên sử dụng giải pháp này

Đây là mã

[todo_list.remove(task) for task in todo_list if task.is_done()]

Thành thật mà nói, bạn đã mất bao lâu để hiểu được điều đó?

Chúng tôi sử dụng cách hiểu danh sách giả trong đó chúng tôi xóa các phần tử đã chọn khỏi danh sách ban đầu, cuối cùng chúng tôi loại bỏ danh sách kết quả của việc hiểu danh sách

Vì vậy, những gì chúng tôi thực sự làm là lạm dụng khả năng hiểu danh sách để lặp lại

indices = [0, 2, 5] # must be ordered!
shift = 0
for i in indices:
    todo_list.pop(i-shift)
    shift += 1
8 và xóa các mục khỏi nó

Phần kết luận

Tùy thuộc vào sự phân bổ của các mục trong danh sách, có các giải pháp khác nhau

  1. Nếu bạn muốn xóa tất cả các phần tử khỏi danh sách, hãy sử dụng phương thức của danh sách
    indices = [0, 2, 5] # must be ordered!
    shift = 0
    for i in indices:
        todo_list.pop(i-shift)
        shift += 1
    6
  2. Nếu bạn muốn xóa một phạm vi liên tục khỏi danh sách hoặc nếu bạn muốn xóa các mục có khoảng cách bằng nhau giữa chúng, hãy sử dụng phép cắt với toán tử
    indices = [0, 2, 5]
    for i in sorted(indices, reverse=True):
        todo_list.pop(i)
    2
  3. Nếu bạn muốn xóa các phần tử được phân phối ngẫu nhiên, hãy sử dụng cách hiểu danh sách chỉ chọn các phần tử bạn muốn giữ lại – đây là giải pháp tôi khuyên dùng

Rõ ràng, có nhiều khả năng hơn để giải quyết vấn đề, tuy nhiên, các giải pháp được trình bày trong bài viết này là những giải pháp phổ biến nhất và cũng dễ hiểu nhất. Nếu bạn tìm thấy một giải pháp tuyệt vời khác, vui lòng liên hệ với chúng tôi. Chúng tôi rất thích nhìn thấy nó

Đi đâu từ đây?

Lý thuyết đủ rồi, bắt tay vào thực hành nào

Để thành công trong lĩnh vực mã hóa, bạn cần phải ra khỏi đó và giải quyết các vấn đề thực sự cho người thực. Đó là cách bạn có thể dễ dàng trở thành người có thu nhập sáu con số. Và đó là cách bạn trau dồi những kỹ năng bạn thực sự cần trong thực tế. Rốt cuộc, việc sử dụng lý thuyết học tập mà không ai cần là gì?

Các dự án thực hành là cách bạn mài sắc lưỡi cưa của mình trong viết mã

Bạn có muốn trở thành bậc thầy viết mã bằng cách tập trung vào các dự án mã thực tế giúp bạn kiếm tiền và giải quyết vấn đề cho mọi người không?

Sau đó trở thành nhà phát triển tự do Python. Đó là cách tốt nhất để tiếp cận nhiệm vụ cải thiện kỹ năng Python của bạn—ngay cả khi bạn là người mới hoàn toàn

Tham gia hội thảo trên web miễn phí của tôi “Cách xây dựng kỹ năng Python có thu nhập cao của bạn” và xem cách tôi phát triển công việc kinh doanh mã hóa trực tuyến của mình cũng như cách bạn có thể làm được—từ sự thoải mái tại nhà riêng của bạn