everyday-cli-utils 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f620a7c1e67d6366c45323874f2346bf710ce9b6
4
- data.tar.gz: 96c78fb64efef0e03dc1bf26b6f1d8a9e7b2d49f
3
+ metadata.gz: ec3b8b4933935c4c8f32696a300d9e03b10aeb0a
4
+ data.tar.gz: 6af1473a8f38fabec5097d9666a6972cd728834f
5
5
  SHA512:
6
- metadata.gz: 5982ac1f4c14e8321e82b9b5c5d194226cd5721f08148d09d896c5d5e17edc6bd29ef450df64f3bc510988da0778a95822531829eff1fc20d3563c15f364564d
7
- data.tar.gz: a54b86c5ca17e02dab387bfcf12550b2c32b34effa6b3d762e06ed124a891bb5980e3ca05da192445230064e41d87032d8e59573a20381cd413e9d2fd0c04f24
6
+ metadata.gz: a56b940fd967714110f57b70e47b1178ce069685cf03fba4d55cc9859e23d0f284fab303c5d0eb6d6f22926e2ede774f04fc317d21a45277743b8dfa40628290
7
+ data.tar.gz: b52ad9d742d1cc507cde63bbb7ae6bd270a268f999ccdeaa022d7d5505fe0a8a052580d3e7a87502039f97c525857d48ed0c73a90e9baf58045c8156fb76c4a2
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # EverydayCliUtils
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/everyday-cli-utils.png)](http://badge.fury.io/rb/everyday-cli-utils)
3
4
  [![Build Status](https://travis-ci.org/henderea/everyday-cli-utils.png?branch=master)](https://travis-ci.org/henderea/everyday-cli-utils)
5
+ [![Dependency Status](https://gemnasium.com/henderea/everyday-cli-utils.png)](https://gemnasium.com/henderea/everyday-cli-utils)
4
6
  [![Code Climate](https://codeclimate.com/github/henderea/everyday-cli-utils.png)](https://codeclimate.com/github/henderea/everyday-cli-utils)
5
7
  [![Coverage Status](https://coveralls.io/repos/henderea/everyday-cli-utils/badge.png?branch=master)](https://coveralls.io/r/henderea/everyday-cli-utils?branch=master)
6
8
 
@@ -1,38 +1,40 @@
1
- module CursesUtils
2
- COLOR_TO_CURSES = {
3
- :black => Curses::COLOR_BLACK,
4
- :red => Curses::COLOR_RED,
5
- :green => Curses::COLOR_GREEN,
6
- :yellow => Curses::COLOR_YELLOW,
7
- :blue => Curses::COLOR_BLUE,
8
- :purple => Curses::COLOR_MAGENTA,
9
- :cyan => Curses::COLOR_CYAN,
10
- :white => Curses::COLOR_WHITE,
11
- :none => -1,
12
- }
1
+ module EverydayCliUtils
2
+ module CursesUtils
3
+ COLOR_TO_CURSES = {
4
+ :black => Curses::COLOR_BLACK,
5
+ :red => Curses::COLOR_RED,
6
+ :green => Curses::COLOR_GREEN,
7
+ :yellow => Curses::COLOR_YELLOW,
8
+ :blue => Curses::COLOR_BLUE,
9
+ :purple => Curses::COLOR_MAGENTA,
10
+ :cyan => Curses::COLOR_CYAN,
11
+ :white => Curses::COLOR_WHITE,
12
+ :none => -1,
13
+ }
13
14
 
14
- def find_color(bgcolor, fgcolor)
15
- @colors.find_index { |v| v[0] == (fgcolor || :none) && v[1] == (bgcolor || :none) }
16
- end
15
+ def find_color(bgcolor, fgcolor)
16
+ @colors.find_index { |v| v[0] == (fgcolor || :none) && v[1] == (bgcolor || :none) }
17
+ end
17
18
 
18
- def add_color(bgcolor, fgcolor)
19
- Curses::init_pair(@colors.count + 1, COLOR_TO_CURSES[fgcolor || :none], COLOR_TO_CURSES[bgcolor || :none])
20
- ind = @colors.count + 1
21
- @colors << [fgcolor || :none, bgcolor || :none]
22
- ind
23
- end
19
+ def add_color(bgcolor, fgcolor)
20
+ Curses::init_pair(@colors.count + 1, COLOR_TO_CURSES[fgcolor || :none], COLOR_TO_CURSES[bgcolor || :none])
21
+ ind = @colors.count + 1
22
+ @colors << [fgcolor || :none, bgcolor || :none]
23
+ ind
24
+ end
24
25
 
25
26
 
26
- private
27
- def handle_color(fgcolor, bgcolor)
28
- return 0 if (fgcolor.nil? || fgcolor == :none) && (bgcolor.nil? || bgcolor == :none)
29
- ind = find_color(bgcolor, fgcolor)
30
- ind = ind.nil? ? add_color(bgcolor, fgcolor) : ind + 1
31
- Curses::color_pair(ind)
32
- end
27
+ private
28
+ def handle_color(fgcolor, bgcolor)
29
+ return 0 if (fgcolor.nil? || fgcolor == :none) && (bgcolor.nil? || bgcolor == :none)
30
+ ind = find_color(bgcolor, fgcolor)
31
+ ind = ind.nil? ? add_color(bgcolor, fgcolor) : ind + 1
32
+ Curses::color_pair(ind)
33
+ end
33
34
 
34
- def get_format(str)
35
- bold, underline, fgcolor, bgcolor = Format::parse_format(str)
36
- (bold ? Curses::A_BOLD : 0) | (underline ? Curses::A_UNDERLINE : 0) | handle_color(fgcolor, bgcolor)
35
+ def get_format(str)
36
+ bold, underline, fgcolor, bgcolor = Format::parse_format(str)
37
+ (bold ? Curses::A_BOLD : 0) | (underline ? Curses::A_UNDERLINE : 0) | handle_color(fgcolor, bgcolor)
38
+ end
37
39
  end
38
- end
40
+ end
@@ -1,110 +1,4 @@
1
- module EverydayCliUtils
2
- module Format
3
- def self.build_format_hash(first_chr)
4
- {
5
- :black => "#{first_chr}0",
6
- :red => "#{first_chr}1",
7
- :green => "#{first_chr}2",
8
- :yellow => "#{first_chr}3",
9
- :blue => "#{first_chr}4",
10
- :purple => "#{first_chr}5",
11
- :cyan => "#{first_chr}6",
12
- :white => "#{first_chr}7",
13
- :none => nil,
14
- }
15
- end
16
-
17
- FORMAT_TO_CODE = {
18
- :bold => '1',
19
- :underline => '4',
20
- }
21
- FG_COLOR_TO_CODE = build_format_hash('3')
22
- BG_COLOR_TO_CODE = build_format_hash('4')
23
-
24
- def self::format(text, format_code)
25
- (format_code.nil? || format_code == '') ? text : "\e[#{format_code}m#{text}\e[0m"
26
- end
27
-
28
- def self::build_string(bold, underline, fgcolor, bgcolor)
29
- str = ''
30
- hit = false
31
- hit, str = handle_bold(bold, hit, str)
32
- hit, str = handle_underline(hit, str, underline)
33
- hit, str = handle_fg_color(fgcolor, hit, str)
34
- handle_bg_color(bgcolor, hit, str)
35
- end
36
-
37
- def self.handle_bold(bold, hit, str)
38
- if bold
39
- hit = true
40
- str = FORMAT_TO_CODE[:bold]
41
- end
42
- return hit, str
43
- end
44
-
45
- def self.handle_underline(hit, str, underline)
46
- if underline
47
- str += ';' if hit
48
- hit = true
49
- str += FORMAT_TO_CODE[:underline]
50
- end
51
- return hit, str
52
- end
53
-
54
- def self.handle_fg_color(fgcolor, hit, str)
55
- unless fgcolor.nil? || FG_COLOR_TO_CODE[fgcolor].nil?
56
- str += ';' if hit
57
- hit = true
58
- str += FG_COLOR_TO_CODE[fgcolor]
59
- end
60
- return hit, str
61
- end
62
-
63
- def self.handle_bg_color(bgcolor, hit, str)
64
- unless bgcolor.nil? || BG_COLOR_TO_CODE[bgcolor].nil?
65
- str += ';' if hit
66
- str += BG_COLOR_TO_CODE[bgcolor]
67
- end
68
- str
69
- end
70
-
71
- def self::parse_format(str)
72
- parts = str.split(';')
73
- bold = false
74
- underline = false
75
- fgcolor = :none
76
- bgcolor = :none
77
- parts.each { |v|
78
- if v == FORMAT_TO_CODE[:bold]
79
- bold = true
80
- elsif v == FORMAT_TO_CODE[:underline]
81
- underline = true
82
- elsif v[0] == '3'
83
- fgcolor = FG_COLOR_TO_CODE.invert[v]
84
- elsif v[0] == '4'
85
- bgcolor = BG_COLOR_TO_CODE.invert[v]
86
- end
87
- }
88
- return bold, underline, fgcolor, bgcolor
89
- end
90
-
91
- def self::colorize(text, fgcolor = nil, bgcolor = nil)
92
- self::format(text, self::build_string(false, false, fgcolor, bgcolor))
93
- end
94
-
95
- def self::bold(text, fgcolor = nil, bgcolor = nil)
96
- self::format(text, self::build_string(true, false, fgcolor, bgcolor))
97
- end
98
-
99
- def self::underline(text, fgcolor = nil, bgcolor = nil)
100
- self::format(text, self::build_string(false, true, fgcolor, bgcolor))
101
- end
102
-
103
- def self::boldunderline(text, fgcolor = nil, bgcolor = nil)
104
- self::format(text, self::build_string(true, true, fgcolor, bgcolor))
105
- end
106
- end
107
- end
1
+ require_relative 'safe/format'
108
2
 
109
3
  class String
110
4
  alias :old_method_missing :method_missing
@@ -1,43 +1,7 @@
1
- require_relative 'maputil'
2
-
3
- module EverydayCliUtils
4
- class Histogram
5
- def self.setup(collection, height, width)
6
- mi = collection.min
7
- ma = collection.max
8
- diff = ma - mi
9
- step = diff.to_f / (width.to_f - 1)
10
- counts = Array.new(width, 0)
11
- collection.each { |v| counts[((v - mi).to_f / step.to_f).floor] += 1 }
12
- max_y = counts.max
13
- lines = Array.new(height) { ' ' * width }
14
- return counts, lines, max_y, mi, step
15
- end
16
-
17
- def self.add_graph(counts, height, lines, max_y, width)
18
- (0...width).each { |i|
19
- h = ((counts[i].to_f / max_y.to_f) * height.to_f).round
20
- ((height - h)...height).each { |j|
21
- lines[j][i] = '#'
22
- }
23
- if h == 0 && counts[i] > 0
24
- lines[height - 1][i] = '_'
25
- end
26
- }
27
- end
28
-
29
- def self.add_averages(height, ks, lines, mi, step, width)
30
- lines[height] = ' ' * width
31
- ks.each { |v| lines[height][((v - mi) / step).to_i] = '|' }
32
- end
33
- end
34
- end
1
+ require_relative 'safe/histogram'
35
2
 
36
3
  module Enumerable
37
4
  def histogram(ks = nil, width = 100, height = 50)
38
- counts, lines, max_y, mi, step = EverydayCliUtils::Histogram.setup(self, height, width)
39
- EverydayCliUtils::Histogram.add_graph(counts, height, lines, max_y, width)
40
- EverydayCliUtils::Histogram.add_averages(height, ks, lines, mi, step, width) unless ks.nil?
41
- lines
5
+ EverydayCliUtils::Histogram.histogram(self, ks, width, height)
42
6
  end
43
7
  end
@@ -1,154 +1,19 @@
1
- require_relative 'maputil'
2
-
3
- module EverydayCliUtils
4
- class Kmeans
5
- def self.normal(x, avg, std)
6
- exp = -(((x - avg) / std) ** 2.0) / 2.0
7
- ((Math.exp(exp) / (std * Math.sqrt(2.0 * Math::PI))))
8
- end
9
-
10
- def self.f_test(clusters, means, cnt, avg)
11
- cnt2 = clusters.count { |i| !i.empty? }
12
- ev = f_test_ev(avg, clusters, cnt2, means)
13
- uv = f_test_uv(clusters, cnt, cnt2, means)
14
- (ev / uv)
15
- end
16
-
17
- def self.f_test_ev(avg, clusters, cnt2, means)
18
- ev = 0.0
19
- (0...means.count).each { |i| ev += clusters[i].empty? ? 0.0 : clusters[i].count * ((means[i] - avg) ** 2.0) }
20
- ev / (cnt2 - 1.0)
21
- end
22
-
23
- def self.f_test_uv(clusters, cnt, cnt2, means)
24
- uv = 0.0
25
- (0...means.count).each { |i|
26
- unless clusters[i].empty?
27
- (0...clusters[i].count).each { |j|
28
- uv += (clusters[i][j] - means[i]) * (clusters[i][j] - means[i])
29
- }
30
- end
31
- }
32
- uv / (cnt - cnt2)
33
- end
34
-
35
- def self.f_test2(clusters, means, cnt)
36
- uv = 0.0
37
- cnt2 = clusters.count { |i| !i.empty? }
38
- (0...means.count).each { |i| uv += f_test2_calc(clusters, i, means, uv) unless clusters[i].empty? }
39
- (uv / (cnt - cnt2))
40
- end
41
-
42
- def self.f_test2_calc(clusters, i, means, uv)
43
- tmp = 0.0
44
- (0...clusters[i].count).each { |j|
45
- tmp += (clusters[i][j] - means[i]) ** 2.0
46
- }
47
- tmp /= clusters[i].count
48
- Math.sqrt(tmp)
49
- end
50
-
51
- def self.nmeans_setup_1(collection)
52
- su = collection.sum
53
- cnt = collection.count
54
- avg = su / cnt
55
- ks1 = collection.kmeans(1)
56
- return avg, cnt, ks1
57
- end
58
-
59
- def self.nmeans_setup_2(collection, avg, cnt, ks1)
60
- cso = collection.get_clusters(ks1)
61
- ft1 = f_test2(cso, ks1, cnt)
62
- ks = collection.kmeans(2)
63
- cs = collection.get_clusters(ks)
64
- ft = f_test(cs, ks, cnt, avg)
65
- ft2 = f_test2(cs, ks, cnt)
66
- return ft, ft1, ft2, ks
67
- end
68
-
69
- def self.run_nmean(collection, avg, cnt, ft, ft2, k, ks)
70
- kso = ks
71
- fto = ft
72
- fto2 = ft2
73
- ks = collection.kmeans(k)
74
- cs = collection.get_clusters(ks)
75
- ft = f_test(cs, ks, cnt, avg)
76
- ft2 = f_test2(cs, ks, cnt)
77
- return ft, ft2, fto, fto2, ks, kso
78
- end
79
-
80
- def self.run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
81
- (3..[max_k, cnt].min).each { |k|
82
- ft, ft2, fto, fto2, ks, kso = run_nmean(collection, avg, cnt, ft, ft2, k, ks)
83
- return kso if ((ft - fto) / fto) < threshold && fto2 < ft1
84
- }
85
- ft2 >= ft1 ? ks1 : ks
86
- end
87
-
88
- def self.run_kmean(collection, ks)
89
- kso = ks
90
- clusters = collection.get_clusters(kso)
91
- ks = []
92
- clusters.each_with_index { |val, key| ks[key] = (val.count <= 0) ? kso[key] : (val.sum / val.count) }
93
- ks.sort
94
- return kso, ks
95
- end
96
- end
97
- end
1
+ require_relative 'safe/kmeans'
98
2
 
99
3
  module Enumerable
100
4
  def outliers(sensitivity = 0.5, k = nil)
101
- ks = k.nil? ? nmeans : kmeans(k)
102
- cs = get_clusters(ks)
103
-
104
- outliers = []
105
-
106
- ks.each_with_index { |avg, i| outliers += find_outliers(avg, cs, i, sensitivity) }
107
- outliers
108
- end
109
-
110
- def find_outliers(avg, cs, i, sensitivity)
111
- csi = cs[i]
112
- std = csi.std_dev
113
- cnt = csi.count
114
- csi.select { |c| (EverydayCliUtils::Kmeans.normal(c, avg, std) * cnt) < sensitivity }
5
+ EverydayCliUtils::Kmeans.outliers(self, sensitivity, k)
115
6
  end
116
7
 
117
8
  def nmeans(max_k = 10, threshold = 0.05)
118
- collection = self.floats
119
- avg, cnt, ks1 = EverydayCliUtils::Kmeans.nmeans_setup_1(collection)
120
- return ks1 if cnt == 1
121
- ft, ft1, ft2, ks = EverydayCliUtils::Kmeans.nmeans_setup_2(collection, avg, cnt, ks1)
122
- EverydayCliUtils::Kmeans.run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
9
+ EverydayCliUtils::Kmeans.nmeans(self, max_k, threshold)
123
10
  end
124
11
 
125
12
  def kmeans(k)
126
- mi = min
127
- ma = max
128
- diff = ma - mi
129
- ks = []
130
- (1..k).each { |i| ks[i - 1] = mi + (i * (diff / (k + 1.0))) }
131
- kso = false
132
- while ks != kso
133
- kso, ks = EverydayCliUtils::Kmeans.run_kmean(self, ks)
134
- end
135
- ks
13
+ EverydayCliUtils::Kmeans.kmeans(self, k)
136
14
  end
137
15
 
138
16
  def get_clusters(means)
139
- clusters = Array.new(means.count) { Array.new }
140
- each { |item|
141
- cluster = false
142
- distance = false
143
- (0...means.count).each { |i|
144
- diff = (means[i] - item).abs
145
- if distance == false || diff < distance
146
- cluster = i
147
- distance = diff
148
- end
149
- }
150
- clusters[cluster][clusters[cluster].count] = item
151
- }
152
- clusters
17
+ EverydayCliUtils::Kmeans.get_clusters(self, means)
153
18
  end
154
19
  end
@@ -1,48 +1,47 @@
1
+ require_relative 'safe/maputil'
2
+
1
3
  module Enumerable
2
4
  def removefalse
3
- select { |i| i }
5
+ EverydayCliUtils::MapUtil.removefalse(self)
4
6
  end
5
7
 
6
8
  def filtermap(&block)
7
- map(&block).removefalse
9
+ EverydayCliUtils::MapUtil.filtermap(self, &block)
8
10
  end
9
11
 
10
12
  def sum
11
- reduce(:+)
13
+ EverydayCliUtils::MapUtil.sum(self)
12
14
  end
13
15
 
14
16
  def prod
15
- reduce(:*)
17
+ EverydayCliUtils::MapUtil.prod(self)
16
18
  end
17
19
 
18
20
  def average
19
- sum.to_f / count.to_f
21
+ EverydayCliUtils::MapUtil.average(self)
20
22
  end
21
23
 
22
24
  def std_dev
23
- avg = average
24
- cnt = count.to_f
25
- su = summap { |v| (v.to_f - avg.to_f) ** 2 }
26
- Math.sqrt(su / cnt)
25
+ EverydayCliUtils::MapUtil.std_dev(self)
27
26
  end
28
27
 
29
28
  def floats
30
- map(&:to_f)
29
+ EverydayCliUtils::MapUtil.floats(self)
31
30
  end
32
31
 
33
32
  def summap(&block)
34
- map(&block).sum
33
+ EverydayCliUtils::MapUtil.summap(self, &block)
35
34
  end
36
35
 
37
36
  def productmap(&block)
38
- map(&block).prod
37
+ EverydayCliUtils::MapUtil.productmap(self, &block)
39
38
  end
40
39
 
41
40
  def chompall
42
- map(&:chomp)
41
+ EverydayCliUtils::MapUtil.chompall(self)
43
42
  end
44
43
 
45
44
  def join(join_str)
46
- map(&:to_s).reduce { |a, b| a << join_str << b }
45
+ EverydayCliUtils::MapUtil.join(self, join_str)
47
46
  end
48
47
  end
@@ -0,0 +1,107 @@
1
+ module EverydayCliUtils
2
+ module Format
3
+ def self.build_format_hash(first_chr)
4
+ {
5
+ :black => "#{first_chr}0",
6
+ :red => "#{first_chr}1",
7
+ :green => "#{first_chr}2",
8
+ :yellow => "#{first_chr}3",
9
+ :blue => "#{first_chr}4",
10
+ :purple => "#{first_chr}5",
11
+ :cyan => "#{first_chr}6",
12
+ :white => "#{first_chr}7",
13
+ :none => nil,
14
+ }
15
+ end
16
+
17
+ FORMAT_TO_CODE = {
18
+ :bold => '1',
19
+ :underline => '4',
20
+ }
21
+ FG_COLOR_TO_CODE = build_format_hash('3')
22
+ BG_COLOR_TO_CODE = build_format_hash('4')
23
+
24
+ def self::format(text, format_code)
25
+ (format_code.nil? || format_code == '') ? text : "\e[#{format_code}m#{text}\e[0m"
26
+ end
27
+
28
+ def self::build_string(bold, underline, fgcolor, bgcolor)
29
+ str = ''
30
+ hit = false
31
+ hit, str = handle_bold(bold, hit, str)
32
+ hit, str = handle_underline(hit, str, underline)
33
+ hit, str = handle_fg_color(fgcolor, hit, str)
34
+ handle_bg_color(bgcolor, hit, str)
35
+ end
36
+
37
+ def self.handle_bold(bold, hit, str)
38
+ if bold
39
+ hit = true
40
+ str = FORMAT_TO_CODE[:bold]
41
+ end
42
+ return hit, str
43
+ end
44
+
45
+ def self.handle_underline(hit, str, underline)
46
+ if underline
47
+ str += ';' if hit
48
+ hit = true
49
+ str += FORMAT_TO_CODE[:underline]
50
+ end
51
+ return hit, str
52
+ end
53
+
54
+ def self.handle_fg_color(fgcolor, hit, str)
55
+ unless fgcolor.nil? || FG_COLOR_TO_CODE[fgcolor].nil?
56
+ str += ';' if hit
57
+ hit = true
58
+ str += FG_COLOR_TO_CODE[fgcolor]
59
+ end
60
+ return hit, str
61
+ end
62
+
63
+ def self.handle_bg_color(bgcolor, hit, str)
64
+ unless bgcolor.nil? || BG_COLOR_TO_CODE[bgcolor].nil?
65
+ str += ';' if hit
66
+ str += BG_COLOR_TO_CODE[bgcolor]
67
+ end
68
+ str
69
+ end
70
+
71
+ def self::parse_format(str)
72
+ parts = str.split(';')
73
+ bold = false
74
+ underline = false
75
+ fgcolor = :none
76
+ bgcolor = :none
77
+ parts.each { |v|
78
+ if v == FORMAT_TO_CODE[:bold]
79
+ bold = true
80
+ elsif v == FORMAT_TO_CODE[:underline]
81
+ underline = true
82
+ elsif v[0] == '3'
83
+ fgcolor = FG_COLOR_TO_CODE.invert[v]
84
+ elsif v[0] == '4'
85
+ bgcolor = BG_COLOR_TO_CODE.invert[v]
86
+ end
87
+ }
88
+ return bold, underline, fgcolor, bgcolor
89
+ end
90
+
91
+ def self::colorize(text, fgcolor = nil, bgcolor = nil)
92
+ self::format(text, self::build_string(false, false, fgcolor, bgcolor))
93
+ end
94
+
95
+ def self::bold(text, fgcolor = nil, bgcolor = nil)
96
+ self::format(text, self::build_string(true, false, fgcolor, bgcolor))
97
+ end
98
+
99
+ def self::underline(text, fgcolor = nil, bgcolor = nil)
100
+ self::format(text, self::build_string(false, true, fgcolor, bgcolor))
101
+ end
102
+
103
+ def self::boldunderline(text, fgcolor = nil, bgcolor = nil)
104
+ self::format(text, self::build_string(true, true, fgcolor, bgcolor))
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,39 @@
1
+ module EverydayCliUtils
2
+ class Histogram
3
+ def self.setup(collection, height, width)
4
+ mi = collection.min
5
+ ma = collection.max
6
+ diff = ma - mi
7
+ step = diff.to_f / (width.to_f - 1)
8
+ counts = Array.new(width, 0)
9
+ collection.each { |v| counts[((v - mi).to_f / step.to_f).floor] += 1 }
10
+ max_y = counts.max
11
+ lines = Array.new(height) { ' ' * width }
12
+ return counts, lines, max_y, mi, step
13
+ end
14
+
15
+ def self.add_graph(counts, height, lines, max_y, width)
16
+ (0...width).each { |i|
17
+ h = ((counts[i].to_f / max_y.to_f) * height.to_f).round
18
+ ((height - h)...height).each { |j|
19
+ lines[j][i] = '#'
20
+ }
21
+ if h == 0 && counts[i] > 0
22
+ lines[height - 1][i] = '_'
23
+ end
24
+ }
25
+ end
26
+
27
+ def self.add_averages(height, ks, lines, mi, step, width)
28
+ lines[height] = ' ' * width
29
+ ks.each { |v| lines[height][((v - mi) / step).to_i] = '|' }
30
+ end
31
+
32
+ def self.histogram(collection, ks = nil, width = 100, height = 50)
33
+ counts, lines, max_y, mi, step = setup(collection, height, width)
34
+ add_graph(counts, height, lines, max_y, width)
35
+ add_averages(height, ks, lines, mi, step, width) unless ks.nil?
36
+ lines
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,152 @@
1
+ require_relative 'maputil'
2
+
3
+ module EverydayCliUtils
4
+ module Kmeans
5
+ def self.normal(x, avg, std)
6
+ exp = -(((x - avg) / std) ** 2.0) / 2.0
7
+ ((Math.exp(exp) / (std * Math.sqrt(2.0 * Math::PI))))
8
+ end
9
+
10
+ def self.f_test(clusters, means, cnt, avg)
11
+ cnt2 = clusters.count { |i| !i.empty? }
12
+ ev = f_test_ev(avg, clusters, cnt2, means)
13
+ uv = f_test_uv(clusters, cnt, cnt2, means)
14
+ (ev / uv)
15
+ end
16
+
17
+ def self.f_test_ev(avg, clusters, cnt2, means)
18
+ ev = 0.0
19
+ (0...means.count).each { |i| ev += clusters[i].empty? ? 0.0 : clusters[i].count * ((means[i] - avg) ** 2.0) }
20
+ ev / (cnt2 - 1.0)
21
+ end
22
+
23
+ def self.f_test_uv(clusters, cnt, cnt2, means)
24
+ uv = 0.0
25
+ (0...means.count).each { |i|
26
+ unless clusters[i].empty?
27
+ (0...clusters[i].count).each { |j|
28
+ uv += (clusters[i][j] - means[i]) * (clusters[i][j] - means[i])
29
+ }
30
+ end
31
+ }
32
+ uv / (cnt - cnt2)
33
+ end
34
+
35
+ def self.f_test2(clusters, means, cnt)
36
+ uv = 0.0
37
+ cnt2 = clusters.count { |i| !i.empty? }
38
+ (0...means.count).each { |i| uv += f_test2_calc(clusters, i, means, uv) unless clusters[i].empty? }
39
+ (uv / (cnt - cnt2))
40
+ end
41
+
42
+ def self.f_test2_calc(clusters, i, means, uv)
43
+ tmp = 0.0
44
+ (0...clusters[i].count).each { |j|
45
+ tmp += (clusters[i][j] - means[i]) ** 2.0
46
+ }
47
+ tmp /= clusters[i].count
48
+ Math.sqrt(tmp)
49
+ end
50
+
51
+ def self.nmeans_setup_1(collection)
52
+ su = EverydayCliUtils::MapUtil.sum(collection)
53
+ cnt = collection.count
54
+ avg = su / cnt
55
+ ks1 = kmeans(collection, 1)
56
+ return avg, cnt, ks1
57
+ end
58
+
59
+ def self.nmeans_setup_2(collection, avg, cnt, ks1)
60
+ cso = get_clusters(collection, ks1)
61
+ ft1 = f_test2(cso, ks1, cnt)
62
+ ks = kmeans(collection, 2)
63
+ cs = get_clusters(collection, ks)
64
+ ft = f_test(cs, ks, cnt, avg)
65
+ ft2 = f_test2(cs, ks, cnt)
66
+ return ft, ft1, ft2, ks
67
+ end
68
+
69
+ def self.run_nmean(collection, avg, cnt, ft, ft2, k, ks)
70
+ kso = ks
71
+ fto = ft
72
+ fto2 = ft2
73
+ ks = kmeans(collection, k)
74
+ cs = get_clusters(collection, ks)
75
+ ft = f_test(cs, ks, cnt, avg)
76
+ ft2 = f_test2(cs, ks, cnt)
77
+ return ft, ft2, fto, fto2, ks, kso
78
+ end
79
+
80
+ def self.run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
81
+ (3..[max_k, cnt].min).each { |k|
82
+ ft, ft2, fto, fto2, ks, kso = run_nmean(collection, avg, cnt, ft, ft2, k, ks)
83
+ return kso if ((ft - fto) / fto) < threshold && fto2 < ft1
84
+ }
85
+ ft2 >= ft1 ? ks1 : ks
86
+ end
87
+
88
+ def self.run_kmean(collection, ks)
89
+ kso = ks
90
+ clusters = get_clusters(collection, kso)
91
+ ks = []
92
+ clusters.each_with_index { |val, key| ks[key] = (val.count <= 0) ? kso[key] : (val.sum / val.count) }
93
+ ks.sort
94
+ return kso, ks
95
+ end
96
+
97
+ def self.get_clusters(collection, means)
98
+ clusters = Array.new(means.count) { Array.new }
99
+ collection.each { |item|
100
+ cluster = false
101
+ distance = false
102
+ (0...means.count).each { |i|
103
+ diff = (means[i] - item).abs
104
+ if distance == false || diff < distance
105
+ cluster = i
106
+ distance = diff
107
+ end
108
+ }
109
+ clusters[cluster] << item
110
+ }
111
+ clusters
112
+ end
113
+
114
+ def self.kmeans(collection, k)
115
+ mi = collection.min
116
+ ma = collection.max
117
+ diff = ma - mi
118
+ ks = []
119
+ (1..k).each { |i| ks[i - 1] = mi + (i * (diff / (k + 1.0))) }
120
+ kso = false
121
+ while ks != kso
122
+ kso, ks = run_kmean(collection, ks)
123
+ end
124
+ ks
125
+ end
126
+
127
+ def self.nmeans(collection, max_k = 10, threshold = 0.05)
128
+ collection = EverydayCliUtils::MapUtil.floats(collection)
129
+ avg, cnt, ks1 = nmeans_setup_1(collection)
130
+ return ks1 if cnt == 1
131
+ ft, ft1, ft2, ks = nmeans_setup_2(collection, avg, cnt, ks1)
132
+ run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
133
+ end
134
+
135
+ def self.find_outliers(avg, cs, i, sensitivity)
136
+ csi = cs[i]
137
+ std = EverydayCliUtils::MapUtil.std_dev(csi)
138
+ cnt = csi.count
139
+ csi.select { |c| (normal(c, avg, std) * cnt) < sensitivity }
140
+ end
141
+
142
+ def self.outliers(collection, sensitivity = 0.5, k = nil)
143
+ ks = k.nil? ? nmeans(collection) : kmeans(collection, k)
144
+ cs = get_clusters(collection, ks)
145
+
146
+ outliers = []
147
+
148
+ ks.each_with_index { |avg, i| outliers += find_outliers(avg, cs, i, sensitivity) }
149
+ outliers
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,50 @@
1
+ module EverydayCliUtils
2
+ module MapUtil
3
+ def self.removefalse(collection)
4
+ collection.select { |i| i }
5
+ end
6
+
7
+ def self.filtermap(collection, &block)
8
+ removefalse(collection.map(&block))
9
+ end
10
+
11
+ def self.sum(collection)
12
+ collection.reduce(:+)
13
+ end
14
+
15
+ def self.prod(collection)
16
+ collection.reduce(:*)
17
+ end
18
+
19
+ def self.average(collection)
20
+ sum(collection).to_f / collection.count.to_f
21
+ end
22
+
23
+ def self.std_dev(collection)
24
+ avg = average(collection)
25
+ cnt = collection.count.to_f
26
+ su = summap(collection) { |v| (v.to_f - avg.to_f) ** 2 }
27
+ Math.sqrt(su / cnt)
28
+ end
29
+
30
+ def self.floats(collection)
31
+ collection.map(&:to_f)
32
+ end
33
+
34
+ def self.summap(collection, &block)
35
+ sum(collection.map(&block))
36
+ end
37
+
38
+ def self.productmap(collection, &block)
39
+ prod(collection.map(&block))
40
+ end
41
+
42
+ def self.chompall(collection)
43
+ collection.map(&:chomp)
44
+ end
45
+
46
+ def self.join(collection, join_str)
47
+ collection.map(&:to_s).reduce { |a, b| a << join_str << b }
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module EverydayCliUtils
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: everyday-cli-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Henderson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-23 00:00:00.000000000 Z
11
+ date: 2014-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -120,6 +120,10 @@ files:
120
120
  - lib/everyday-cli-utils/maputil.rb
121
121
  - lib/everyday-cli-utils/mycurses.rb
122
122
  - lib/everyday-cli-utils/option.rb
123
+ - lib/everyday-cli-utils/safe/format.rb
124
+ - lib/everyday-cli-utils/safe/histogram.rb
125
+ - lib/everyday-cli-utils/safe/kmeans.rb
126
+ - lib/everyday-cli-utils/safe/maputil.rb
123
127
  - lib/everyday-cli-utils/version.rb
124
128
  - spec/everyday-cli-utils/format_spec.rb
125
129
  - spec/everyday-cli-utils/kmeans_spec.rb