これはまさに map-reduce が解決しようとしている種類の問題です。慣れていない方のために説明すると、map-reduce は単純な 2 ステップのプロセスです。テキストで見つけたい単語を格納したリストがあるとします。マッパー関数は、テキストの行ごとにこの単語のリストを反復処理できます。行に表示される場合は、結果リストに格納される ['word', lineNum] などの値を返します。マッパーは基本的に for ループのラッパーです。次に、結果リストを取得して「削減」できます。この場合、 [['word1', 1]...['word1', n] のような結果リストを取得できるレデューサー関数を記述します。 ...] を {'word1': [1, 2, 5], 'word3': [7], ...} のようなオブジェクトに変換します。
このアプローチは、各アイテムに共通のアクションを実行しながらリストを反復処理するプロセスを抽象化するため、有利です。また、分析のニーズが変化した場合 (よくあることですが)、マッパー/リダクション関数を変更するだけでよく、残りの部分には触れずに済みます。コード。さらに、このメソッドは高度に並列化可能であり、問題が発生した場合に備えています (Google に聞いてください!)。
Python 3.x には、map() および reduce() として組み込みの map/reduce メソッドがあります。python docs で調べてください。それらがどのように機能するかを確認できるように、組み込みライブラリを使用せずに、問題に基づいて map/reduce のバージョンを実装しました。データの保存方法を指定しなかったので、私はそれについていくつかの仮定を立てました。つまり、関心のある単語のリストはコンマ区切りのファイルとして与えられるべきでした。テキスト ファイルを読み取るために、readlines() を使用して行の配列を取得し、正規表現パターンを使用して行を単語に分割します (つまり、英数字以外で分割します)。もちろん、これはあなたのニーズに合わないかもしれないので、あなたが見ているファイルにとって意味のあるものに変更することができます。
私は難解な python 機能 (ラムダなし!) から離れようとしたので、うまくいけば実装は明確です。最後に、ループを使用してテキスト ファイルの行を反復処理し、map 関数を使用して対象の単語のリストを反復処理しました。代わりにネストされたマップ関数を使用することもできますが、ループ インデックスを追跡したかったのです (行番号が重要なので)。マップ関数を本当に入れ子にしたい場合は、ファイルを読み取るときに配列行を行と行番号のタプルとして保存するか、マップ関数を変更してインデックスを返すことができます。
これが役立つことを願っています!
#!usr/bin/env/ python
#Regexp library
import re
#Map
#This function returns a new array containing
#the elements after that have been modified by whatever function we passed in.
def mapper(function, sequence):
#List to store the results of the map operation
result = []
#Iterate over each item in sequence, append the values to the results list
#after they have been modified by the "function" supplied as an argument in the
#mapper function call.
for item in sequence:
result.append(function(item))
return result
#Reduce
#The purpose of the reduce function is to go through an array, and combine the items
#according to a specified function - this specified function should combine an element
#with a base value
def reducer(function, sequence, base_value):
#Need to get an base value to serve as the starting point for the construction of
#the result
#I will assume one is given, but in most cases you should include extra validation
#here to either ensure one is given, or some sensible default is chosen
#Initialize our accumulative value object with the base value
accum_value = base_value
#Iterate through the sequence items, applying the "function" provided, and
#storing the results in the accum_value object
for item in sequence:
accum_value = function(item, accum_value)
return accum_value
#With these functions it should be sufficient to address your problem, what remains
#is simply to get the data from the text files, and keep track of the lines in
#which words appear
if __name__ == 'main':
word_list_file = 'FILEPATH GOES HERE'
#Read in a file containing the words that will be searched in the text file
#(assumes words are given as a comma separated list)
infile = open(word_list_file, 'rt') #Open file
content = infile.read() #read the whole file as a single string
word_list = content.split(',') #split the string into an array of words
infile.close()
target_text_file = 'FILEPATH GOES HERE'
#Read in the text to analyze
infile = open(target_text_file, 'rt') #Open file
target_text_lines = infile.readlines() #Read the whole file as an array of lines
infile.close()
#With the data loaded, the overall strategy will be to loop over the text lines, and
#we will use the map function to loop over the the word_list and see if they are in
#the current text file line
#First, define the my_mapper function that will process your data, and will be passed to
#the map function
def my_mapper(item):
#Split the current sentence into words
#Will split on any non alpha-numeric character. This strategy can be revised
#to find matches to a regular expression pattern based on the words in the
#words list. Either way, make sure you choose a sensible strategy to do this.
current_line_words = re.split(r'\W+', target_text_lines[k])
#lowercase the words
current_line_words = [word.lower() for word in current_line_words]
#Check if the current item (word) is in the current_line_words list, and if so,
#return the word and the line number
if item in current_line_words:
return [item, k+1] #Return k+1 because k begins at 0, but I assume line
#counting begins with 1?
else:
return [] #Technically, this does not need to be added, it can simply
#return None by default, but that requires manually handling iterator
#objects so the loop doesn't crash when seeing the None values,
#and I am being lazy :D
#With the mapper function established, we can proceed to loop over the text lines of the
#array, and use our map function to process the lines against the list of words.
#This array will store the results of the map operation
map_output = []
#Loop over text file lines, use mapper to find which words are in which lines, store
#in map_output list. This is the exciting stuff!
for k in range(len(target_text_lines)):
map_output.extend(mapper(my_mapper, word_list))
#At this point, we should have a list of lists containing the words and the lines they
#appeared in, and it should look like, [['word1', 1] ... ['word25': 5] ... [] ...]
#As you can see, the post-map array will have an entry for each word that appeared in
#each line, and if a particular word did not appear in a particular line, there will be a
#empty list instead.
#Now all that remains is to summarize our data, and that is what the reduce function is
#for. We will iterate over the map_output list, and collect the words and which lines
#they appear at in an object that will have the format { 'word': [n1, n2, ...] },where
#n1, n2, ... are the lines the word appears in. As in the case for the mapper
#function, the output of the reduce function can be modified in the my_reducer function
#you supply to it. If you'd rather it return something else (like say, word count), this
#is the function to modify.
def my_reducer(item, accum_value):
#First, verify item is not empty
if item != []:
#If the element already exists in the output object, append the current line
#value to it, if not, add it to the object and create a set holding the current
#line value
#Check this word/line combination isn't already stored in the output dict
if (item[0] in accum_value) and (item[1] not in accum_value[item[0]]):
accum_value[item[0]].append(item[1])
else:
accum_value[item[0]] = [item[1]]
return accum_value
#Now we can call the reduce function, save it's output, print it to screen, and we're
#done!
#(Note that for base value we are just passing in an empty object, {})
reduce_results = reducer(my_reducer, map_output, {})
#Print results to screen
for result in reduce_results:
print('word: {}, lines: {}'.format(result, reduce_results[result]))