Skip to content

Instantly share code, notes, and snippets.

@dduugg
Created May 15, 2020 22:54
Show Gist options
  • Save dduugg/55a591a584c3b2fe2de459feb24d886b to your computer and use it in GitHub Desktop.
Save dduugg/55a591a584c3b2fe2de459feb24d886b to your computer and use it in GitHub Desktop.
Functional Programming in Erlang - Programming challenge: Text processing
-module(text).
-export([get_file_contents/1, process/2, process/3,
show_file_contents/1, test/0]).
% 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.
process(Filename, ".VB") -> process(Filename, ".VB", 0).
process(Filename, Command, Length) ->
Lines = get_file_contents(Filename),
process_file_contents(Lines, Command, Length).
process_file_contents(Lines, Command, Length) ->
Processed = case Command of
".VB" -> Lines;
".CV" -> center_lines(Lines, Length);
".LP" -> fill_lines(Lines, Length, left);
".RP" -> fill_lines(Lines, Length, right);
".JU" ->
justify_lines(fill_lines(Lines, Length, left), Length);
_ -> erlang:error(badarg)
end,
string:join(Processed, "\n").
process_file_contents_test() ->
" a b \nc d " =
process_file_contents(string:tokens(" a b \nc d ",
"\n"),
".VB", 0),
" a b \n "
" c d " =
process_file_contents(string:tokens(" a b \nc d ",
"\n"),
".CV", 35),
"a b c d" =
process_file_contents(string:tokens(" a b \n c d ",
"\n"),
".LP", 35),
" a b c d" =
process_file_contents(string:tokens(" a b \n c d ",
"\n"),
".RP", 35),
"a b c d" =
process_file_contents(string:tokens(" a b \n c d ",
"\n"),
".JU", 35),
ok.
% similar to string:centre, but does not truncate strings that exceed Length
center_lines([], _) -> [];
center_lines([Line | Lines], Length) ->
Centered = case length(Line) < Length of
true -> string:centre(Line, Length);
false -> Line
end,
% erlang:display(Centered),
[Centered] ++ center_lines(Lines, Length).
% break lines individual words, then call fill_lines/5
fill_lines(Lines, Length, Direction) ->
Words = string:tokens(string:join(Lines, " "), " "),
fill_lines(Words, Length, Direction, "", []).
% takes a list of words, line length, direction to justify, in-progress line, and filled lines
% returns a list of justified lines
fill_lines([], Length, Direction, Line, Filled) ->
LastLine = case Direction of
left -> lists:droplast(Line);
right -> string:right(lists:droplast(Line), Length);
_ -> erlang:error(badarg)
end,
Filled ++ [LastLine];
fill_lines([Word | Words], Length, Direction, Line,
Filled) ->
% erlang:display([Word, Words, Line, Filled]),
case length(Line ++ Word ++ " ") =< Length of
true ->
fill_lines(Words, Length, Direction,
Line ++ Word ++ " ", Filled);
false ->
case Direction of
left ->
fill_lines(Words, Length, Direction, Word ++ " ",
Filled ++ [lists:droplast(Line)]);
right ->
fill_lines(Words, Length, Direction, Word ++ " ",
Filled ++
[string:right(lists:droplast(Line), Length)]);
_ -> erlang:error(badarg)
end
end.
justify_lines([], _) -> [];
justify_lines([Line | Lines], Length) ->
Words = string:tokens(Line, " "),
[justify(Words, Length)] ++
justify_lines(Lines, Length).
justify([], _) -> [];
justify([Word], _) -> Word;
justify([Word | Words], Length) ->
NumGaps = length([Word | Words]) - 1,
NumSpaces = Length - sum_lengths([Word | Words]),
PaddedWord = string:left(Word,
length(Word) + round(ceil(NumSpaces / NumGaps))),
PaddedWord ++
justify(Words, Length - length(PaddedWord)).
justify_test() ->
"dark corners and waiting out the" = justify(["dark",
"corners", "and",
"waiting", "out", "the"],
35).
sum_lengths([]) -> 0;
sum_lengths([Word | Words]) ->
length(Word) + sum_lengths(Words).
sum_lengths_test() -> 10 = sum_lengths(["hi", "bye"]).
test() ->
justify_test(),
process_file_contents_test(),
sum_lengths_test(),
ok.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment