color_sort 1.0.0

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
+ SHA1:
3
+ metadata.gz: 81e0d767639e5bb6b4ffd6915f0b4607ce65b05d
4
+ data.tar.gz: b399a98a8bcede7671af2d587e08bc5593a93bc7
5
+ SHA512:
6
+ metadata.gz: 0c090a1934e79feb4dc86f608b0fc61da779f30de431fc5973aaa3eaafb2b0fd639505c9158e67577cefd79d3e20d67b2dba64f3e7a22d05a725bf6462bd6f82
7
+ data.tar.gz: 0ad0740c0cdca320c4f10c10c24abf10f5db10f20623437b47ba082c000a3d1d5031dca9cbb8c0fedefb4726bd9970b7cd939c65b4e197f6bf68b7c1e4406e7b
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in color_sort.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Pip Taylor
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # ColorSort
2
+
3
+ ColorSort sorts an array of colors perceptually, using the
4
+ [CIEDE2000](http://en.wikipedia.org/wiki/Color_difference#CIEDE2000)
5
+ color distance function.
6
+
7
+ *Unsorted* | *Sorted*
8
+ ---------- | --------
9
+ ![200 unsorted lines of color](http://ms-digital-labs.github.io/unsorted.png) | ![200 sorted lines of color](http://ms-digital-labs.github.io/sorted.png)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'color_sort'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install color_sort
24
+
25
+ ## Usage
26
+
27
+ Call `ColorSort.sort` with an array of RGB colors in hex form (with or without preceeding #):
28
+
29
+ ```ruby
30
+ unsorted_colors = ["35c047", "7f40ed", "39ae1e", "5f9d4c", "94ec1e", "93e482"]
31
+
32
+ sorted_colors = ColorSort.sort(unsorted_colors)
33
+ # => ["94ec1e", "93e482", "39ae1e", "35c047", "5f9d4c", "7f40ed"]
34
+ ```
35
+
36
+ ## How it works
37
+
38
+ Often when dealing with colors on computers, we're working with the [RGB color model](http://en.wikipedia.org/wiki/Color_model#RGB_color_model). As each color has three components, we can think of it as a point in 3D space. This allows us to use the [Euclidean distance](http://en.wikipedia.org/wiki/Euclidean_distance) to see how far apart two colors are.
39
+
40
+ Unfortunately, the RGB color space doesn't model how people perceive colors, so a pair of colors that are close together in RGB space may actually look quite different, and vice-versa. We can solve this by using the [Lab color space](http://en.wikipedia.org/wiki/Lab_color_space) and [CIEDE2000 distance function](http://en.wikipedia.org/wiki/Color_difference#CIEDE2000). This color space and distance function are tuned to give a smaller distance for perceputally close colors.
41
+
42
+ Now we have a way of determining the perceptual distance between two colors, but we can't use this with a normal sorting algorithm because we can't define a [Total order](http://en.wikipedia.org/wiki/Total_order) over all colors. What we really need to do is find the shortest path through our colors in Lab color space, using the CIEDE2000 distance function.
43
+
44
+ This turns out to be the [Travelling salesman problem](http://en.wikipedia.org/wiki/Travelling_salesman_problem). We first tried the [Nearest neighbour](http://en.wikipedia.org/wiki/Nearest_neighbour_algorithm) heuristic, but found that it left ugly discontinuities in the sorted colors.
45
+
46
+ Next we tried [Simulated annealing](http://en.wikipedia.org/wiki/Simulated_annealing), but found that it didn't converge on a solution anywhere near quickly enough to be useful.
47
+
48
+ The solution we settled on was to iteratively build of a list of colors, inserting each one in to the list at the point at which it causes the lowest increase in the total distance between all the colors currently in the list. This allows us to sort a few hundred colors in a couple of seconds, and produces visually pleasing results.
49
+
50
+ #### Other approaches
51
+
52
+ Wikipedia lists [several other approaches for solving TSP](http://en.wikipedia.org/wiki/Travelling_salesman_problem#Computing_a_solution) - both exact solutions and approximate solutions. We didn't explore these because we couldn't find libraries that implemented them and didn't have the time to implement them ourselves.
53
+
54
+ It's almost certain that there's a better approach to solving this particular case of TSP, but we ran out of time/motivation to investigate further. If you know a better approach, please let us know!
55
+
56
+ ## Performance
57
+
58
+ The algorithm used by this gem has complexity O(n<sup>2</sup>) in the size of the list being sorted. Some example timings on a MacBook Pro:
59
+
60
+ Number of colors | Time (seconds)
61
+ ---------------- | --------------
62
+ 100 | 0.11
63
+ 200 | 0.37
64
+ 300 | 0.72
65
+ 500 | 2.14
66
+ 1000 | 9.01
67
+
68
+ ## A note on naming
69
+
70
+ This gem was authored in the UK, where color is usually spelt colour. However, this gem depends on two others ([color](http://color.rubyforge.org/) and [colorscore](https://github.com/quadule/colorscore)) that spell it without the u, so for the sake of consistency it's spelt as color in this gem too.
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it ( https://github.com/ms-digital-labs/color_sort/fork )
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'color_sort/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "color_sort"
8
+ spec.version = ColorSort::VERSION
9
+ spec.authors = ["Pip Taylor"]
10
+ spec.email = ["pip@evilgeek.co.uk"]
11
+ spec.summary = %q{Sorts colors perceptually}
12
+ spec.homepage = "https://github.com/ms-digital-labs/color_sort"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "colorscore"
21
+ spec.add_dependency "color"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rspec-radar"
27
+ end
@@ -0,0 +1,8 @@
1
+ module ColorSort
2
+ module ColorSpaceConverter
3
+ def self.hex_rgb_to_lab(hex)
4
+ rgb = Color::RGB.from_html(hex)
5
+ Colorscore::Metrics.xyz_to_lab(*Colorscore::Metrics.rgb_to_xyz(rgb))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ require "colorscore"
2
+
3
+ module ColorSort
4
+ module Distance
5
+ def self.ciede2000(lab_color_a, lab_color_b)
6
+ cache_key = [lab_color_a, lab_color_b].sort
7
+ ciede2000_cache[cache_key] ||=
8
+ Colorscore::Metrics.delta_e_cie_2000(*lab_color_a, *lab_color_b)
9
+ end
10
+
11
+ private
12
+ def self.ciede2000_cache
13
+ @ciede2000_cache ||= {}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ require "color_sort/distance"
2
+
3
+ module ColorSort
4
+ class Ordering
5
+ attr_reader :colors
6
+
7
+ def initialize
8
+ @colors = []
9
+ end
10
+
11
+ def add_lab_color(color)
12
+ if colors.size < 2
13
+ colors << color
14
+ else
15
+ min_delta = distance(color, colors.first)
16
+ min_delta_index = 0
17
+
18
+ (1...colors.size).each do |i|
19
+ remove_distance = distance(colors[i-1], colors[i])
20
+ add_distance = distance(colors[i-1], color) + distance(color, colors[i])
21
+ delta = add_distance - remove_distance
22
+ if delta < min_delta
23
+ min_delta = delta
24
+ min_delta_index = i
25
+ end
26
+ end
27
+
28
+ if distance(colors.last, color) < min_delta
29
+ min_delta_index = colors.size
30
+ end
31
+
32
+ colors.insert(min_delta_index, color)
33
+ end
34
+ end
35
+
36
+ private
37
+ def distance(color_a, color_b)
38
+ ColorSort::Distance.ciede2000(color_a, color_b)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ module ColorSort
2
+ class Sorter
3
+ def initialize(colors)
4
+ @colors = colors
5
+ end
6
+
7
+ def sorted
8
+ ordering.colors.map { |lab_color|
9
+ color_map[lab_color]
10
+ }
11
+ end
12
+
13
+ private
14
+ attr_reader :colors
15
+
16
+ def color_map
17
+ @color_map ||= colors.each_with_object({}) { |hex_rgb_color, hash|
18
+ lab_color = ColorSort::ColorSpaceConverter.hex_rgb_to_lab(hex_rgb_color)
19
+ hash[lab_color] = hex_rgb_color
20
+ }
21
+ end
22
+
23
+ def lab_colors
24
+ @lab_colors ||= colors.map { |hex_rgb_color|
25
+ ColorSort::ColorSpaceConverter.hex_rgb_to_lab(hex_rgb_color)
26
+ }
27
+ end
28
+
29
+ def ordering
30
+ @ordering ||= lab_colors.each_with_object(Ordering.new) { |lab_color, ordering|
31
+ ordering.add_lab_color(lab_color)
32
+ }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module ColorSort
2
+ VERSION = "1.0.0"
3
+ end
data/lib/color_sort.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "color_sort/version"
2
+ require "color_sort/ordering"
3
+ require "color_sort/distance"
4
+ require "color_sort/color_space_converter"
5
+ require "color_sort/sorter"
6
+
7
+ module ColorSort
8
+ def self.sort(colors)
9
+ Sorter.new(colors).sorted
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe ColorSort do
4
+ it 'should have a version number' do
5
+ ColorSort::VERSION.should_not be_nil
6
+ end
7
+
8
+ describe ".sort" do
9
+ it "passes the colors to ColorSort::Sorter and calls #sorted" do
10
+ expect(ColorSort.sort(["#123456"])).to eq(["#123456"])
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe ColorSort::ColorSpaceConverter do
4
+ describe "#hex_rgb_to_lab" do
5
+ subject { ColorSort::ColorSpaceConverter }
6
+
7
+ let(:expected_lab_result) {
8
+ [21.041610053894317, 1.0523062624117063, -24.099168114444936]
9
+ }
10
+
11
+ context "with a preceeding #" do
12
+ it "converts hex RGB color to LAB" do
13
+ expect(subject.hex_rgb_to_lab("#123456")).to eq(expected_lab_result)
14
+ end
15
+ end
16
+
17
+ context "with a preceeding #" do
18
+ it "converts hex RGB color to LAB" do
19
+ expect(subject.hex_rgb_to_lab("123456")).to eq(expected_lab_result)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe ColorSort::Distance do
4
+ describe "#ciede2000" do
5
+ let(:distance) { double(:distance) }
6
+
7
+ it "splats the lab colors and passes them to Colorscore" do
8
+ expect(Colorscore::Metrics).to receive(:delta_e_cie_2000)
9
+ .with(1, 2, 3, 4, 5, 6)
10
+
11
+ ColorSort::Distance.ciede2000([1, 2, 3], [4, 5, 6])
12
+ end
13
+
14
+ it "only calls in to Colorscore once for each pair of colors" do
15
+ expect(Colorscore::Metrics).to receive(:delta_e_cie_2000)
16
+ .with(1, 2, 3, 4, 5, 6)
17
+ .once
18
+ .and_return(distance)
19
+
20
+ expect(ColorSort::Distance.ciede2000([1, 2, 3], [4, 5, 6])).to eq(distance)
21
+ expect(ColorSort::Distance.ciede2000([4, 5, 6], [1, 2, 3])).to eq(distance)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+
3
+ describe ColorSort::Ordering do
4
+ let(:red) { double(:red) }
5
+ let(:green) { double(:green) }
6
+ let(:blue) { double(:blue) }
7
+
8
+ before do
9
+ set_distance(red, red, 0)
10
+ set_distance(green, green, 0)
11
+ set_distance(blue, blue, 0)
12
+
13
+ set_distance(red, blue, 1)
14
+ set_distance(blue, green, 2)
15
+ set_distance(red, green, 3)
16
+ end
17
+
18
+ describe "#add_lab_color" do
19
+ context "with an empty ordering" do
20
+ it "adds the color to the end of the array" do
21
+ subject.add_lab_color(red)
22
+
23
+ expect(subject.colors).to eq([red])
24
+ end
25
+ end
26
+
27
+ context "with one color in the ordering" do
28
+ it "adds the color to the end of the array" do
29
+ subject.add_lab_color(red)
30
+ subject.add_lab_color(green)
31
+
32
+ expect(subject.colors).to eq([red, green])
33
+ end
34
+ end
35
+
36
+ context "with multiple colors in the ordering" do
37
+ context "best position is at the beginning" do
38
+ it "adds the color in the position that minimises total distance" do
39
+ subject.add_lab_color(blue)
40
+ subject.add_lab_color(green)
41
+ subject.add_lab_color(red)
42
+
43
+ expect(subject.colors).to eq([red, blue, green])
44
+ end
45
+ end
46
+
47
+ context "best position is in the middle" do
48
+ it "adds the color in the position that minimises total distance" do
49
+ subject.add_lab_color(red)
50
+ subject.add_lab_color(green)
51
+ subject.add_lab_color(blue)
52
+
53
+ expect(subject.colors).to eq([red, blue, green])
54
+ end
55
+ end
56
+
57
+ context "best position is at the end" do
58
+ it "adds the color in the position that minimises total distance" do
59
+ subject.add_lab_color(green)
60
+ subject.add_lab_color(blue)
61
+ subject.add_lab_color(red)
62
+
63
+ expect(subject.colors).to eq([green, blue, red])
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def set_distance(color_a, color_b, distance)
70
+ allow(ColorSort::Distance).to receive(:ciede2000)
71
+ .with(color_a, color_b)
72
+ .and_return(distance)
73
+ allow(ColorSort::Distance).to receive(:ciede2000)
74
+ .with(color_b, color_a)
75
+ .and_return(distance)
76
+ end
77
+ end
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+
3
+ describe ColorSort::Sorter do
4
+ describe "#sorted" do
5
+ let(:red_1) { "#ff0000" }
6
+ let(:red_2) { "#cc0000" }
7
+ let(:green_1) { "#00ff00" }
8
+ let(:green_2) { "#00cc00" }
9
+ let(:blue_1) { "#0000ff" }
10
+ let(:blue_2) { "#0000cc" }
11
+
12
+ let(:colors) { [red_1, blue_1, green_1, red_2, blue_2, green_2] }
13
+
14
+ subject { described_class.new(colors) }
15
+
16
+ it "orders the colors perceptually" do
17
+ expect(subject.sorted).to eq([red_1, red_2, blue_2, blue_1, green_2, green_1])
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'color_sort'
3
+ require "rspec/radar"
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: color_sort
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Pip Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorscore
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
+ - !ruby/object:Gem::Dependency
28
+ name: color
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-radar
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - pip@evilgeek.co.uk
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - color_sort.gemspec
111
+ - lib/color_sort.rb
112
+ - lib/color_sort/color_space_converter.rb
113
+ - lib/color_sort/distance.rb
114
+ - lib/color_sort/ordering.rb
115
+ - lib/color_sort/sorter.rb
116
+ - lib/color_sort/version.rb
117
+ - spec/color_sort_spec.rb
118
+ - spec/lib/color_space_converter_spec.rb
119
+ - spec/lib/distance_spec.rb
120
+ - spec/lib/ordering_spec.rb
121
+ - spec/lib/sorter_spec.rb
122
+ - spec/spec_helper.rb
123
+ homepage: https://github.com/ms-digital-labs/color_sort
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.2.2
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Sorts colors perceptually
147
+ test_files:
148
+ - spec/color_sort_spec.rb
149
+ - spec/lib/color_space_converter_spec.rb
150
+ - spec/lib/distance_spec.rb
151
+ - spec/lib/ordering_spec.rb
152
+ - spec/lib/sorter_spec.rb
153
+ - spec/spec_helper.rb