【问题标题】:Python: Choose random line from file, then delete that linePython:从文件中选择随机行,然后删除该行
【发布时间】:2015-12-23 15:39:06
【问题描述】:

我是 Python 新手(因为我是通过 CodeAcademy 课程学习的),并且可以通过一些帮助来解决这个问题。

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

所以如果我的文件有 10 行:

Carrot
Banana
Strawberry
Canteloupe
Blueberry
Snacks
Apple
Raspberry
Papaya
Watermelon

我需要它从那些行中随机挑选出来,告诉我它是随机挑选的蓝莓、胡萝卜、西瓜和香蕉,然后删除那些行。

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

我目前的问题是双重的:

  1. 它正在复制随机元素。如果它选择一条线,我需要它不要再次选择同一条线。但是,使用 random.sample 似乎不起作用,因为当我稍后使用每一行附加到 URL 时,我需要将这些行分开。
  2. 我不觉得我的逻辑(写入数组->在文本文件中查找匹配项->删除)是最理想的逻辑。有没有更好的写法?

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

【问题讨论】:

  • 方法是打开文件,读入readlines()的所有行,关闭文件,然后重写整个文件。
  • 我如何告诉它只是删除随机行?
  • file_object.seek(0) 应该让您从头开始迭代。在您的示例中,lines 看起来像是一个 file_object。

标签: python algorithm random


【解决方案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 我的疏忽。我会相应地进行编辑。
【解决方案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")

【讨论】:

  • 所以不是将随机行写入数组,而是将所有其他非随机行写入数组并创建一个新文件?
  • 为什么要使用数组(Python 中的 FWIW 是 list 而不是 array)?从源代码读取一行,决定是否要保留它,如果是,将其写入临时文件,泡沫重复。
【解决方案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

如果需要,can be improved 是 O(n**2) 算法(对于像输入这样的小文件,您不需要它)

【讨论】:

  • 作为初学者,这本书非常容易阅读和理解,非常感谢。 :) 现在,我遇到的问题是,如果我将它放入函数中,它会在 elif 行上引发语法错误。您对为什么会这样有任何想法吗?
  • @SamW:我猜,你破坏了代码缩进(确保你没有混合使用制表符和空格进行缩进,使用其中一个或两个)但我不能确定你是否这样做'不显示 exact 代码:create a minimal but complete code example,它演示了该问题并将其添加到您的问题中(或者如果您认为该错误可能对其他人感兴趣,请提出一个新问题)。
  • 天哪,呵呵!非常感谢,这非常有帮助,我学到了很多东西。 :) 非常感谢您抽出宝贵时间将其写出来。
【解决方案4】:

要从文件中随机选择一行,您可以使用节省空间的单通道reservoir-sampling algorithm。要删除该行,您可以打印除所选行之外的所有内容:

#!/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() implements the reservoir-sampling algorithm:

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() 函数不会将整个文件加载到内存中,因此它们可以处理任意大文件。

【讨论】:

    【解决方案5】:

    也许您可以尝试使用

    从 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”。

    要了解为什么上面的复制代码有效,这篇文章可能会有所帮助:

    Remove items from a list while iterating

    编辑:要获取随机生成的索引处的行,只需执行以下操作:

    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),那只会给我一个数字列表,但这些数字与我已经拉出的随机线不对应。我是不是误会了什么?
    • 因此在这种情况下,您将识别要删除的随机行索引,而不是行本身。请注意,由于在这两种情况下都是随机选择行,因此这两种方法在从文件中识别 10 个随机行方面是等效的。编辑:(所以你会这样做而不是随机选择实际的行,并且替换在逻辑上会给你相同的结果)这有意义吗?
    • 啊,这确实澄清了一些事情。问题是,我需要行中的实际文本,因为稍后我将使用这些行中的文本添加到 URL 字符串的末尾。所以我需要知道那个索引是哪一行,然后删除它。
    【解决方案6】:

    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
    

    【讨论】:

    • 我最初的问题并不像应该的那样精确。我需要它从文件中随机选择行,告诉我这些行说什么,然后删除这些行。
    • @SamW 被删除的行在变量deleted中,剩下的行还在lines中。你还需要什么?
    • 为什么需要在这里对索引进行排序? (line.pop(i) 是 O(n) 无论哪种方式)
    • @J.F.Sebastian 只是为了防止出现 IndexError: pop index out of range
    • 这是有道理的。我可能一直在考虑for i in choices: items.remove(i) 来自@Josh Trii Johnston's answer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    • 2015-04-23
    • 2012-09-03
    • 1970-01-01
    相关资源
    最近更新 更多