Python: выберите случайную строку из файла, затем удалите эту строку

Я новичок в Python (в том, что я изучил его через курс CodeAcademy) и мог бы помочь с определением этого.

У меня есть файл 'TestingDeleteLines.txt', который содержит около 300 строк текста. Прямо сейчас я пытаюсь заставить его напечатать мне 10 случайных строк из этого файла, а затем удалить эти строки.

Так что, если мой файл имеет 10 строк:

Carrot
Banana
Strawberry
Canteloupe
Blueberry
Snacks
Apple
Raspberry
Papaya
Watermelon

Мне нужно, чтобы он случайно выбрал эти строки, сказал мне, что они случайно выбрали чернику, морковь, арбуз и банан, а затем удалил эти строки.

Проблема в том, что когда Python читает файл, он читает этот файл, и как только он доходит до конца, он не возвращается и не удаляет строки. В настоящее время я думал, что могу написать строки в список, затем снова открыть файл, сопоставить список с текстовым файлом и, если он найдет совпадение, удалить строки.

Моя текущая проблема состоит из двух частей:

  1. Это дублирование случайных элементов. Если он выбирает линию, мне нужно, чтобы она не выбиралась снова. Однако использование random.sample, похоже, не работает, так как мне нужно, чтобы эти строки выделялись, когда я позже использую каждую строку для добавления в URL.
  2. Я не чувствую, что моя логика (написать в array-> найти совпадения в тексте file-> удалить) - самая идеальная логика. Есть ли лучший способ написать это?

    import webbrowser
    import random
    
    """url= 'http://www.google.com'
    webbrowser.open_new_tab(url+myline)""" Eventually, I need a base URL + my 10 random lines opening in each new tab
    
    def ShowMeTheRandoms():
        x=1
        DeleteList= []
        lines=open('TestingDeleteLines.txt').read().splitlines()
    for x in range(0,10):
        myline=random.choice(lines)
        print(myline) """debugging, remove later"""
        DeleteList.append(myline)
        x=x+1
        print DeleteList """debugging, remove later"""
    ShowMeTheRandoms()
    

Ответ 1

У меня есть файл TestingDeleteLines.txt, который содержит около 300 строк текста. Прямо сейчас, я пытаюсь заставить его напечатать мне 10 случайных строк из этого файла, а затем удалить эти строки.

#!/usr/bin/env python
import random

k = 10
filename = 'TestingDeleteLines.txt'
with open(filename) as file:
    lines = file.read().splitlines()

if len(lines) > k:
    random_lines = random.sample(lines, k)
    print("\n".join(random_lines)) # print random lines

    with open(filename, 'w') as output_file:
        output_file.writelines(line + "\n"
                               for line in lines if line not in random_lines)
elif lines: # file is too small
    print("\n".join(lines)) # print all lines
    with open(filename, 'wb', 0): # empty the file
        pass

Это алгоритм O(n**2), который может быть улучшен, если это необходимо (вам это не нужно для крошечного файла, такого как ваш ввод)

Ответ 2

Точка: вы не "удаляете" из файла, а переписываете весь файл (или другой) с новым контентом. Канонический способ состоит в том, чтобы прочитать исходный файл по строкам, записать строки, которые вы хотите сохранить во временный файл, а затем заменить старый файл на новый.

with open("/path/to/source.txt") as src, open("/path/to/temp.txt", "w") as dest:
    for line in src:
        if should_we_keep_this_line(line):
            dest.write(line)
os.rename("/path/to/temp.txt", "/path/to/source.txt")

Ответ 3

Как насчет list.pop - он дает вам элемент и обновляет список за один шаг.

lines = readlines()
deleted = []

indices_to_delete = random.sample(xrange(len(lines)), 10)

# sort to delete biggest index first 
indices_to_delete.sort(reverse=True)

for i in indices_to_delete:
    # lines.pop(i) delete item at index i and return the item
    # do you need it or its index in the original file than
    deleted.append((i, lines.pop(i)))

# write the updated *lines* back to the file or new file ?!
# and you have everything in deleted if you need it again

Ответ 4

Предположим, что у вас есть список строк из вашего файла, хранящихся в items

>>> items = ['a', 'b', 'c', 'd', 'e', 'f']
>>> choices = random.sample(items, 2)  # select 2 items
>>> choices  # here are the two
['b', 'c']
>>> for i in choices:
...   items.remove(i)
...
>>> items  # tee daa, no more b or c
['a', 'd', 'e', 'f']

Здесь вы должны перезаписать свой предыдущий текстовый файл с содержимым items, соединяющим с вашей предпочтительной строкой \r\n или\n. readlines() не разделяет концы строк, поэтому, если вы используете этот метод, вам не нужно добавлять свои собственные окончания строки.

Ответ 5

Чтобы выбрать случайную строку из файла, вы можете использовать пространственный эффективный однопроходный алгоритм выборки коллектора. Чтобы удалить эту строку, вы можете распечатать все, кроме выбранной строки:

#!/usr/bin/env python3
import fileinput

with open(filename) as file:
    k = select_random_it(enumerate(file), default=[-1])[0]

if k >= 0: # file is not empty
    with fileinput.FileInput(filename, inplace=True, backup='.bak') as file:
        for i, line in enumerate(file):
            if i != k: # keep line
                print(line, end='') # stdout is redirected to filename

где select_random_it() реализует алгоритм выборки коллектора:

import random

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from https://stackoverflow.com/a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

Чтобы напечатать k случайные строки из файла и удалить их:

#!/usr/bin/env python3
import random
import sys

k = 10
filename = 'TestingDeleteLines.txt'
with open(filename) as file:
    random_lines = reservoir_sample(file, k) # get k random lines

if not random_lines: # file is empty
    sys.exit() # do nothing, exit immediately

print("\n".join(map(str.strip, random_lines))) # print random lines
delete_lines(filename, random_lines) # delete them from the file

где reservoir_sample() использует тот же алгоритм, что и select_random_it(), но позволяет выбирать элементы k вместо одного:

import random

def reservoir_sample(iterable, k,
                     randrange=random.randrange, shuffle=random.shuffle):
    """Select *k* random elements from *iterable*.

    Use O(n) Algorithm R https://en.wikipedia.org/wiki/Reservoir_sampling

    If number of items less then *k* then return all items in random order.
    """
    it = iter(iterable)
    if not (k > 0):
        raise ValueError("sample size must be positive")

    sample = list(islice(it, k)) # fill the reservoir
    shuffle(sample)
    for i, item in enumerate(it, start=k+1):
        j = randrange(i) # random [0..i)
        if j < k:
            sample[j] = item # replace item with gradually decreasing probability
    return sample

и delete_lines() функция утилиты удаляет выбранные случайные строки из файла:

import fileinput
import os

def delete_lines(filename, lines):
    """Delete *lines* from *filename*."""
    lines = set(lines) # for amortized O(1) lookup
    with fileinput.FileInput(filename, inplace=True, backup='.bak') as file:
        for line in file:
            if line not in lines:
                print(line, end='')
    os.unlink(filename + '.bak') # remove backup if there is no exception

reservoir_sample(), delete_lines() funciton не загружает весь файл в память, и поэтому они могут работать для произвольных больших файлов.

Ответ 6

Возможно, вы могли бы попробовать создать 10 случайных чисел от 0 до 300, используя

deleteLineNums = random.sample(xrange(len(lines)), 10)

а затем удалите из массива строк, сделав копию со списком:

linesCopy = [line for idx, line in enumerate(lines) if idx not in deleteLineNums]
lines[:] = linesCopy

И затем верните строки в "TestingDeleteLines.txt".

Чтобы узнать, почему работает код копирования выше, это сообщение может быть полезно:

Удалить элементы из списка во время итерации

РЕДАКТИРОВАТЬ. Чтобы получить строки с произвольно создаваемыми индексами, просто выполните:

actualLines = []
for n in deleteLineNums:
    actualLines.append(lines[n])

Затем actualLines связывает фактический текст строки случайно генерируемых индексов строк.

РЕДАКТИРОВАТЬ: Или даже лучше, используйте понимание списка:

actualLines = [lines[n] for n in deleteLineNums]