Select 行来自两个包含匹配字符串的文件

Select lines from two files containing matching strings

假设我有两个文件A和B。A的内容:

foo1 foo2
bar1 bar3

B 的内容:

bar2 bar3
foo3 foo4

如何 select A 的第二行和 B 的第一行?没有搜索字符串。我需要 select 包含所有可能的常见字符串的行。

请注意,我不是在寻找来自两个不同文件的匹配行。所需的行不相同,但包含一个公共字符串。

任何帮助将不胜感激。谢谢!

TXR Lisp中的解决方案:

$ txr common-word-lines.tl file1 file2
bar1 bar3
bar2 bar3

common-word-lines.tl中的代码:

(defun hash-file-words (name)
  (with-stream (s (record-adapter #/\s+/ (open-file name "r")))
    (hash-list (get-lines s) :equal-based)))

(defun lines-containing-words-in-both-hashes (name hash1 hash2)
  (let ((s (open-file name "r")))
    (mappend*
      (op if [some (tok-str @1 #/\S+/) (andf hash1 hash2)]
        (list @1))
      (get-lines s))))

(tree-case *args*
  ((file1 file2 extra . junk) (throwf 'error "too many arguments"));
  ((file1 file2)
   (let ((hash1 (hash-file-words file1))
         (hash2 (hash-file-words file2)))
     (put-lines (lines-containing-words-in-both-hashes file1 hash1 hash2))
     (put-lines (lines-containing-words-in-both-hashes file2 hash1 hash2))))
  (else (throwf 'error "insufficient arguments")))

这会两次遍历文件。在第一遍中,我们构建了两个文件中所有 space 分隔词的哈希值。在第二遍中,我们打印每个文件的每一行,其中至少包含一个出现在两个哈希值中的单词。

使用了惰性列表处理,因此虽然看起来我们正在一次读取整个文件,但实际上并非如此。 get-lines returns a lazy list. In hash-file-words, the file is actually being read as the hash-list function is marching down the lazy list that is passed into it. In lines-containing-words-in-both-hashes, mappend* 用于延迟过滤列表并附加片段。

什么是(andf hash1 hash2)?首先,andf 是一个组合子。它接受多个参数,这些参数都是函数,returns 一个函数是这些函数的短路 AND 组合。 (andf a b c) 生成一个函数,该函数会将其参数传递给函数 a。如果那个 returnsnil (false),它停止并且 returns nil。否则它将其参数传递给 b,并应用相同的逻辑。如果它一直到达 c,则返回 c 返回的任何值。其次,虽然 hash1hash2 是散列 table,但它们可以用作 TXR Lisp 中的函数。散列 table 表现为单参数函数,它在散列 table 中查找其参数,并且 returns 对应的值,否则 nil。因此 (andf hash1 hash2) 简单地使用 AND 组合器来构建一个函数,如果它的参数存在于两个散列 table 中(与非 nil 值关联),则该函数 returns 为真。

因此,[some (tok-str @1 #/\S+/) (andf hash1 hash2)] 表示 "tokenize the line into words, and report if some of them are in both hashes"。 @1 是由 (op ...) 宏生成的匿名函数的隐式参数。 (get-lines)生成的列表中的每个元素都会调用该函数;即文件的每一行。所以@1依次表示每一行。

更通用的版本:更短,并处理两个或更多参数:

(defun hash-file-words (name)
  (with-stream (s (record-adapter #/\s+/ (open-file name "r")))
    (hash-list (get-lines s) :equal-based)))

(defun lines-containing-words-in-all-hashes (name hashes)
  (let ((s (open-file name "r")))
    (mappend*
      (op if [some (tok-str @1 #/\S+/) (andf . hashes)]
        (list @1))
      (get-lines s))))

(unless *args*
  (put-line `specify one or more files`)
  (exit 1))

(let ((word-hashes [mapcar hash-file-words *args*]))
  (each ((file *args*))
    (put-lines (lines-containing-words-in-all-hashes file word-hashes))))