【问题标题】:Python or Bash - Iterate all words in a text file over itselfPython 或 Bash - 遍历文本文件中的所有单词
【发布时间】:2015-10-14 16:24:26
【问题描述】:

我有一个包含数千个单词的文本文件,例如:

laban
labrador
labradors
lacey
lachesis
lacy
ladoga
ladonna
lafayette
lafitte
lagos
lagrange
lagrangian
lahore
laius
lajos
lakeisha
lakewood

我想迭代每个单词,所以我得到:

labanlaban
labanlabrador
labanlabradors
labanlacey
labanlachesis
etc...

在 bash 中我可以执行以下操作,但速度非常慢:

#!/bin/bash
( cat words.txt | while read word1; do
  cat words.txt | while read word2; do
    echo "$word1$word2" >> doublewords.txt
 done; done )

有没有更快更有效的方法来做到这一点? 另外,我将如何以这种方式迭代两个不同的文本文件?

【问题讨论】:

  • 你能把列表放入内存吗?

标签: python bash loops while-loop


【解决方案1】:

如果您可以将列表放入内存中:

import itertools

with open(words_filename, 'r') as words_file:
    words = [word.strip() for word in words_file]

for words in itertools.product(words, repeat=2):
    print(''.join(words))

(你也可以做一个双循环,但我今晚感觉itertools。)

我怀疑这里的胜利是我们可以避免一遍又一遍地重新读取文件; bash 示例中的内部循环将为外部循环的每次迭代生成一个文件。另外,我认为 Python 的执行速度往往比 bash、IIRC 快。

你当然可以用 bash 来实现这个技巧(将文件读入数组,编写一个双 for 循环),只是更痛苦。

【讨论】:

  • 谢谢。那工作得很好。如果我想迭代两个不同的文本文件而不是同一个文本文件怎么办?
  • 使用with… 行加载两个文件并使用itertools.product(words1, words2) 而不是itertools.product(words, repeat=2)。 (见 ["docs.python.org/2/library/itertools.html"][here])
  • 感谢您的帮助,但我无法完成。我尝试使用相同的“with”加载两个文件,但效果不佳(例如with open('a', 'w') as a, open('b', 'w') as b:)。尝试了 2 个单独的“with”循环,也没有运气。
  • 如果你想打开两个文件来阅读,你需要'r'作为open的第二个参数。 ('w' 是“写”,将删除文件!)
【解决方案2】:

看起来sed 在每一行附加一个文本非常有效。 我建议:

#!/bin/bash

for word in $(< words.txt)
do 
    sed "s/$/$word/" words.txt;
done > doublewords.txt

(您是否混淆了$ 表示sed$word 的行尾,这是一个bash 变量)。

对于一个 2000 行的文件,这在我的计算机上运行大约需要 20 秒,而您的解决方案大约需要 2 分钟。


备注:看起来你最好重定向整个程序的标准输出而不是在每个循环中强制写入。


(警告,这有点偏离主题和个人观点!)

如果您真的追求速度,您应该考虑使用编译语言,例如 C++。例如:

vector<string> words;
ifstream infile("words.dat");
for(string line ; std::getline(infile,line) ; )
    words.push_back(line);
infile.close();

ofstream outfile("doublewords.dat");
for(auto word1 : data)
    for(auto word2 : data)
        outfile << word1 << word2 << "\n";
outfile.close();

您需要了解 bash 和 python 都不擅长双重 for 循环:这就是您使用技巧 (@Thanatos) 或预定义命令 (sed) 的原因。最近,我遇到了一个双循环问题(给定一组 10000 个 3d 点,计算对之间的所有距离),我使用 C++ 而不是 python 或 Matlab 成功解决了它。

【讨论】:

  • 谢谢。我会试试你的解决方案。另外,我将如何迭代两个不同的文本文件而不是同一个文本文件?
  • words.txt 的两次出现是独立的。只需将第一个替换为words1.txt,将第二个替换为words2.txt
  • 我想我在这里做错了。它是否正确? #!/bin/bash for word in $(&lt; doublewords.txt) do sed "s/$/$word/" numbers.txt; done &gt; words-numbers.txt
  • @user3552978 你的意思是用(&lt; doublewords.txt) 代替(&lt; words.txt)。除此之外,它看起来是正确的(除了缺少换行符,我认为这只是来自评论)。
【解决方案3】:

如果您有可用的 GHC,笛卡尔积是同步的!

Q1:一个文件

-- words.hs
import Control.Applicative
main = interact f
    where f = unlines . g . words
          g x = map (++) x <*> x

这会将文件拆分为单词列表,然后使用应用程序 &lt;*&gt; 将每个单词附加到其他单词。

用 GHC 编译,

ghc words.hs

然后使用 IO 重定向运行:

./words <words.txt >out

Q2:两个文件

-- words2.hs
import Control.Applicative
import Control.Monad
import System.Environment
main = do
    ws <- mapM ((liftM words) . readFile) =<< getArgs
    putStrLn $ unlines $ g ws
    where g (x:y:_) = map (++) x <*> y

像以前一样编译并使用这两个文件作为参数运行:

./words2 words1.txt words2.txt > out

Bleh,正在编译?

想要 shell 脚本的便利性和编译后的可执行文件的性能?为什么不两者都做?

只需将所需的 Haskell 程序包装在一个包装脚本中,该脚本在 /var/tmp 中编译它,然后将其自身替换为生成的可执行文件:

#!/bin/bash
# wrapper.sh

cd /var/tmp
cat > c.hs <<CODE
# replace this comment with haskell code
CODE
ghc c.hs >/dev/null
cd - >/dev/null
exec /var/tmp/c "$@"

这会处理参数和 IO 重定向,就好像包装器不存在一样。

结果

用两个 2000 字的文件对其他一些答案进行计时:

$ time ./words2 words1.txt words2.txt >out
3.75s user 0.20s system 98% cpu 4.026 total

$ time ./wrapper.sh words1.txt words2.txt > words2
4.12s user 0.26s system 97% cpu 4.485 total

$ time ./thanatos.py > out
4.93s user 0.11s system 98% cpu 5.124 total

$ time ./styko.sh
7.91s user 0.96s system 74% cpu 11.883 total

$ time ./user3552978.sh
57.16s user 29.17s system 93% cpu 1:31.97 total

【讨论】:

  • 太棒了!两个不同的文本文件怎么样?
  • @user3552978 我现在为两个不同的文本文件改进了 Haskell 脚本并更新了结果。
【解决方案4】:

您可以通过创建tempfile 并在读取现有文件的同时将数据写入其中,最后以remove 原始文件和move 将新文件写入原始文件来以pythonic 方式执行此操作。

import sys
from os import remove
from shutil import move
from tempfile import mkstemp


def data_redundent(source_file_path):
    fh, target_file_path = mkstemp()
    with open(target_file_path, 'w') as target_file:
        with open(source_file_path, 'r') as source_file:
            for line in source_file:
                target_file.write(line.replace('\n', '')+line)
    remove(source_file_path)
    move(target_file_path, source_file_path)

data_redundent('test_data.txt')

【讨论】:

    【解决方案5】:

    我不确定这有多有效,但是一个非常简单的方法,使用专门为这类事情设计的 Unix 工具,会是

    paste -d'\0' <file> <file>
    

    -d 选项指定要在连接部分之间使用的分隔符,\0 表示 NULL 字符(即根本没有分隔符)。

    【讨论】:

    • 这只会输出像“labanlaban”这样的双打,而不是像“labanlacey”这样的东西。 (它只是将每行加倍;OP 似乎想要一个笛卡尔积。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-10
    • 2014-04-24
    • 2021-11-26
    • 1970-01-01
    相关资源
    最近更新 更多