在我構建英語語言資料庫的作業中,我經常處理來自不同來源的文本內容,并且需要合并共享相同第一個欄位的行。我經常在文本編輯器中使用捕獲第一個欄位的正則運算式來破解它,在“\n”中搜索,但我的文本檔案通常>10GB,因此命令列流式解決方案優于記憶體。
樣本輸入:
apple|pear
apple|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson
cherry|ruddy
cherry|cerise
期望的輸出:
apple|pear|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson|ruddy|cerise
邏輯是連接(用“|”連接)具有相同第一個欄位的所有行。
唯一的分隔符是“|”,并且分隔符在每個輸入行只出現一次。即它實際上是一個 2 列文本檔案。檔案排序無關緊要,唯一關心的是具有相同第一個欄位的連續行。
我有很多解決方案和單行代碼(通常在 awk 或 ruby?? 中)來處理同一行內容,但是在處理多行內容時我遇到了問題,希望得到幫助。出于某種原因,多行處理總是讓我陷入困境。
我確信這可以用 awk 簡潔地完成。
uj5u.com熱心網友回復:
假設/理解:
- 整個檔案可能未排序(按第一個欄位)
- 第一個欄位中具有相同字串的所有行將連續列出;這應該消除了在記憶體中維護大量資料的需要,但我們需要更多的輸入
- 第二個欄位可能包含尾隨空格(每個樣本輸入);這將需要洗掉
- 輸出不需要排序(按第一個欄位)
一個awk想法:
awk '
function print_line() {
if (prev != "")
print prev,data
}
BEGIN { FS=OFS="|" }
{ if ($1 != prev) {
print_line()
prev=$1
data=""
}
gsub(/[[:space:]] $/,"",$2) # strip trailing white space
data= data (data=="" ? "" : OFS) $2 # concatentate 2nd fields with OFS="|"
}
END { print_line() } # flush last set of data to stdout
' pipe.dat
這會產生:
apple|pear|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson|ruddy|cerise
uj5u.com熱心網友回復:
在每個 Unix 機器上的任何 shell 中使用任何 awk 并假設您的輸入按示例輸入中所示的第一個欄位分組,并且您在某些行的末尾實際上沒有尾隨空格:
$ cat tst.awk
BEGIN { FS=OFS="|" }
$1 != prev {
if ( NR>1 ) {
print out
}
out = prev = $1
}
{ out = out OFS $2 }
END { print out }
$ awk -f tst.awk file
apple|pear|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson|ruddy|cerise
如果它沒有被分組然后做sort file | awk -f tst.awk并且如果有尾隨空格然后添加{ sub(/ $/,"") }作為腳本的第一行。
uj5u.com熱心網友回復:
這是一個逐行讀取檔案的 Ruby 解決方案。最后,我展示了如果可以將檔案吞入字串,解決方案會變得多么簡單。
讓我們首先創建一個要使用的輸入檔案。
str =<<~_
apple|pear
apple|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson
cherry|ruddy
cherry|cerise
_
file_name_in = 'file_in'
File.write(file_name_in, str)
#=> 112
逐行讀取檔案時的解決方案
我們可以使用以下方法生成所需的輸出檔案。
def doit(file_name_in, file_name_out)
fin = File.new(file_name_in, "r")
fout = File.new(file_name_out, "w")
str = ''
until fin.eof?
s = fin.gets.strip
k,v = s.split(/(?=\|)/)
if str.empty?
str = s
key = k
elsif k == key
str << v
else
fout.puts(str)
str = s
key = k
end
end
fout.puts(str)
fin.close
fout.close
end
讓我們試試看。
file_name_out = 'file_out'
doit(file_name_in, file_name_out)
puts File.read(file_name_out)
列印以下內容。
apple|pear|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson|ruddy|cerise
注意
"apple|pear".split(/(?=\|)/)
#=> ["apple", "|pear"]
正則運算式包含與和之間的零寬度位置匹配的正前瞻 。(?=\|)'e''|'
檔案被吞入字串時的解決方案
我意識到(在詢問后)OP 不想將檔案吞入一個字串(因此我在上面的解決方案),但我想說明如果可以這樣做,問題有多簡單。這是執行此操作的眾多方法之一。
def gulp_it(file_name_in, file_name_out)
File.write(file_name_out,
File.read(file_name_in).gsub(/^(. )\|.*[^ ]\K *\r?\n\1/, ''))
end
gulp_it(file_name_in, file_name_out)
#=> 98
File.read(file_name_out)
印刷
apple|pear|quince
apple cider|juice
banana|plantain
cherry|cheerful, crimson|ruddy
cherry|cerise
考慮一下正則運算式引擎將要做什么,這可能是可以接受的快,當然取決于檔案大小。
正則運算式演示
雖然鏈接使用 PCRE 引擎,但使用 Ruby 的正則運算式引擎 (Onigmo) 的結果將是相同的。我們可以通過以自由間距模式撰寫正則運算式自記錄。
/
^ # match the beginning of a line
(. ) # match one or more characters
\|.*[^ ] # match '|', then zero or more chars, then a non-space
\K # resets the starting point of the match and discards
# any previously-matched characters
[ ]* # match zero or more chars
\r?\n # march the line terminator(s)
\1 # match the content of capture group 1
/x # invoke free-spacing mode
(. )匹配, 'apple','banana'并且'cherry'因為這些詞在開頭行。一個也可以寫([^|]*)。
uj5u.com熱心網友回復:
如果我們假設s是一個包含檔案中所有行的字串。
s.split("\n").inject({}) { |h, x| k, v = x.split('|'); h[k] ||= []; h[k] << v.strip; h }
將產生:
{"apple"=>["pear", "quince"], "apple cider"=>["juice"], "banana"=>["plantain"], "cherry"=>["cheerful, crimson", "ruddy", "cerise"]}
然后:
s.split("\n").inject({}) { |h, x| k, v = x.split('|'); h[k] ||= []; h[k] << v.strip; h }.map { |k, v| "#{k}|#{v.join('|')}" }
產量:
["apple|pear|quince", "apple cider|juice", "banana|plantain", "cherry|cheerful, crimson|ruddy|cerise"]
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/427593.html
