histogram-rb 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 697a2cb2bd4da96b74ed8bbbea30058ab1b98c9e83cbaffec4656614152a9d7e
4
+ data.tar.gz: ad1aa054124f41c1e32626d452d5b187b3b154ad32ca722e65eb182a9bc8c0f0
5
+ SHA512:
6
+ metadata.gz: 196ec245d6ffacca8e9032e16fe37351b8eb21c9b48ef60c14c777f3830c6392de6dc2a8dcf64a6733fc4e0b1872b2477779cf54777bc2e424bd336a8a16ea53
7
+ data.tar.gz: 6baf9065d3154e1307875a14bbd49af4b7375fe954db26d517979ac453e459d0e81693648a7eaae75bb70425664707096fab0b9cbd4a3b14cc84840f152f1609
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ gem "rake", "~> 13.0"
9
+ gem "pry-byebug"
10
+ end
11
+
12
+ group :test do
13
+ gem "minitest", "~> 5.0"
14
+ gem "minitest-reporters"
15
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 derekstride
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Histogram
2
+ [![Ruby CI](https://github.com/DerekStride/histogram/actions/workflows/ruby.yml/badge.svg)](https://github.com/DerekStride/histogram/actions/workflows/ruby.yml) [![Publish new releases to rubygems](https://github.com/DerekStride/histogram/actions/workflows/publish-gem.yml/badge.svg)](https://github.com/DerekStride/histogram/actions/workflows/publish-gem.yml)
3
+
4
+ Easily create a histogram from an array of values and allow easy rendering to the terminal.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'histogram-rb'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install histogram-rb
21
+
22
+ ## Usage
23
+
24
+ Initialize a Histogram with a list of values:
25
+
26
+ ```ruby
27
+ histogram = Histogram.new(1000.times.map { Random.rand })
28
+ ```
29
+
30
+ Then call render to get a print-friendly string, all parameters are optional.
31
+
32
+ ```ruby
33
+ histogram.render(
34
+ title: "Uniform Distribution",
35
+ precision: 2,
36
+ )
37
+ ```
38
+
39
+ ```
40
+ Uniform Distribution
41
+ ═════════════════════════════════════════════════════════════════════════════════
42
+ x x x x x x
43
+ x x x x x x x
44
+ x x x x x x x
45
+ x x x x x x x
46
+ x x x x x x x
47
+ x x x x x x x
48
+ x x x x x x x
49
+ x x x x x x x
50
+ x x x x x x x
51
+ x x x x x x x
52
+ x x x x x x x
53
+ x x x x x x x
54
+ ─────────────────────────────────────────────────────────────────────────────────
55
+ 0.0-0.14 0.14-0.29 0.29-0.43 0.43-0.57 0.57-0.71 0.71-0.86 0.86-1.0
56
+ (150) (128) (154) (145) (136) (150) (137)
57
+ ```
58
+
59
+ You can use the `border` parameter to specify a `terminal-table` border class to change the output. e.g.
60
+
61
+ ```ruby
62
+ histogram.render(
63
+ title: "Normal Distribution",
64
+ precision: 2,
65
+ border: Terminal::Table::AsciiBorder.new,
66
+ )
67
+ ```
68
+
69
+ ```
70
+ Normal Distribution
71
+ -----------+----------+----------+-----------+-----------+-----------+-----------
72
+ x
73
+ x
74
+ x x
75
+ x x
76
+ x x x
77
+ x x x x
78
+ x x x x
79
+ x x x x x
80
+ x x x x x
81
+ x x x x x x x
82
+ x x x x x x x
83
+ x x x x x x x
84
+ -----------+----------+----------+-----------+-----------+-----------+-----------
85
+ 0.02-0.16 0.16-0.3 0.3-0.44 0.44-0.57 0.57-0.71 0.71-0.85 0.85-0.98
86
+ (26) (51) (108) (141) (81) (73) (24)
87
+ ```
88
+ ## Development
89
+
90
+ After checking out the repo, run `bundle install` to install dependencies. You can also run `bin/console` for an
91
+ interactive prompt that will allow you to experiment.
92
+
93
+ ## Testing
94
+
95
+ Running the unit tests with `bundle exec rake test`.
96
+
97
+ The rendering portion of the histogram is harder to test with unit tests. Instead there is a script that will allow you
98
+ to render histograms easily to enable visual tests.
99
+
100
+ Examples:
101
+
102
+ `bin/test`
103
+ `bin/test -b ascii -p 2`
104
+ `bin/test -t "Example Title" -b thick -p 2`
105
+ `bin/test -t "Example Title" -b ascii -p 3`
106
+ `bin/test -t "Normal Distribution" -d normal -p 2 -c 10000`
107
+
108
+ ## License
109
+
110
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
data/histogram.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/histogram/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "histogram-rb"
7
+ spec.version = Histogram::VERSION
8
+ spec.authors = ["derekstride"]
9
+ spec.email = ["derek@stride.host"]
10
+
11
+ spec.summary = "Easily convert an array of values into a histogram."
12
+ spec.description = "Easily convert an array of values into a histogram."
13
+ spec.homepage = "https://github.com/derekstride/histogram"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject do |f|
21
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
22
+ end
23
+ end
24
+
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_dependency("terminal-table")
30
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Bin = Struct.new(:range, :count)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinCounter
4
+ extend self
5
+
6
+ def evaluate(values)
7
+ min, max = values.minmax
8
+
9
+ candidate =
10
+ if max - min <= 12 && (max.integer? || min.integer?)
11
+ (max - min).ceil
12
+ elsif max - min <= 1
13
+ 10
14
+ elsif max - min <= 12
15
+ 12
16
+ elsif Math.sqrt(max - min) <= 12
17
+ Math.sqrt(max - min).ceil
18
+ end
19
+
20
+ candidate || [Math.sqrt(values.size).ceil, 12].min
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BinPacker
4
+ attr_reader :bin_count, :min, :max
5
+
6
+ def initialize(bin_count, min, max)
7
+ @bin_count = bin_count
8
+ @max = max
9
+ @min = min
10
+ end
11
+
12
+ def bins(values)
13
+ groups = Array.new(bin_count) do |i|
14
+ f = min + i * bin_width
15
+ range_max = if f.is_a?(Integer)
16
+ f + bin_width - 1
17
+ else
18
+ f + bin_width.prev_float
19
+ end
20
+
21
+ Bin.new(f..range_max, 0)
22
+ end
23
+ binmax = groups.last.range.max
24
+ unless binmax >= max
25
+ groups << if bin_width == 1
26
+ Bin.new(max..max, 0)
27
+ else
28
+ Bin.new(binmax..(binmax + bin_width), 0)
29
+ end
30
+ end
31
+
32
+ values.each do |v|
33
+ bin = groups.detect { |bin| bin.range.include?(v) }
34
+ bin.count += 1
35
+ end
36
+
37
+ groups
38
+ end
39
+
40
+ def bin_width
41
+ if max.is_a?(Float) || min.is_a?(Float)
42
+ (max - min) / bin_count.to_f
43
+ else
44
+ ((max - min) / bin_count.to_f).ceil
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Histogram
4
+ VERSION = "0.1.1"
5
+ end
data/lib/histogram.rb ADDED
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "histogram/bin"
4
+ require "histogram/bin_packer"
5
+ require "histogram/bin_counter"
6
+ require "terminal-table"
7
+
8
+ class Histogram
9
+ class Error < StandardError; end
10
+
11
+ attr_reader :bin_count
12
+
13
+ def initialize(values)
14
+ @values = values
15
+ @bin_count = BinCounter.evaluate(values)
16
+ @bin_packer = BinPacker.new(@bin_count, values.min, values.max)
17
+ end
18
+
19
+ def bin_width
20
+ @bin_packer.bin_width
21
+ end
22
+
23
+ def bins
24
+ @bins ||= @bin_packer.bins(values)
25
+ end
26
+
27
+ def render(title: nil, precision: 2, border: Terminal::Table::UnicodeBorder.new)
28
+ largest_bin = bins.max_by(&:count).count
29
+ max_marker_count = [largest_bin, 12].min
30
+ marker_width = largest_bin / max_marker_count
31
+
32
+ rows = max_marker_count.times.map do |y|
33
+ bins.size.times.map do |x|
34
+ value = if (bins[x].count / marker_width.to_f).ceil > y
35
+ "x"
36
+ else
37
+ ""
38
+ end
39
+ { value: value, alignment: :center }
40
+ end
41
+ end.reverse
42
+ rows << bins.map { |bin| "(#{bin.count})" }
43
+
44
+ border.top = false
45
+ border.left = false
46
+ border.right = false
47
+ border.data[:y] = " "
48
+ border.data[:n] = border.data[:nx]
49
+ border.data[:ai] = border.data[:ax]
50
+ border.data[:s] = border.data[:sx]
51
+
52
+ headings = bins.map do |bin|
53
+ min, max = bin.range.minmax
54
+ if min == max
55
+ min.to_s
56
+ else
57
+ "#{min.round(precision)}-#{max.round(precision)}"
58
+ end
59
+ end
60
+ options = {
61
+ headings: headings,
62
+ rows: rows,
63
+ style: {
64
+ border: border,
65
+ },
66
+ }
67
+ options[:title] = title if title
68
+
69
+ output = Terminal::Table.new(**options).to_s
70
+
71
+ lines = output.lines
72
+ lines.delete_at(1) # delete header seperator
73
+ header = if title
74
+ lines.delete_at(1)
75
+ else
76
+ lines.shift
77
+ end
78
+ totals = lines.delete_at(-2)
79
+ lines.last << "\n"
80
+ lines << header
81
+ lines << totals.chomp
82
+ lines.join
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :values, :title
88
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: histogram-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - derekstride
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: terminal-table
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Easily convert an array of values into a histogram.
28
+ email:
29
+ - derek@stride.host
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - LICENSE.txt
36
+ - README.md
37
+ - Rakefile
38
+ - histogram.gemspec
39
+ - lib/histogram.rb
40
+ - lib/histogram/bin.rb
41
+ - lib/histogram/bin_counter.rb
42
+ - lib/histogram/bin_packer.rb
43
+ - lib/histogram/version.rb
44
+ homepage: https://github.com/derekstride/histogram
45
+ licenses:
46
+ - MIT
47
+ metadata:
48
+ homepage_uri: https://github.com/derekstride/histogram
49
+ source_code_uri: https://github.com/derekstride/histogram
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.3.7
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Easily convert an array of values into a histogram.
69
+ test_files: []