-
Notifications
You must be signed in to change notification settings - Fork 0
/
histogram.exs
executable file
·94 lines (76 loc) · 2.59 KB
/
histogram.exs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/usr/bin/env elixir
defmodule Lab05 do
@doc """
My take on [the histogram exercise]
(http://computing.southern.edu/halterman/Courses/Fall2013/124/Labs/lab05_F13.html)
mentioned in [this newsgroup post]
(https://groups.google.com/d/msg/elixir-lang-talk/TTSjV0iy9yA/hpiGDZOk6DkJ)
"""
def main(), do: parse_pars(System.argv, nil)
defp parse_pars([], nil), do: histogram()
defp parse_pars(["-h"|_t], _acc), do: show_help
defp parse_pars(["--help"|_t], _acc), do: show_help
defp parse_pars(x, _acc) do
IO.puts "unexpected parameter: #{x}"
show_help
end
defp show_help() do
IO.puts """
Usage:
lab05
Options:
-h, [--help] # Show this help message and quit.
Description:
Read integers from stdin and display a histogram.
In the default, input may be terminated by a negative number
or by end-of-file (Ctrl-D).
All bits that are not parseable as integers are ignored.
This enables culling of integers from documents containing
other text.
"""
end
defp histogram() do
IO.stream(:stdio, :line)
|> Transform.extract_numbers()
|> Enum.take_while(&(&1 >=0))
|> Transform.count_ranges
|> Output.bar_chart
end
end
defmodule Transform do
def extract_numbers(stream), do: Stream.flat_map(stream, &extract_numbers_from_line/1)
defp extract_numbers_from_line(line) do
Regex.scan( %r/(\-)?\d+/, line)
|> Stream.map(&hd/1)
|> Stream.map(&String.to_integer/1)
|> Stream.reject(&(&1 == nil))
|> Stream.map(&elem(&1, 0))
end
def count_ranges(numbers), do: Enum.reduce(numbers, init_dict, &inc_count/2)
defp init_dict(), do: Enum.map(0..5, &{get_key(&1 * 100), 0}) |> HashDict.new
defp inc_count(nr, dict), do: Dict.update(dict, get_key(nr), 0, &(&1 + 1))
defp get_key(nr) when nr < 500 do
start = div(nr, 100) * 100
sp = nr < 100 && " " || ""
"#{start}-#{sp}#{start + 99}"
end
defp get_key(_), do: "500+ "
end
defmodule Output do
def bar_chart(data, max_bar_length // 50) do
max_count = Enum.max(Dict.values(data) ++ [1])
scale = max_bar_length / max_count
Enum.each data, &print_bar(&1, scale)
IO.puts ""
end
defp print_bar({key, value}, scale) do
bar = repeat("*", round(value * scale))
c = String.at(key, 0)
sp1 = (c < "1" || c > "4") && " " || ""
sp2 = value < 10 && " " || ""
IO.puts " #{sp1}#{key} #{sp2}#{value}|#{bar}"
end
defp repeat(_char, 0), do: ""
defp repeat(char, len), do: 1..len |> Enum.map_join fn _ -> "#{char}" end
end
Lab05.main()