naturally 1.3.1 → 1.3.2
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 +4 -4
- data/.travis.yml +2 -4
- data/Gemfile +1 -1
- data/README.md +7 -23
- data/lib/naturally.rb +29 -84
- data/lib/naturally/segment.rb +28 -0
- data/lib/naturally/version.rb +1 -1
- data/naturally.gemspec +2 -1
- data/spec/naturally_spec.rb +42 -30
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11b862eddf78c3c623300fc93d643a1b0081f815
|
4
|
+
data.tar.gz: 576b16fec163a92e756c98c1df4f3b1af8ebab31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 08f910f8cd088cb92969c6f1d257cc53a2178e54894639bac1b361651c986f312e90c6cd42eacb202b89fc664dbf1108d76b474578a40e0a35462bb6ab4f23fe
|
7
|
+
data.tar.gz: 1f389ebe3746dc8d2f5c80cd35eb0dcdf4e408fd5f9116945e3355bdf2e037ecff46a60ca458bb3e02df5a701b3847460c184d4ba29012248aae2a09b32e26c9
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,29 +1,8 @@
|
|
1
1
|
# Naturally
|
2
2
|
[](http://badge.fury.io/rb/naturally) [](https://travis-ci.org/dogweather/naturally) [](https://codeclimate.com/github/dogweather/naturally)
|
3
3
|
|
4
|
-
Natural (version number) sorting with
|
5
|
-
See Jeff Atwood's [Sorting for Humans: Natural Sort Order](http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html) and the
|
6
|
-
|
7
|
-
The core of the search is [from here](https://github.com/ahoward/version_sorter). It's since been extended to handle the particular types of numbers that come up in statutes, such
|
8
|
-
as *335.1, 336, 336a*, etc.
|
9
|
-
|
10
|
-
`Naturally` will also sort "numbers" in college course code format such as
|
11
|
-
*MATH101, MATH102, ...*. See the specs for examples.
|
12
|
-
|
13
|
-
|
14
|
-
## Installation
|
15
|
-
|
16
|
-
Add this line to your application's Gemfile:
|
17
|
-
|
18
|
-
gem 'naturally'
|
19
|
-
|
20
|
-
And then execute:
|
21
|
-
|
22
|
-
$ bundle
|
23
|
-
|
24
|
-
Or install it outside of bundler with:
|
25
|
-
|
26
|
-
$ gem install naturally
|
4
|
+
Natural (version number) sorting with support for **legal document numbering**, **college course codes**, and **Unicode**.
|
5
|
+
See Jeff Atwood's [Sorting for Humans: Natural Sort Order](http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html) and the WebLaws.org post [Counting to 10 in Californian](http://www.weblaws.org/blog/2012/08/counting-from-1-to-10-in-californian/).
|
27
6
|
|
28
7
|
|
29
8
|
## Usage
|
@@ -68,6 +47,11 @@ expect(sorted.map(&:name)).to eq [
|
|
68
47
|
|
69
48
|
See [the spec for more examples](https://github.com/dogweather/naturally/blob/master/spec/naturally_spec.rb) of what Naturally can sort.
|
70
49
|
|
50
|
+
## Related Work
|
51
|
+
|
52
|
+
* [ahoward/version_sorter](https://github.com/ahoward/version_sorter), the starting point for the `naturally` gem.
|
53
|
+
* [GitHub's Version sorter](https://github.com/github/version_sorter)
|
54
|
+
|
71
55
|
|
72
56
|
## Contributing
|
73
57
|
|
data/lib/naturally.rb
CHANGED
@@ -1,98 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'naturally/segment'
|
2
|
+
|
3
|
+
# A module which performs natural sorting on a variety of number
|
4
|
+
# formats. (See the specs for examples.)
|
5
|
+
#
|
6
|
+
# It achieves this by capitalizing on Ruby's behavior when
|
7
|
+
# comparing arrays: The module sorts arrays of segmented numbers such as
|
8
|
+
# ['1.9', '1.9a', '1.10'] by comparing them in their array forms.
|
9
|
+
# I.e., approximately [['1', '9'], ['1, '9a'], ['1', '10']]
|
5
10
|
module Naturally
|
6
11
|
# Perform a natural sort.
|
7
12
|
#
|
8
13
|
# @param [Array<String>] an_array the list of numbers to sort.
|
9
14
|
# @return [Array<String>] the numbers sorted naturally.
|
10
15
|
def self.sort(an_array)
|
11
|
-
|
16
|
+
an_array.sort_by { |x| normalize(x) }
|
12
17
|
end
|
13
18
|
|
19
|
+
# Sort an array of objects "naturally" by a given attribute.
|
20
|
+
#
|
21
|
+
# @param [Array<Object>] an_array the list of objects to sort.
|
22
|
+
# @param [Symbol] an_attribute the attribute by which to sort.
|
23
|
+
# @return [Array<Object>] the objects in natural sort order.
|
14
24
|
def self.sort_by(an_array, an_attribute)
|
15
|
-
an_array.sort_by{|
|
25
|
+
an_array.sort_by { |obj| normalize(obj.send(an_attribute)) }
|
16
26
|
end
|
17
27
|
|
18
|
-
# Convert the given number
|
19
|
-
#
|
28
|
+
# Convert the given number an array of {Segment}s.
|
29
|
+
# This enables it to be sorted against other arrays
|
30
|
+
# by the standard #sort method.
|
20
31
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# sorting in an array. It's an object representing
|
32
|
-
# a value which implements the {Comparable} interface.
|
33
|
-
class NumberElement
|
34
|
-
include Comparable
|
35
|
-
attr_accessor :val
|
36
|
-
|
37
|
-
def initialize(v)
|
38
|
-
@val = v
|
39
|
-
end
|
40
|
-
|
41
|
-
def <=>(other)
|
42
|
-
if both_are_integers_without_letters(other)
|
43
|
-
return @val.to_i <=> other.val.to_i
|
44
|
-
end
|
45
|
-
|
46
|
-
if either_is_numbers_followed_by_letters(other)
|
47
|
-
return simple_normalize(@val) <=> simple_normalize(other.val)
|
48
|
-
end
|
49
|
-
|
50
|
-
if either_is_letters_followed_by_numbers(other)
|
51
|
-
return reverse_simple_normalize(@val) <=> reverse_simple_normalize(other.val)
|
52
|
-
end
|
53
|
-
|
54
|
-
@val <=> other.val
|
55
|
-
end
|
56
|
-
|
57
|
-
def either_is_letters_followed_by_numbers(other)
|
58
|
-
letters_with_numbers? || other.letters_with_numbers?
|
59
|
-
end
|
60
|
-
|
61
|
-
def either_is_numbers_followed_by_letters(other)
|
62
|
-
numbers_with_letters? || other.numbers_with_letters?
|
63
|
-
end
|
64
|
-
|
65
|
-
def both_are_integers_without_letters(other)
|
66
|
-
pure_integer? && other.pure_integer?
|
67
|
-
end
|
68
|
-
|
69
|
-
def pure_integer?
|
70
|
-
@val =~ /^\d+$/
|
71
|
-
end
|
72
|
-
|
73
|
-
def numbers_with_letters?
|
74
|
-
val =~ /^\d+\p{Alpha}+$/
|
75
|
-
end
|
76
|
-
|
77
|
-
def letters_with_numbers?
|
78
|
-
val =~ /^\p{Alpha}+\d+$/
|
79
|
-
end
|
80
|
-
|
81
|
-
def simple_normalize(n)
|
82
|
-
if n =~ /^(\d+)(\p{Alpha}+)$/
|
83
|
-
[$1.to_i, $2]
|
84
|
-
else
|
85
|
-
[n.to_i]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def reverse_simple_normalize(n)
|
90
|
-
if n =~ /^(\p{Alpha}+)(\d+)$/
|
91
|
-
[$1, $2.to_i]
|
92
|
-
else
|
93
|
-
[n.to_s]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
32
|
+
# For example:
|
33
|
+
# '1.2a.3' becomes [Segment<'1'>, Segment<'2a'>, Segment<'3'>]
|
34
|
+
#
|
35
|
+
# @param [String] complex_number the number in a hierarchical form
|
36
|
+
# such as 1.2a.3.
|
37
|
+
# @return [Array<Segment>] an array of Segments which
|
38
|
+
# can be sorted naturally via a standard #sort.
|
39
|
+
def self.normalize(complex_number)
|
40
|
+
tokens = complex_number.to_s.scan(/\p{Word}+/)
|
41
|
+
tokens.map { |t| Segment.new(t) }
|
97
42
|
end
|
98
43
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Naturally
|
2
|
+
# An entity which can be compared to other like elements for
|
3
|
+
# sorting. It's an object representing
|
4
|
+
# a value which implements the {Comparable} interface.
|
5
|
+
class Segment
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def initialize(v)
|
9
|
+
@val = v
|
10
|
+
end
|
11
|
+
|
12
|
+
def <=>(other)
|
13
|
+
to_array <=> other.to_array
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_array
|
17
|
+
if @val =~ /^(\p{Digit}+)(\p{Alpha}+)$/
|
18
|
+
[$1.to_i, $2]
|
19
|
+
elsif @val =~ /^(\p{Alpha}+)(\p{Digit}+)$/
|
20
|
+
[$1, $2.to_i]
|
21
|
+
elsif @val =~ /^\p{Digit}+$/
|
22
|
+
[@val.to_i]
|
23
|
+
else
|
24
|
+
[@val]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/naturally/version.rb
CHANGED
data/naturally.gemspec
CHANGED
@@ -6,10 +6,11 @@ require 'naturally/version'
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = "naturally"
|
8
8
|
gem.version = Naturally::VERSION
|
9
|
+
gem.license = 'MIT'
|
9
10
|
gem.authors = ["Robb Shecter"]
|
10
11
|
gem.email = ["robb@weblaws.org"]
|
11
12
|
gem.summary = %q{Sorts numbers according to the way people are used to seeing them.}
|
12
|
-
gem.description = %q{Natural Sorting with support for legal numbering}
|
13
|
+
gem.description = %q{Natural Sorting with support for legal numbering, course numbers, and other number/letter mixes.}
|
13
14
|
gem.homepage = "http://github.com/dogweather/naturally"
|
14
15
|
|
15
16
|
gem.files = `git ls-files`.split($/)
|
data/spec/naturally_spec.rb
CHANGED
@@ -1,55 +1,67 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
2
|
require 'naturally'
|
4
3
|
|
5
4
|
describe Naturally do
|
5
|
+
def it_sorts(this:, to_this:)
|
6
|
+
actual = Naturally.sort(this)
|
7
|
+
expect(actual).to eq(to_this)
|
8
|
+
end
|
9
|
+
|
6
10
|
describe '#sort' do
|
7
11
|
it 'sorts an array of strings nicely as if they were legal numbers' do
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
it_sorts(
|
13
|
+
this: %w(676 676.1 676.11 676.12 676.2 676.3 676.9 676.10),
|
14
|
+
to_this: %w(676 676.1 676.2 676.3 676.9 676.10 676.11 676.12)
|
15
|
+
)
|
11
16
|
end
|
12
17
|
|
13
18
|
it 'sorts a more complex list of strings' do
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
it_sorts(
|
20
|
+
this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.45 354.5),
|
21
|
+
to_this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.5 354.45)
|
22
|
+
)
|
17
23
|
end
|
18
24
|
|
19
25
|
it 'sorts when numbers have letters in them' do
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
it_sorts(
|
27
|
+
this: %w(335 335.1 336a 336 337 337a 337.1 337.15 337.2),
|
28
|
+
to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337a)
|
29
|
+
)
|
23
30
|
end
|
24
31
|
|
25
32
|
it 'sorts when numbers have unicode letters in them' do
|
26
|
-
|
27
|
-
|
28
|
-
|
33
|
+
it_sorts(
|
34
|
+
this: %w(335 335.1 336a 336 337 337я 337.1 337.15 337.2),
|
35
|
+
to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337я)
|
36
|
+
)
|
29
37
|
end
|
30
38
|
|
31
39
|
it 'sorts when letters have numbers in them' do
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
it_sorts(
|
41
|
+
this: %w(PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, PBLI, SBP1, SBP3),
|
42
|
+
to_this: %w(PBLI, PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, SBP1, SBP3)
|
43
|
+
)
|
35
44
|
end
|
36
45
|
|
37
46
|
it 'sorts when letters have numbers and unicode characters in them' do
|
38
|
-
|
39
|
-
|
40
|
-
|
47
|
+
it_sorts(
|
48
|
+
this: %w(АБ4, АБ2, АБ10, АБ12, АБ1, АБ3, АД8, АД5, АЩФ12, АЩФ8, ЫВА1),
|
49
|
+
to_this: %w(АБ1, АБ2, АБ3, АБ4, АБ10, АБ12, АД5, АД8, АЩФ8, АЩФ12, ЫВА1)
|
50
|
+
)
|
41
51
|
end
|
42
52
|
|
43
53
|
it 'sorts double digits with letters correctly' do
|
44
|
-
|
45
|
-
|
46
|
-
|
54
|
+
it_sorts(
|
55
|
+
this: %w(12a 12b 12c 13a 13b 2 3 4 5 10 11 12),
|
56
|
+
to_this: %w(2 3 4 5 10 11 12 12a 12b 12c 13a 13b)
|
57
|
+
)
|
47
58
|
end
|
48
59
|
|
49
60
|
it 'sorts double digits with unicode letters correctly' do
|
50
|
-
|
51
|
-
|
52
|
-
|
61
|
+
it_sorts(
|
62
|
+
this: %w(12а 12б 12в 13а 13б 2 3 4 5 10 11 12),
|
63
|
+
to_this: %w(2 3 4 5 10 11 12 12а 12б 12в 13а 13б)
|
64
|
+
)
|
53
65
|
end
|
54
66
|
end
|
55
67
|
|
@@ -64,8 +76,8 @@ describe Naturally do
|
|
64
76
|
UbuntuVersion.new('Quantal Quetzal', '12.10'),
|
65
77
|
UbuntuVersion.new('Lucid Lynx', '10.04.4')
|
66
78
|
]
|
67
|
-
|
68
|
-
expect(
|
79
|
+
actual = Naturally.sort_by(releases, :version)
|
80
|
+
expect(actual.map(&:name)).to eq [
|
69
81
|
'Lucid Lynx',
|
70
82
|
'Maverick Meerkat',
|
71
83
|
'Precise Pangolin',
|
@@ -86,8 +98,8 @@ describe Naturally do
|
|
86
98
|
Thing.new('2.1', 'Калуга'),
|
87
99
|
Thing.new('1.3', 'Васюки')
|
88
100
|
]
|
89
|
-
|
90
|
-
|
101
|
+
actual = objects.sort_by { |o| Naturally.normalize(o.name) }
|
102
|
+
expect(actual.map(&:name)).to eq %w(
|
91
103
|
Брест
|
92
104
|
Будапешт
|
93
105
|
Васюки
|
@@ -95,7 +107,7 @@ describe Naturally do
|
|
95
107
|
Киев
|
96
108
|
Москва
|
97
109
|
Париж
|
98
|
-
|
110
|
+
)
|
99
111
|
end
|
100
112
|
end
|
101
113
|
end
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: naturally
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robb Shecter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Natural Sorting with support for legal numbering
|
13
|
+
description: Natural Sorting with support for legal numbering, course numbers, and
|
14
|
+
other number/letter mixes.
|
14
15
|
email:
|
15
16
|
- robb@weblaws.org
|
16
17
|
executables: []
|
@@ -24,11 +25,13 @@ files:
|
|
24
25
|
- README.md
|
25
26
|
- Rakefile
|
26
27
|
- lib/naturally.rb
|
28
|
+
- lib/naturally/segment.rb
|
27
29
|
- lib/naturally/version.rb
|
28
30
|
- naturally.gemspec
|
29
31
|
- spec/naturally_spec.rb
|
30
32
|
homepage: http://github.com/dogweather/naturally
|
31
|
-
licenses:
|
33
|
+
licenses:
|
34
|
+
- MIT
|
32
35
|
metadata: {}
|
33
36
|
post_install_message:
|
34
37
|
rdoc_options: []
|
@@ -46,10 +49,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
49
|
version: '0'
|
47
50
|
requirements: []
|
48
51
|
rubyforge_project:
|
49
|
-
rubygems_version: 2.
|
52
|
+
rubygems_version: 2.4.6
|
50
53
|
signing_key:
|
51
54
|
specification_version: 4
|
52
55
|
summary: Sorts numbers according to the way people are used to seeing them.
|
53
56
|
test_files:
|
54
57
|
- spec/naturally_spec.rb
|
55
|
-
has_rdoc:
|