naturally 1.3.1 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/naturally.png)](http://badge.fury.io/rb/naturally) [![Build Status](https://travis-ci.org/dogweather/naturally.png)](https://travis-ci.org/dogweather/naturally) [![Code Climate](https://codeclimate.com/github/dogweather/naturally.png)](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:
|