sort_authority 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem*
2
+ ext/**/*.bundle
3
+ ext/**/*.o
4
+ Gemfile.lock
5
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT LICENSE
2
+
3
+ Copyright (c) 2013 Wegowise Inc.
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,53 @@
1
+ # sort_authority
2
+
3
+ Natural order string comparison using
4
+ [natsort](http://sourcefrog.net/projects/natsort/)
5
+ by [Martin Pool](http://sourcefrog.net/).
6
+
7
+ ### Getting Started
8
+
9
+ If you're using Bundler, you can add `sort_authority` to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'sort_authority'
13
+ ```
14
+
15
+ Or manually install the gem using:
16
+
17
+ ```shell
18
+ gem install sort_authority
19
+ ```
20
+
21
+ ### Usage
22
+
23
+ ```ruby
24
+ # bad
25
+ ['9 Main Street', '10 Main Street'].sort
26
+ # => ['10 Main Street', '9 Main Street']
27
+
28
+ # good!
29
+ require 'sort_authority/ext/enumerable'
30
+ ['9 Main Street', '10 Main Street'].natural_sort
31
+ # => ['9 Main Street', '10 Main Street']
32
+ ```
33
+
34
+ ### Benchmarks
35
+
36
+ Comparison of sorting an array `['x1'] * 100_000` with [naturalsort](https://rubygems.org/gems/naturalsort),
37
+ [naturally](https://rubygems.org/gems/naturally), [natcmp](https://rubygems.org/gems/natcmp),
38
+ and [sensible_sort](http://www.davekoelle.com/alphanum.html).
39
+
40
+ ```
41
+ user system total real
42
+ sort 0.000000 0.000000 0.000000 ( 0.001758)
43
+ strnatcmp.c 0.030000 0.000000 0.030000 ( 0.029083)
44
+ naturalsort gem 0.060000 0.000000 0.060000 ( 0.061879)
45
+ naturally gem 0.990000 0.020000 1.010000 ( 1.009634)
46
+ natcmp gem 1.140000 0.010000 1.150000 ( 1.141950)
47
+ sensible_sort 2.280000 0.000000 2.280000 ( 2.286274)
48
+ ```
49
+
50
+
51
+ ### Contributing
52
+
53
+ Fork, branch, and pull-request.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rake/testtask'
10
+ Rake::TestTask.new do |t|
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ end
data/ext/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'ffi-compiler/compile_task'
2
+
3
+ FFI::Compiler::CompileTask.new('strnatcmp') do |c|
4
+ c.have_header?('ctype.h', '/usr/include')
5
+ c.have_header?('string.h', '/usr/include')
6
+ c.have_header?('assert.h', '/usr/include')
7
+ c.have_header?('stdio.h', '/usr/include')
8
+ end
data/ext/strnatcmp.c ADDED
@@ -0,0 +1,178 @@
1
+ /* -*- mode: c; c-file-style: "k&r" -*-
2
+
3
+ strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
4
+ Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
5
+
6
+ This software is provided 'as-is', without any express or implied
7
+ warranty. In no event will the authors be held liable for any damages
8
+ arising from the use of this software.
9
+
10
+ Permission is granted to anyone to use this software for any purpose,
11
+ including commercial applications, and to alter it and redistribute it
12
+ freely, subject to the following restrictions:
13
+
14
+ 1. The origin of this software must not be misrepresented; you must not
15
+ claim that you wrote the original software. If you use this software
16
+ in a product, an acknowledgment in the product documentation would be
17
+ appreciated but is not required.
18
+ 2. Altered source versions must be plainly marked as such, and must not be
19
+ misrepresented as being the original software.
20
+ 3. This notice may not be removed or altered from any source distribution.
21
+ */
22
+
23
+
24
+ /* partial change history:
25
+ *
26
+ * 2004-10-10 mbp: Lift out character type dependencies into macros.
27
+ *
28
+ * Eric Sosman pointed out that ctype functions take a parameter whose
29
+ * value must be that of an unsigned int, even on platforms that have
30
+ * negative chars in their default char type.
31
+ */
32
+
33
+ #include <ctype.h>
34
+ #include <string.h>
35
+ #include <assert.h>
36
+ #include <stdio.h>
37
+
38
+ #include "strnatcmp.h"
39
+
40
+
41
+ /* These are defined as macros to make it easier to adapt this code to
42
+ * different characters types or comparison functions. */
43
+ static inline int
44
+ nat_isdigit(nat_char a)
45
+ {
46
+ return isdigit((unsigned char) a);
47
+ }
48
+
49
+
50
+ static inline int
51
+ nat_isspace(nat_char a)
52
+ {
53
+ return isspace((unsigned char) a);
54
+ }
55
+
56
+
57
+ static inline nat_char
58
+ nat_toupper(nat_char a)
59
+ {
60
+ return toupper((unsigned char) a);
61
+ }
62
+
63
+
64
+
65
+ static int
66
+ compare_right(nat_char const *a, nat_char const *b)
67
+ {
68
+ int bias = 0;
69
+
70
+ /* The longest run of digits wins. That aside, the greatest
71
+ value wins, but we can't know that it will until we've scanned
72
+ both numbers to know that they have the same magnitude, so we
73
+ remember it in BIAS. */
74
+ for (;; a++, b++) {
75
+ if (!nat_isdigit(*a) && !nat_isdigit(*b))
76
+ return bias;
77
+ else if (!nat_isdigit(*a))
78
+ return -1;
79
+ else if (!nat_isdigit(*b))
80
+ return +1;
81
+ else if (*a < *b) {
82
+ if (!bias)
83
+ bias = -1;
84
+ } else if (*a > *b) {
85
+ if (!bias)
86
+ bias = +1;
87
+ } else if (!*a && !*b)
88
+ return bias;
89
+ }
90
+
91
+ return 0;
92
+ }
93
+
94
+
95
+ static int
96
+ compare_left(nat_char const *a, nat_char const *b)
97
+ {
98
+ /* Compare two left-aligned numbers: the first to have a
99
+ different value wins. */
100
+ for (;; a++, b++) {
101
+ if (!nat_isdigit(*a) && !nat_isdigit(*b))
102
+ return 0;
103
+ else if (!nat_isdigit(*a))
104
+ return -1;
105
+ else if (!nat_isdigit(*b))
106
+ return +1;
107
+ else if (*a < *b)
108
+ return -1;
109
+ else if (*a > *b)
110
+ return +1;
111
+ }
112
+
113
+ return 0;
114
+ }
115
+
116
+
117
+ static int strnatcmp0(nat_char const *a, nat_char const *b, int fold_case)
118
+ {
119
+ int ai, bi;
120
+ nat_char ca, cb;
121
+ int fractional, result;
122
+
123
+ assert(a && b);
124
+ ai = bi = 0;
125
+ while (1) {
126
+ ca = a[ai]; cb = b[bi];
127
+
128
+ /* skip over leading spaces or zeros */
129
+ while (nat_isspace(ca))
130
+ ca = a[++ai];
131
+
132
+ while (nat_isspace(cb))
133
+ cb = b[++bi];
134
+
135
+ /* process run of digits */
136
+ if (nat_isdigit(ca) && nat_isdigit(cb)) {
137
+ fractional = (ca == '0' || cb == '0');
138
+
139
+ if (fractional) {
140
+ if ((result = compare_left(a+ai, b+bi)) != 0)
141
+ return result;
142
+ } else {
143
+ if ((result = compare_right(a+ai, b+bi)) != 0)
144
+ return result;
145
+ }
146
+ }
147
+
148
+ if (!ca && !cb) {
149
+ /* The strings compare the same. Perhaps the caller
150
+ will want to call strcmp to break the tie. */
151
+ return 0;
152
+ }
153
+
154
+ if (fold_case) {
155
+ ca = nat_toupper(ca);
156
+ cb = nat_toupper(cb);
157
+ }
158
+
159
+ if (ca < cb)
160
+ return -1;
161
+ else if (ca > cb)
162
+ return +1;
163
+
164
+ ++ai; ++bi;
165
+ }
166
+ }
167
+
168
+
169
+
170
+ int strnatcmp(nat_char const *a, nat_char const *b) {
171
+ return strnatcmp0(a, b, 0);
172
+ }
173
+
174
+
175
+ /* Compare, recognizing numeric string and ignoring case. */
176
+ int strnatcasecmp(nat_char const *a, nat_char const *b) {
177
+ return strnatcmp0(a, b, 1);
178
+ }
data/ext/strnatcmp.h ADDED
@@ -0,0 +1,31 @@
1
+ /* -*- mode: c; c-file-style: "k&r" -*-
2
+
3
+ strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
4
+ Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
5
+
6
+ This software is provided 'as-is', without any express or implied
7
+ warranty. In no event will the authors be held liable for any damages
8
+ arising from the use of this software.
9
+
10
+ Permission is granted to anyone to use this software for any purpose,
11
+ including commercial applications, and to alter it and redistribute it
12
+ freely, subject to the following restrictions:
13
+
14
+ 1. The origin of this software must not be misrepresented; you must not
15
+ claim that you wrote the original software. If you use this software
16
+ in a product, an acknowledgment in the product documentation would be
17
+ appreciated but is not required.
18
+ 2. Altered source versions must be plainly marked as such, and must not be
19
+ misrepresented as being the original software.
20
+ 3. This notice may not be removed or altered from any source distribution.
21
+ */
22
+
23
+
24
+ /* CUSTOMIZATION SECTION
25
+ *
26
+ * You can change this typedef, but must then also change the inline
27
+ * functions in strnatcmp.c */
28
+ typedef char nat_char;
29
+
30
+ int strnatcmp(nat_char const *a, nat_char const *b);
31
+ int strnatcasecmp(nat_char const *a, nat_char const *b);
@@ -0,0 +1,17 @@
1
+ module Enumerable
2
+ def natural_sort
3
+ sort {|a, b| SortAuthority.strnatcmp(a, b) }
4
+ end
5
+
6
+ def natural_sort!
7
+ sort! {|a, b| SortAuthority.strnatcmp(a, b) }
8
+ end
9
+
10
+ def natural_sort_by(&block)
11
+ sort {|a, b| SortAuthority.strnatcmp(block.call(a), block.call(b)) }
12
+ end
13
+
14
+ def natural_sort_by!(&block)
15
+ sort! {|a, b| SortAuthority.strnatcmp(block.call(a), block.call(b)) }
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module SortAuthority
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'ffi'
2
+ require 'ffi-compiler/loader'
3
+
4
+ module SortAuthority
5
+ extend FFI::Library
6
+ ffi_lib FFI::Compiler::Loader.find('strnatcmp')
7
+ attach_function :strnatcmp, [:pointer, :pointer], :int
8
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/sort_authority/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'sort_authority'
5
+ s.version = SortAuthority::VERSION
6
+ s.authors = ['Nathan Fixler']
7
+ s.email = 'nathan@fixler.org'
8
+ s.license = 'MIT'
9
+ s.summary = 'Natural order string comparison'
10
+ s.description = 'String ordering for humans.'
11
+ s.homepage = 'https://rubygems.org/gems/sort_authority'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.extensions << 'ext/Rakefile'
15
+
16
+ s.add_dependency 'rake'
17
+ s.add_dependency 'ffi-compiler', '~> 0.1.3'
18
+
19
+ s.add_development_dependency 'minitest', '~> 5.0.0'
20
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ describe 'SortAuthority Benchmark' do
4
+ bench_performance_linear '#natural_sort' do |n|
5
+ ary = ['x1'] * n
6
+ ary.natural_sort
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ describe Enumerable do
4
+ it 'natural sorts' do
5
+ assert_equal ['9m', '10m'], ['10m', '9m'].natural_sort
6
+ end
7
+
8
+ it 'natural sorts in place' do
9
+ actual = ['10m', '9m']
10
+ actual.natural_sort!
11
+ assert_equal ['9m', '10m'], actual
12
+ end
13
+
14
+ it 'natural sorts with a block' do
15
+ actual = []
16
+ actual << OpenStruct.new(name: 'hello world')
17
+ actual << OpenStruct.new(name: 'hello world 2')
18
+ actual << OpenStruct.new(name: 'hello 2 world')
19
+ assert_equal ['hello 2 world', 'hello world', 'hello world 2'],
20
+ actual.natural_sort_by(&:name).map(&:name)
21
+ end
22
+
23
+ it 'natural sorts with a block in place' do
24
+ actual = []
25
+ actual << OpenStruct.new(name: 'hello world')
26
+ actual << OpenStruct.new(name: 'hello world 2')
27
+ actual << OpenStruct.new(name: 'hello 2 world')
28
+ actual.natural_sort_by!(&:name)
29
+ assert_equal ['hello 2 world', 'hello world', 'hello world 2'],
30
+ actual.map(&:name)
31
+ end
32
+
33
+ end
@@ -0,0 +1,53 @@
1
+ require 'test_helper'
2
+
3
+ describe 'strnatcmp' do
4
+ include SortAuthority
5
+
6
+ it 'sorts two empty strings' do
7
+ assert_equal 0, strnatcmp('', '')
8
+ end
9
+
10
+ it 'sorts two identical strings with letters' do
11
+ assert_equal 0, strnatcmp('x', 'x')
12
+ end
13
+
14
+ it 'sorts two identical strings with letters and numbers' do
15
+ assert_equal 0, strnatcmp('x1', 'x1')
16
+ end
17
+
18
+ it 'sorts letters (x < y)' do
19
+ assert_equal -1, strnatcmp('x', 'y')
20
+ end
21
+
22
+ it 'sorts letters (y > x)' do
23
+ assert_equal 1, strnatcmp('y', 'x')
24
+ end
25
+
26
+ it 'sorts letters and numbers (x1 < y1)' do
27
+ assert_equal -1, strnatcmp('x1', 'y1')
28
+ end
29
+
30
+ it 'sorts letters and numbers (y1 > x1)' do
31
+ assert_equal 1, strnatcmp('y1', 'x1')
32
+ end
33
+
34
+ it 'sorts letters and numbers (x1 < x2)' do
35
+ assert_equal -1, strnatcmp('x1', 'x2')
36
+ end
37
+
38
+ it 'sorts letters and numbers (x2 > x1)' do
39
+ assert_equal 1, strnatcmp('x2', 'x1')
40
+ end
41
+
42
+ it 'sorts numbers numerically' do
43
+ assert_equal -1, strnatcmp('9', '10')
44
+ end
45
+
46
+ it 'sorts multi-word strings that are equal' do
47
+ assert_equal 0, strnatcmp('x 1', 'x 1')
48
+ end
49
+
50
+ it 'sorts multi-word strings that are not equal' do
51
+ assert_equal -1, strnatcmp('x 1', 'x 2')
52
+ end
53
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../lib/sort_authority'
2
+ require_relative '../lib/sort_authority/ext/enumerable'
3
+
4
+ gem 'minitest'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest/spec'
8
+ require 'minitest/benchmark'
9
+ require 'ostruct'
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sort_authority
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Fixler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: ffi-compiler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.1.3
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 5.0.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 5.0.0
62
+ description: String ordering for humans.
63
+ email: nathan@fixler.org
64
+ executables: []
65
+ extensions:
66
+ - ext/Rakefile
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - ext/Rakefile
75
+ - ext/strnatcmp.c
76
+ - ext/strnatcmp.h
77
+ - lib/sort_authority.rb
78
+ - lib/sort_authority/ext/enumerable.rb
79
+ - lib/sort_authority/version.rb
80
+ - sort_authority.gemspec
81
+ - test/benchmark_test.rb
82
+ - test/ext/enumerable_test.rb
83
+ - test/strnatcmp_test.rb
84
+ - test/test_helper.rb
85
+ homepage: https://rubygems.org/gems/sort_authority
86
+ licenses:
87
+ - MIT
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ segments:
99
+ - 0
100
+ hash: -215718808201645381
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ segments:
108
+ - 0
109
+ hash: -215718808201645381
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.23
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Natural order string comparison
116
+ test_files: []