Python:从文件中随机选择一行,然后删除该行。

3

我是新手Python程序员(通过CodeAcademy课程学习),需要一些帮助来解决这个问题。

我有一个文件,“TestingDeleteLines.txt”,大约有300行文本。现在,我正在尝试从该文件中打印出10行随机行,然后删除这些行。

因此,如果我的文件有10行:

Carrot
Banana
Strawberry
Canteloupe
Blueberry
Snacks
Apple
Raspberry
Papaya
Watermelon

我需要它从这些行中随机选出一个,告诉我它随机选出了蓝莓、胡萝卜、西瓜和香蕉,然后删除这些行。

问题在于,当Python读取文件时,它会读取该文件,一旦到达结尾,就不会返回并删除这些行。我目前的想法是将这些行写入列表,然后重新打开文件,将列表与文本文件匹配,如果找到匹配项,则删除这些行。

我目前的问题有两个:

  1. It's duplicating the random elements. If it picks a line, I need it to not pick that same line again. However, using random.sample doesn't seem to work, as I need those lines separated out when I later use each line to append to a URL.
  2. I don't feel like my logic (write to array->find matches in text file->delete) is the most ideal logic. Is there a better way to write this?

    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()
    

3
要做到这一点,需要打开文件,使用readlines()读入所有行,关闭文件,然后重新写入整个文件。 - Morgan Thrapp
我该如何告诉它只删除随机行呢? - Sam W
file_object.seek(0)应该让您再次从开头开始迭代。在您的示例中,lines看起来像是一个file_object - wwii
6个回答

4

关键是:你不能从文件中“删除”,而是用新内容重写整个文件(或另一个文件)。规范的方法是逐行读取原始文件,将要保留的行写回临时文件,然后用新文件替换旧文件。

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")

所以,我应该将所有其他非随机行写入数组并创建一个新文件,而不是将随机行写入数组? - Sam W
为什么要使用数组(在Python中,它是list而不是array)?从源中读取一行,决定是否要保留它,如果是,则将其写入临时文件,反复执行。 - bruno desthuilliers

3
我有一个文件名为“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) 的算法,如果需要的话可以进行改进(对于像您的输入文件这样小的文件,您不需要它)


作为一个初学者,这篇文章读起来非常容易理解,非常感谢你。 :)现在我遇到的问题是,如果我把代码放进一个函数中,它就会在 elif 行抛出语法错误。你有什么想法为什么会这样吗? - Sam W
@SamW:我猜你是破坏了代码缩进(确保不要混用制表符和空格进行缩进,只使用其中一种),但如果你不展示精确的代码,我无法确定:创建一个最小但完整的代码示例,演示问题并将其添加到你的问题中(或者如果你认为错误可能对其他人有趣,可以提出一个新问题)。 - jfs
1
哦,我的天啊!谢谢你非常地帮助我,我从中学到了很多。:) 你抽出时间写下这些对我来说真的非常感激。 - Sam W

3
要从文件中随机选择一行,您可以使用高效的单次遍历蓄水池抽样算法。要删除该行,您可以打印除所选行之外的所有内容:
#!/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://dev59.com/MUnSa4cB1Zd3GeqPOHQ5#1456750
    # 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() 函数不会将整个文件加载到内存中,因此它们可以用于任意大的文件。


1
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

我的初始问题可能不够精确。我需要它从文件中随机选择行,告诉我这些行说了什么,然后删除这些行。 - Sam W
@SamW 被删除的行存储在变量 deleted 中,剩余的行仍然在 lines 中。你还需要什么? - Brent Washburne
为什么你需要在这里排序索引?(line.pop(i) 无论如何都是 O(n)) - jfs
@J.F.Sebastian 仅仅为了防止 IndexError: pop index out of range。 - rebeling
有道理。我可能在考虑@Josh Trii Johnston的答案中的for i in choices: items.remove(i) - jfs

1
假设您有一个从文件中读取的行列表,存储在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() 方法,则不需要添加自己的行结束符,因为该方法不会删除行结束符。

“使用您首选的行尾符 \r\n 或 \n 进行连接是错误的,因为 readlines 列表项包含末尾的换行符...这会添加额外的空行。” - rebeling
我的疏忽,我会进行编辑。 - Josh J

0
也许你可以尝试使用编程语言生成0到300之间的10个随机数。
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]

我如何将其与我的原始随机行连接起来?'for x in range(0,10): myline=random.choice(lines) print(myline)'假设它提取了“胡萝卜,香蕉,苹果”。我现在想删除这些完全相同的行。如果我添加deleteLineNums = random.sample(xrange(len(lines)), 10),那只会给我一个数字列表,但是这些数字与我已经提取的随机行不对应。我是否有什么误解? - Sam W
因此,在这种情况下,您将识别要删除的随机行索引,而不是行本身。请注意,由于在两种情况下都是随机选择行,因此这两种方法在从文件中识别10个随机行方面是等效的。编辑:(因此,您将在选择实际随机行的位置执行此操作,并且该替换逻辑上给出相同的结果)这有意义吗? - sgrg
啊,这样就清楚了。问题是,我需要那些行的实际文本,因为后面我要使用那些行的文本添加到URL字符串的末尾。所以我需要知道该索引处的行是什么,然后将其删除。 - Sam W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接