unnatural 0.2.0 → 0.3.0
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/.gitignore +2 -1
- data/.travis.yml +6 -6
- data/README.md +3 -4
- data/Rakefile +16 -16
- data/ext/unnatural/Rakefile +3 -0
- data/ext/unnatural/unnatural_ext.c +48 -0
- data/lib/unnatural/fast.rb +25 -16
- data/lib/unnatural/version.rb +1 -1
- data/unnatural.gemspec +8 -9
- metadata +16 -16
- data/ext/unnatural/extconf.rb +0 -4
- data/ext/unnatural/fast_compare.c +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9634e4ac888d2b64eb49af87a89cd124569a56bf
|
4
|
+
data.tar.gz: 65f819eddbaf0d32983aac0b874b506de6211112
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b10ba4248e41c4295b8fb6117adbb7a1038e8f3812fceba905097fc17c58a32dae3b23d1704498aea796c06a21ce7c99e1e95fe26c7f73f9a7b4bd5bf0acaf8
|
7
|
+
data.tar.gz: 80c87c122827309aeb58c611cead2674052b577d33799e2301f97a3311ce1fc8a1e4b42c6920dcd38af99c4fa61e4a6af5d540b72ef2929b69224999518dc479
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
-
- 2.0.0
|
5
4
|
- 2.1.10
|
6
5
|
- 2.2.5
|
7
6
|
- 2.3.1
|
8
|
-
|
9
|
-
|
10
|
-
- rvm: jruby
|
11
|
-
env: JRUBY_OPTS='-Xcompat.version=2.0'
|
7
|
+
- jruby-1.7.20
|
8
|
+
- jruby-9.0.5.0
|
12
9
|
before_install:
|
13
|
-
-
|
10
|
+
- gem install bundler
|
11
|
+
cache:
|
12
|
+
- apt
|
13
|
+
- bundler
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Unnatural defines a natural sort as one where:
|
|
12
12
|
|
13
13
|
Unnatural does not (currently) provide support for:
|
14
14
|
|
15
|
-
1. non-ASCII-compatible encoding
|
15
|
+
1. non-ASCII-compatible encoding (although the pure ruby comparison functions seem to work)
|
16
16
|
2. any number representation other than simple decimal integers
|
17
17
|
3. whitespace insensitivity (i.e., one space and two spaces can be considered as different)
|
18
18
|
|
@@ -20,7 +20,7 @@ Unnatural provides four algorithms, all of which use Ruby's built-in quicksort a
|
|
20
20
|
|
21
21
|
### Unnatural::Fast
|
22
22
|
|
23
|
-
Compares strings byte-by-byte. Comparison function implemented in C. Much faster than any of the pure Ruby options. The default
|
23
|
+
Compares strings byte-by-byte. Comparison function implemented in C. Does not appear to sort unicode strings correctly. Much faster than any of the pure Ruby options. The default.
|
24
24
|
|
25
25
|
### Unnatural::Scan
|
26
26
|
|
@@ -32,7 +32,7 @@ Compares strings by spliting them into arrays of alternating string and integer
|
|
32
32
|
|
33
33
|
### Unnatural::Substitution
|
34
34
|
|
35
|
-
Compares strings by zero-padding integer sequences such that all are the same length. Pure Ruby. Tends to be outperformed by `Unnatural::Scan` on longer strings.
|
35
|
+
Compares strings by zero-padding integer sequences such that all are the same length. Pure Ruby. Tends to be outperformed by `Unnatural::Scan` on longer strings. Recommended for sorting short unicode strings.
|
36
36
|
|
37
37
|
## Installation
|
38
38
|
|
@@ -96,7 +96,6 @@ The default can be changed throughout an application:
|
|
96
96
|
|
97
97
|
```ruby
|
98
98
|
# use Scan instead of Fast
|
99
|
-
# (or, if on JRruby, use Scan instead of Substitution)
|
100
99
|
Unnatural.algorithm = Unnatural::Scan
|
101
100
|
```
|
102
101
|
|
data/Rakefile
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
+
require 'bundler/setup'
|
1
2
|
require 'bundler/gem_tasks'
|
3
|
+
|
2
4
|
require 'rake/testtask'
|
3
5
|
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/clean'
|
8
|
+
|
9
|
+
require 'ffi'
|
10
|
+
require 'ffi-compiler/compile_task'
|
11
|
+
|
12
|
+
CLEAN.include('ext/unnatural/*{.o,.log,.so,.bundle}')
|
13
|
+
CLEAN.include('lib/**/*{.o,.log,.so,.bundle}')
|
14
|
+
|
4
15
|
Rake::TestTask.new(:test) do |t|
|
5
16
|
t.libs << 'test'
|
6
17
|
t.libs << 'lib'
|
7
18
|
t.test_files = FileList['test/**/*_test.rb']
|
8
19
|
end
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
ext.name = 'fast_compare'
|
14
|
-
ext.ext_dir = 'ext/unnatural'
|
15
|
-
ext.lib_dir = 'lib/unnatural'
|
16
|
-
ext.gem_spec = spec
|
21
|
+
desc 'FFI compiler'
|
22
|
+
namespace 'ffi-compiler' do
|
23
|
+
FFI::Compiler::CompileTask.new('ext/unnatural/unnatural_ext')
|
17
24
|
end
|
25
|
+
task compile_ffi: ['ffi-compiler:default']
|
18
26
|
|
19
|
-
task :
|
20
|
-
require './test/benchmark.rb'
|
21
|
-
end
|
22
|
-
|
23
|
-
if RUBY_ENGINE == 'jruby'
|
24
|
-
task default: :test
|
25
|
-
else
|
26
|
-
task default: [:compile, :test]
|
27
|
-
end
|
27
|
+
task default: [:clean, :compile_ffi, :test]
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#define DIGIT_TO_I(C) (C - '0')
|
2
|
+
#define IS_DIGIT(C) ('0' <= C && C <= '9')
|
3
|
+
#define IS_LOWER(C) ('a' <= C && C <= 'z')
|
4
|
+
#define UPCASE(C) (IS_LOWER(C) ? C - ('a' - 'A') : C)
|
5
|
+
|
6
|
+
int compare(char* a_str, int a_length, char* b_str, int b_length) {
|
7
|
+
int a_pos = 0;
|
8
|
+
int b_pos = 0;
|
9
|
+
int a_number = 0;
|
10
|
+
int b_number = 0;
|
11
|
+
char a, b;
|
12
|
+
int diff = 0;
|
13
|
+
|
14
|
+
for (a_pos = 0, b_pos = 0; a_pos < a_length && b_pos < b_length; a_pos++, b_pos++) {
|
15
|
+
a = a_str[a_pos];
|
16
|
+
b = b_str[b_pos];
|
17
|
+
|
18
|
+
if ( IS_DIGIT(a) && !IS_DIGIT(b)) return -1;
|
19
|
+
if (!IS_DIGIT(a) && IS_DIGIT(b)) return +1;
|
20
|
+
|
21
|
+
if (IS_DIGIT(a) && IS_DIGIT(b)) {
|
22
|
+
while (IS_DIGIT(a)) {
|
23
|
+
a_number = a_number * 10 + DIGIT_TO_I(a);
|
24
|
+
a_pos++;
|
25
|
+
a = a_str[a_pos];
|
26
|
+
}
|
27
|
+
|
28
|
+
while (IS_DIGIT(b)) {
|
29
|
+
b_number = b_number * 10 + DIGIT_TO_I(b);
|
30
|
+
b_pos++;
|
31
|
+
b = b_str[b_pos];
|
32
|
+
}
|
33
|
+
|
34
|
+
diff = a_number - b_number;
|
35
|
+
if (diff != 0) return diff;
|
36
|
+
|
37
|
+
a_number = 0;
|
38
|
+
b_number = 0;
|
39
|
+
a_pos--;
|
40
|
+
b_pos--;
|
41
|
+
} else {
|
42
|
+
diff = UPCASE(a) - UPCASE(b);
|
43
|
+
if (diff != 0) return diff;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
return a_length - b_length;
|
48
|
+
}
|
data/lib/unnatural/fast.rb
CHANGED
@@ -1,21 +1,30 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'ffi'
|
2
|
+
require 'ffi-compiler/loader'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Unnatural
|
5
|
+
module Fast
|
6
|
+
module Ext
|
7
|
+
extend FFI::Library
|
8
|
+
ffi_lib FFI::Compiler::Loader.find('unnatural_ext')
|
9
|
+
attach_function :compare, [:string, :int, :string, :int], :int
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
12
|
+
def self.compare(a, b)
|
13
|
+
Ext.compare(a, a.size, b, b.size)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.sort(enumerable)
|
17
|
+
enumerable.sort { |a, b| compare(a, b) }
|
17
18
|
end
|
18
|
-
end
|
19
19
|
|
20
|
-
|
20
|
+
def self.sort_by(enumerable)
|
21
|
+
raise ArgumentError, 'Block expected but none given' unless block_given?
|
22
|
+
enumerable
|
23
|
+
.map { |e| [(yield e), e] }
|
24
|
+
.sort { |a, b| compare(a.first, b.first) }
|
25
|
+
.map { |ary| ary[1] }
|
26
|
+
end
|
27
|
+
end
|
21
28
|
end
|
29
|
+
|
30
|
+
Unnatural.algorithm ||= Unnatural::Fast
|
data/lib/unnatural/version.rb
CHANGED
data/unnatural.gemspec
CHANGED
@@ -14,18 +14,17 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = 'https://github.com/bjmllr/unnatural'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
|
-
spec.files = `git ls-files -z`.split("\x0")
|
18
|
-
|
19
|
-
spec.
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
.reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.files += Dir.glob('ext/unnatural/*')
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
-
|
23
|
-
spec.extensions << 'ext/unnatural/extconf.rb'
|
24
|
-
end
|
22
|
+
spec.extensions = ['ext/unnatural/Rakefile']
|
25
23
|
|
26
|
-
spec.
|
27
|
-
spec.
|
28
|
-
|
24
|
+
spec.add_dependency 'ffi-compiler', '~> 1.0'
|
25
|
+
spec.add_dependency 'rake', '~> 10.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
29
28
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
30
29
|
spec.add_development_dependency 'benchmark-ips'
|
31
30
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unnatural
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Miller
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: ffi-compiler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
|
-
type: :
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '1.12'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '1.12'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -85,7 +85,7 @@ email:
|
|
85
85
|
- bjmllr@gmail.com
|
86
86
|
executables: []
|
87
87
|
extensions:
|
88
|
-
- ext/unnatural/
|
88
|
+
- ext/unnatural/Rakefile
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
91
|
- ".gitignore"
|
@@ -97,8 +97,8 @@ files:
|
|
97
97
|
- Rakefile
|
98
98
|
- bin/console
|
99
99
|
- bin/setup
|
100
|
-
- ext/unnatural/
|
101
|
-
- ext/unnatural/
|
100
|
+
- ext/unnatural/Rakefile
|
101
|
+
- ext/unnatural/unnatural_ext.c
|
102
102
|
- lib/unnatural.rb
|
103
103
|
- lib/unnatural/fast.rb
|
104
104
|
- lib/unnatural/scan.rb
|
data/ext/unnatural/extconf.rb
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
#include <ruby.h>
|
2
|
-
|
3
|
-
static VALUE rb_mUnnatural;
|
4
|
-
static VALUE rb_mFast;
|
5
|
-
|
6
|
-
#define DIGIT_TO_I(C) (C - '0')
|
7
|
-
#define IS_DIGIT(C) ('0' <= C && C <= '9')
|
8
|
-
#define IS_LOWER(C) ('a' <= C && C <= 'z')
|
9
|
-
#define UPCASE(C) (IS_LOWER(C) ? C - ('a' - 'A') : C)
|
10
|
-
|
11
|
-
VALUE compare(VALUE self, VALUE a_arg, VALUE b_arg) {
|
12
|
-
VALUE a_value, b_value;
|
13
|
-
int a_length, b_length, a_pos, b_pos, a_number, b_number, diff;
|
14
|
-
char *a_str, *b_str, a, b;
|
15
|
-
|
16
|
-
if (RB_TYPE_P(a_arg, T_ARRAY)) {
|
17
|
-
a_value = rb_ary_entry(a_arg, 0);
|
18
|
-
b_value = rb_ary_entry(b_arg, 0);
|
19
|
-
} else {
|
20
|
-
a_value = a_arg;
|
21
|
-
b_value = b_arg;
|
22
|
-
}
|
23
|
-
|
24
|
-
a_length = RSTRING_LEN(a_value);
|
25
|
-
b_length = RSTRING_LEN(b_value);
|
26
|
-
a_str = RSTRING_PTR(a_value);
|
27
|
-
b_str = RSTRING_PTR(b_value);
|
28
|
-
a_pos = 0;
|
29
|
-
b_pos = 0;
|
30
|
-
a_number = 0;
|
31
|
-
b_number = 0;
|
32
|
-
diff = 0;
|
33
|
-
|
34
|
-
for (a_pos = 0, b_pos = 0; a_pos < a_length && b_pos < b_length; a_pos++, b_pos++) {
|
35
|
-
a = a_str[a_pos];
|
36
|
-
b = b_str[b_pos];
|
37
|
-
|
38
|
-
if ( IS_DIGIT(a) && !IS_DIGIT(b)) return INT2FIX(-1);
|
39
|
-
if (!IS_DIGIT(a) && IS_DIGIT(b)) return INT2FIX(+1);
|
40
|
-
|
41
|
-
if (IS_DIGIT(a) && IS_DIGIT(b)) {
|
42
|
-
while (IS_DIGIT(a)) {
|
43
|
-
a_number = a_number * 10 + DIGIT_TO_I(a);
|
44
|
-
a_pos++;
|
45
|
-
a = a_str[a_pos];
|
46
|
-
}
|
47
|
-
|
48
|
-
while (IS_DIGIT(b)) {
|
49
|
-
b_number = b_number * 10 + DIGIT_TO_I(b);
|
50
|
-
b_pos++;
|
51
|
-
b = b_str[b_pos];
|
52
|
-
}
|
53
|
-
|
54
|
-
diff = a_number - b_number;
|
55
|
-
if (diff != 0) return INT2FIX(diff);
|
56
|
-
|
57
|
-
a_number = 0;
|
58
|
-
b_number = 0;
|
59
|
-
a_pos--;
|
60
|
-
b_pos--;
|
61
|
-
} else {
|
62
|
-
diff = UPCASE(a) - UPCASE(b);
|
63
|
-
if (diff != 0) return INT2FIX(diff);
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
|
-
diff = a_length - b_length;
|
68
|
-
if (diff != 0) return INT2FIX(diff);
|
69
|
-
|
70
|
-
return INT2FIX(0);
|
71
|
-
}
|
72
|
-
|
73
|
-
void Init_fast_compare() {
|
74
|
-
rb_mUnnatural = rb_define_module("Unnatural");
|
75
|
-
rb_mFast = rb_define_module_under(rb_mUnnatural, "Fast");
|
76
|
-
rb_define_singleton_method(rb_mFast, "compare", compare, 2);
|
77
|
-
}
|