neumachen
3/5/2017 - 1:58 AM

Functional programming in Erlang -- index program.

Functional programming in Erlang -- index program.

-module(index).
-export([get_file_contents/1,show_file_contents/1]).
-export([index_file/1,print_index/1]).
-export([partition_test/0]).


%% The main prorgram.
%% Run:
%%   index:index_file("gettysburg-address.txt").
%% to produce a list of word indexes.
%%
%% To produce a "formated" index listing
%% Run:
%%   index:print_index(index:index_file("gettysburg-address.txt")).
%%
index_file(File) ->
    Lines = get_file_contents(File),
    Words = process_lines(Lines),
    collect_words(Words).

%% Count and process each line of text, produces a list of {Word, Line_Number} pairs.
process_lines(Lines) -> process_lines(Lines, 0, []).
process_lines([], _Count, Acc) -> lists:sort(Acc);
process_lines([L|Ls], Count, Acc) -> 
    Next = Count+ 1,
    process_lines(Ls, Next, index_line(L, Next, Acc)). 

%% Given a line of text, split line into words and produce a list of Word, Line_number pairs.
index_line(Line, Count, Acc) ->
    Toks = string:tokens(Line, " \t\n\b\r\\.,:;!?=-"),
    Words = clean_words(Toks),
    index_words(Words, Count, Acc).

%% Given a list of words produces a list of lowercase words above a certain length.
clean_words([])     -> [];
clean_words([X|Xs]) ->
    case string:len(X) > 3  of
        true  -> [string:to_lower(X) | clean_words(Xs)];
        false -> clean_words(Xs)
    end.

%% Given a list of Words and a Line_number, produce a list of Word, Line_Number pairs.
index_words([], _, Acc) -> Acc;
index_words([T|Ts], Count, Acc) ->
    index_words(Ts, Count, [{T, Count} | Acc]).

%% Given a sorted list of {Word, Line_Numbers} pairs collect into a list of {Word, List-0f-Line-Numbers}.
collect_words(Wrds) -> lists:sort(collect_words(Wrds, [], [])).
collect_words([{Wrd,Ln}], Acc, Idx) ->
    [{Wrd,lists:sort([Ln|Acc])}|Idx];
collect_words([{Wrd,Ln1},{Wrd,_Ln2}|Wrds], Acc, Idx) ->
    collect_words(Wrds,[Ln1|Acc], Idx);
collect_words([{Wrd1,Ln1},{_Wrd2,_Ln2}|Wrds], Acc, Idx) ->
    Index = partition([Ln1|Acc]),
    collect_words(Wrds, [], [{Wrd1, Index}|Idx]). 


%% partition a list of numbers into a list of Number Range Pairs.
%% See test function for examples.
partition(Lst) -> partition(lists:sort(Lst), []).

partition([], Acc) -> lists:reverse(Acc);
partition([X|Xs], [{Start}|Acc]) when X == Start+1 ->
	partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], [{Start,X}|Acc]) ->
	partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], [{Start,End}|Acc]) when X == End+1 ->
	partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], Acc) ->
	partition(Xs, [{X,X}|Acc]).

partition_test() ->    
    [{1,1}] = partition([1]),
    [{1,1}] = partition([1,1,1]),
    [{1,2}] = partition([2,1,1]),
    [{1,3}] = partition([3,2,2,1,1]),
    [{1,3},{5,5}] = partition([5,5,3,2,2,1]),
    [{1,1},{3,3},{5,7}] = partition([7,7,6,5,3,1,1]),
    ok.


%% Print out file world index
print_index([]) ->  ok;
print_index([X|Xs]) ->
    print_item(X),
    print_index(Xs).

% print single word index item
print_item({Word, Ranges}) ->
    io:format("~p ", [Word]),
    print_ranges(Ranges),
    io:format("~n").

% print the list of ranges
print_ranges([]) -> done;
print_ranges([{Beg,End}|Xs]) ->
    io:format(",{~p,~p}", [Beg,End]),
    print_ranges(Xs);
print_ranges([X]) ->  % sidestep a partition function bug.
    io:format("{~p,~p}", [X,X]),
    print_ranges([]).


% Used to read a file into a list of lines.
% Example files available in:
%   gettysburg-address.txt (short)
%   dickens-christmas.txt  (long)


% Get the contents of a text file into a list of lines.
% Each line has its trailing newline removed.

get_file_contents(Name) ->
    {ok,File} = file:open(Name,[read]),
    Rev = get_all_lines(File,[]),
    lists:reverse(Rev).

% Auxiliary function for get_file_contents.
% Not exported.
get_all_lines(File,Partial) ->
    case io:get_line(File,"") of
        eof -> file:close(File),
               Partial;
        Line -> {Strip,_} = lists:split(length(Line)-1,Line),
                get_all_lines(File,[Strip|Partial])
    end.

% Show the contents of a list of strings.
% Can be used to check the results of calling get_file_contents.

show_file_contents([L|Ls]) ->
    io:format("~s~n",[L]),
    show_file_contents(Ls);
show_file_contents([]) ->
    ok.