【问题标题】:python file manipulations (bash script porting)python 文件操作(bash 脚本移植)
【发布时间】:2013-03-25 22:05:39
【问题描述】:

我正在尝试重写一些我认为非常低效(更不用说不优雅)的旧 bash 脚本并使用一些可怕的管道......也许有真正 Python 技能的人可以给我一些指示......

该脚本使用多个临时文件...我认为另一件事是一种不好的风格,可能可以避免...

它本质上是通过首先从顶部剪掉一定数量的行(丢弃标题)来操纵INPUT-FILE。
然后它拉出其中一列并:

  • 计算raws = N的数量;
  • 从这个单列文件中丢弃所有重复的条目(我使用sort -u -n FILE > S-FILE)。

之后,我创建了一个从 1 到 N 的连续整数索引,并使用粘贴命令将此新索引列粘贴到原始 INPUT-FILE。
然后,我的 bash 脚本会为我们写入 S-FILE 的值生成百分比等级。
我相信 Python 会利用 scipy.stats,而在 bash 中,我会确定 S-FILE 中每个唯一条目的重复行数(dupline),然后计算 per-rank=$((100*($counter+$dupline/2)/$length)),其中 $length= FILE 的长度而不是 S-FILE。然后,我会将结果打印到一个单独的 1 列文件中(并且重复相同的每个等级的次数与我们有重复行的次数一样多)。
然后,我会将这个带有百分位等级的新列粘贴回 INPUT-FILE(因为我会按用于计算百分位等级的列对 INPUT-FILE 进行排序 - 结果中的所有内容都会完美排列)。

在这之后,它进入下面的丑陋......

sort -o $INPUT-FILE $INPUT-FILE

awk 'int($4)>2000' $INPUT-FILE | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 2000-$INPUT-FILE

diff $INPUT-FILE 2000-$INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>1000' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 1000-$INPUT-FILE

cat 2000-$INPUT-FILE 1000-$INPUT-FILE | sort > merge-$INPUT-FILE

diff merge-$INPUT-FILE $INPUT-FILE | sed '/^[0-9][0-9]*/d; s/^. //; /^---$/d' | awk 'int($4)>500' | awk -v seed=$RANDOM 'BEGIN{srand(seed);} {print rand()"\t"$0}' | sort -k1 -k2 -n | cut -f2- | head -n 500 > 500-$INPUT-FILE

rm merge-$INPUT-FILE

本质上,这是一种非常不优雅的 bash 方式:

  1. 从 $INPUT-FILE 中随机选择 500 行,其中第 4 列中的值大于 2000,并将其写入文件 2000-$INPUT-FILE
  2. 对于 $INPUT-FILE 中的所有 REMAINING 行,随机选择第 4 列中的值大于 1000 的 500 行并将其写入文件 1000-$INPUT-FILE
  3. 对于 1) 和 2) 之后 $INPUT-FILE 中的所有 REMAINING 行,随机选择第 4 列中的值大于 500 的 500 行并将其写入文件 500-$INPUT-FILE

再次,我希望有人可以帮助我将这个丑陋的管道东西改造成蟒蛇之美! :) 谢谢!

【问题讨论】:

  • 您能否从文件中添加一个示例行以及您从命令行调用此脚本的示例?
  • 发帖前请先查看帖子预览,并确保避开文字墙。它只会让你的问题被忽略,因为它很难破译所说的内容。
  • 一些要查看的库:tempfile, difflib random
  • 正如 Serdalis 所提到的,这是相当广泛的。您最好开始着手并将其分解为多个部分,在遇到问题时提出问题。
  • @Serdalis 感谢您主持我的帖子,先生!我同意,这有点罗嗦......只是试图为问题的实际内容提供一个一般背景,以“在此之后,它进入下面的丑陋......”开始。这是我很想重写的主要部分。其他事情都相当琐碎......

标签: python parsing text


【解决方案1】:

cmets中的两个关键点:

(A) 该文件是约 50k 行约 100 个字符。足够小,可以舒适地放入现代台式机/服务器/笔记本电脑系统的内存中。

(B)作者的主要问题是关于如何跟踪已经选择的行,并且不要再次选择它们。

我建议三个步骤。

(1) 浏览文件,制作三个单独的列表——分别称为 u、v、w——满足每个条件的行号。这些列表可能有 500 多行,并且可能包含重复项,但我们将在第 (2) 步中解决这些问题。

u = []
v = []
w = []

with open(filename, "r") as f:
    for linenum, line in enumerate(f):
        x = int(line.split()[3])
        if x > 2000:
            u.append(x)
        if x > 1000:
            v.append(x)
        if x > 500:
            w.append(x)

(2) 选择行号。您可以使用内置的 Random.sample() 从总体中选择 k 个元素的样本。我们要删除之前选择的元素,因此要跟踪集合中的这些元素。 (“选择”集合是一个集合而不是一个列表,因为测试“如果 x 未在选择中”对于一个集合来说是 O(log(n)),但对于一个列表来说是 O(n)。将其更改为一个列表和如果您精确测量时间,您会看到速度变慢,尽管对于“仅”50k 个数据点/500 个样本/3 个类别的数据集而言,这可能不是明显的延迟。)

import random
rand = random.Random()       # change to random.Random(1234) for repeatable results

chosen = set()
s0 = rand.sample(u, 500)
chosen.update(s0)
s1 = rand.sample([x for x in v if x not in chosen], 500)
chosen.update(s1)
s2 = rand.sample([x for x in w if x not in chosen], 500)
chosen.update(s2)

(3) 再次遍历输入文件,将编号为 s0 的行放入第一个输出文件,将编号为 s1 的行放入第二个输出文件,将编号为 s2 的行放入第三个输出文件.这在任何语言中都很简单,但这里有一个使用 Python“成语”的实现:

linenum2sample = dict([(x, 0) for x in s0]+[(x, 1) for x in s1]+[(x, 2) for x in s2])

outfile = [open("-".join(x, filename), "w") for x in ["2000", "1000", "500"]]

try:
    with open(filename, "r") as f:
        for linenum, line in enumerate(f):
            s = linenum2sample.get(linenum)
            if s is not None:
                outfile[s].write(line)
finally:
    for f in outfile:
        f.close()

【讨论】:

  • 感谢您的详细解答!几个 cmets...1) 在第三块代码中,join() 似乎只有在我将其作为 join([x, filename]) 放入时才起作用 2)我已将样本大小从 500 更改为 100 以进行测试我得到的三个文件似乎在大小上波动,即首先在一个超过 76k 行的大文件上运行分别产生了三个分别有 98、100 和 100 行的文件。第二次运行 SAME 文件生成了三个文件,每个文件有 100、98 和 94 行。第三次运行出现了 99、98 和 99 行的文件。我也不太了解为什么......有什么想法吗??
  • 显然,在 s0、s1 和 s2 中可能会出现重复项......并且 dict() 不会创建重复条目 - 即输出文件中的行数会减少通过在每个集合中找到的重复项的数量...
  • 好的,我通过使用 random.sample(set(u), 500) 构造等绕过了这个问题。谢谢!看起来解决方案在进行小修正后仍然有效! :)
【解决方案2】:

把它分解成简单的部分。

  1. 如果标头不可用,则使用 csv.DictReader 或 csv.reader 读取文件。在遍历这些行时,检查第 4 列的值并将这些行插入到列表字典中,其中字典键类似于“gt_2000”、“gt_1000”、“gt_500”。

  2. 遍历您的字典键,并为每个键创建一个文件并执行 500 次循环,对于每次迭代,使用 random.randint(0, len(the_list)-1) 获取随机索引列表,将其写入文件,然后从列表中删除该索引处的项目。如果任何存储桶中的项目可能少于 500 个,那么这将需要更多一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    • 2013-12-14
    • 1970-01-01
    相关资源
    最近更新 更多