society 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +5 -5
- data/Gemfile +1 -1
- data/LICENSE.txt +19 -12
- data/lib/society.rb +5 -6
- data/lib/society/edge.rb +32 -6
- data/lib/society/formatter/report/html.rb +1 -0
- data/lib/society/formatter/report/templates/components/society-assets/society.js +16 -13
- data/lib/society/node.rb +86 -0
- data/lib/society/object_graph.rb +57 -5
- data/lib/society/parser.rb +454 -31
- data/lib/society/version.rb +1 -1
- data/society.gemspec +4 -5
- data/spec/cli_spec.rb +1 -1
- data/spec/object_graph_spec.rb +211 -17
- data/spec/parser_spec.rb +203 -12
- metadata +5 -37
- data/lib/society/association_processor.rb +0 -206
- data/lib/society/clusterer.rb +0 -188
- data/lib/society/formatter/graph/json.rb +0 -54
- data/lib/society/reference_processor.rb +0 -60
- data/society_graph.json +0 -1
- data/spec/association_processor_spec.rb +0 -174
- data/spec/clusterer_spec.rb +0 -37
- data/spec/fixtures/clustering/clusterer_fixtures.rb +0 -144
- data/spec/fixtures/clustering/edges_1.txt +0 -322
- data/spec/formatter/graph/json_spec.rb +0 -52
- data/spec/reference_processor_spec.rb +0 -18
@@ -1,54 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
module Society
|
4
|
-
module Formatter
|
5
|
-
module Graph
|
6
|
-
class JSON
|
7
|
-
|
8
|
-
def initialize(graph)
|
9
|
-
@graph = graph
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_json
|
13
|
-
to_hash.to_json
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_hash
|
17
|
-
{
|
18
|
-
nodes: node_names.map { |name| { name: name } },
|
19
|
-
edges: named_edges.map do |edge|
|
20
|
-
{
|
21
|
-
from: node_names.index(edge.from),
|
22
|
-
to: node_names.index(edge.to)
|
23
|
-
}
|
24
|
-
end,
|
25
|
-
clusters: clusters_of_indices
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
attr_reader :graph
|
32
|
-
|
33
|
-
def node_names
|
34
|
-
@node_names ||= graph.nodes.map(&:full_name).uniq
|
35
|
-
end
|
36
|
-
|
37
|
-
def named_edges
|
38
|
-
@named_edges ||= graph.edges.map { |edge| Edge.new(from: edge.from.full_name, to: edge.to.full_name) }
|
39
|
-
end
|
40
|
-
|
41
|
-
def clusters_of_indices
|
42
|
-
Society::Clusterer.new.cluster(graph_of_names).map do |cluster|
|
43
|
-
cluster.map { |name| node_names.index(name) }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def graph_of_names
|
48
|
-
ObjectGraph.new(nodes: node_names, edges: named_edges)
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
module Society
|
2
|
-
|
3
|
-
class ReferenceProcessor
|
4
|
-
|
5
|
-
attr_reader :classes, :references, :unresolved_references
|
6
|
-
|
7
|
-
def initialize(classes)
|
8
|
-
@classes = classes
|
9
|
-
@references = []
|
10
|
-
@unresolved_references = []
|
11
|
-
process
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def process
|
17
|
-
classes.each do |klass|
|
18
|
-
klass.constants.each do |const|
|
19
|
-
if assigned_constant(const, klass)
|
20
|
-
next
|
21
|
-
elsif target = perfect_match_for(const)
|
22
|
-
@references << Edge.new(from: klass,
|
23
|
-
to: target,
|
24
|
-
meta: {
|
25
|
-
type: :perfect,
|
26
|
-
entity: const})
|
27
|
-
elsif target = partial_match_for(const)
|
28
|
-
@references << Edge.new(from: klass,
|
29
|
-
to: target,
|
30
|
-
meta: {
|
31
|
-
type: :partial,
|
32
|
-
entity: const})
|
33
|
-
else
|
34
|
-
@unresolved_references << { class: klass,
|
35
|
-
target_name: const.full_name,
|
36
|
-
constant: const }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def perfect_match_for(const)
|
43
|
-
classes.detect { |klass| klass.full_name == const.name }
|
44
|
-
end
|
45
|
-
|
46
|
-
def partial_match_for(const)
|
47
|
-
partial_matches = classes.select do |klass|
|
48
|
-
klass.full_name.include? const.full_name
|
49
|
-
end
|
50
|
-
partial_matches.first if partial_matches.size == 1
|
51
|
-
end
|
52
|
-
|
53
|
-
def assigned_constant(const, klass)
|
54
|
-
klass.constant_assignments.map(&:name).include? const.name
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
data/society_graph.json
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
{"nodes":[{"name":"Account"},{"name":"Attachment"},{"name":"Attempt"},{"name":"Auth"},{"name":"Certificate"},{"name":"Collection"},{"name":"CollectionEnrollment"},{"name":"CollectionGroupEnrollment"},{"name":"CollectionItem"},{"name":"ConcreteConfig"},{"name":"CourseTemplate"},{"name":"CustomField"},{"name":"EnrollingMembership"},{"name":"Enrollment"},{"name":"Group"},{"name":"GroupEnrollment"},{"name":"ImportObject"},{"name":"LearnerCourse"},{"name":"LearnerCustomFieldValue"},{"name":"LearnerFile"},{"name":"LearnerLibraryCourse"},{"name":"LearnerLiveCourse"},{"name":"LearnerSlide"},{"name":"LiveCourse"},{"name":"LiveCourseEnrollment"},{"name":"LiveCourseEnrollmentSource"},{"name":"LiveCourseGroupEnrollment"},{"name":"LiveCourseSession"},{"name":"LiveCourseSessionRegistration"},{"name":"Manager"},{"name":"Membership"},{"name":"MembershipBuilder"},{"name":"NotifiableEnrollment"},{"name":"NotifiableLiveCourseEnrollment"},{"name":"NotifiableLiveCourseSessionRegistration"},{"name":"Notification"},{"name":"NotificationProfile"},{"name":"PasswordToken"},{"name":"Presence"},{"name":"QuestionAnswer"},{"name":"QuestionHash"},{"name":"QuestionResponse"},{"name":"RequestHost"},{"name":"Rule"},{"name":"RulesAttributesNormalizer"},{"name":"SessionToken"},{"name":"Slide"},{"name":"User"},{"name":"Account::Config"},{"name":"Attempt::NullFurthestSlide"},{"name":"Auth::Basic"},{"name":"Auth::CAS"},{"name":"GroupEnrollment::StatisticsCalculator"},{"name":"QuestionHash::Factoid"},{"name":"QuestionHash::Factoid::MultipleChoiceQuestion"},{"name":"QuestionHash::Factoid::Statement"},{"name":"QuestionHash::Factoid::TrueFalseQuestion"},{"name":"QuestionHash::Glossary"},{"name":"QuestionHash::Glossary::MultipleChoiceQuestion"},{"name":"QuestionHash::Glossary::TrueFalseQuestion"},{"name":"QuestionHash::OrderedList"},{"name":"QuestionHash::OrderedList::MultipleChoiceQuestion"},{"name":"QuestionHash::OrderedList::ReorderQuestion"},{"name":"Rule::InvalidPredicate"},{"name":"RulesAttributesNormalizer::FieldRules"},{"name":"Notifications::AuthorCourseDigest"},{"name":"Notifications::CourseAssignedNotificationBuilder"},{"name":"Notifications::CourseDigest"},{"name":"Notifications::LearnerCourseDigest"},{"name":"Notifications::LiveCourseInvitationNotificationBuilder"},{"name":"Notifications::LiveCourseNotificationBuilder"},{"name":"Notifications::LiveCourseRegisteredForSessionNotificationBuilder"},{"name":"Notifications::LiveCourseSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromCanceledSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromUnattendedSessionNotificationBuilder"},{"name":"Notifications::ManagerCourseDigest"},{"name":"Notifications::NotificationBuilder"},{"name":"Notifications::TimeUntilSessionNotificationBuilder"},{"name":"Reports::CourseStatusReport"},{"name":"Reports::PassRateReport"},{"name":"Reports::ScoreDistributionReport"},{"name":"Scorm::Activity"},{"name":"Scorm::Attempt"},{"name":"Scorm::CourseUploadResponse"},{"name":"Scorm::CourseUploadStatusResponse"},{"name":"Scorm::Enrollment"},{"name":"Scorm::RegistrationListResponse"},{"name":"Scorm::RegistrationResponse"},{"name":"Search::CustomFieldValueSearch"},{"name":"Search::EnrollableSearch"}],"edges":[{"from":0,"to":36},{"from":1,"to":46},{"from":2,"to":46},{"from":2,"to":46},{"from":2,"to":13},{"from":2,"to":41},{"from":5,"to":8},{"from":5,"to":6},{"from":5,"to":7},{"from":6,"to":5},{"from":6,"to":47},{"from":7,"to":5},{"from":7,"to":14},{"from":8,"to":5},{"from":8,"to":10},{"from":10,"to":47},{"from":10,"to":8},{"from":10,"to":15},{"from":10,"to":46},{"from":11,"to":18},{"from":11,"to":43},{"from":13,"to":10},{"from":13,"to":47},{"from":13,"to":2},{"from":13,"to":2},{"from":14,"to":30},{"from":14,"to":15},{"from":14,"to":26},{"from":14,"to":43},{"from":14,"to":7},{"from":15,"to":14},{"from":15,"to":10},{"from":16,"to":47},{"from":18,"to":47},{"from":18,"to":11},{"from":23,"to":47},{"from":23,"to":24},{"from":23,"to":27},{"from":23,"to":26},{"from":24,"to":47},{"from":24,"to":23},{"from":24,"to":25},{"from":24,"to":28},{"from":25,"to":24},{"from":26,"to":23},{"from":26,"to":14},{"from":27,"to":23},{"from":27,"to":28},{"from":28,"to":47},{"from":28,"to":27},{"from":28,"to":24},{"from":29,"to":47},{"from":29,"to":47},{"from":30,"to":14},{"from":30,"to":47},{"from":36,"to":0},{"from":41,"to":2},{"from":41,"to":46},{"from":43,"to":14},{"from":43,"to":11},{"from":45,"to":47},{"from":46,"to":10},{"from":46,"to":41},{"from":46,"to":1},{"from":47,"to":13},{"from":47,"to":10},{"from":47,"to":6},{"from":47,"to":24},{"from":47,"to":28},{"from":47,"to":23},{"from":47,"to":45},{"from":47,"to":30},{"from":47,"to":18},{"from":47,"to":29},{"from":47,"to":29},{"from":5,"to":10},{"from":5,"to":47},{"from":5,"to":14},{"from":10,"to":5},{"from":10,"to":14},{"from":11,"to":47},{"from":13,"to":46},{"from":14,"to":10},{"from":14,"to":23},{"from":14,"to":47},{"from":14,"to":5},{"from":23,"to":47},{"from":24,"to":14},{"from":24,"to":27},{"from":27,"to":47},{"from":47,"to":10},{"from":47,"to":23},{"from":47,"to":14},{"from":47,"to":11},{"from":47,"to":47},{"from":47,"to":47},{"from":0,"to":0},{"from":0,"to":3},{"from":0,"to":48},{"from":0,"to":36},{"from":2,"to":49},{"from":2,"to":22},{"from":2,"to":41},{"from":5,"to":13},{"from":5,"to":10},{"from":6,"to":13},{"from":6,"to":2},{"from":10,"to":46},{"from":12,"to":30},{"from":12,"to":30},{"from":13,"to":46},{"from":13,"to":47},{"from":14,"to":10},{"from":14,"to":14},{"from":14,"to":47},{"from":27,"to":78},{"from":27,"to":78},{"from":27,"to":78},{"from":28,"to":78},{"from":31,"to":30},{"from":31,"to":30},{"from":31,"to":30},{"from":32,"to":86},{"from":32,"to":66},{"from":32,"to":66},{"from":33,"to":69},{"from":33,"to":69},{"from":34,"to":71},{"from":34,"to":78},{"from":34,"to":78},{"from":37,"to":78},{"from":37,"to":78},{"from":37,"to":47},{"from":37,"to":37},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":55},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":40},{"from":41,"to":40},{"from":43,"to":63},{"from":45,"to":45},{"from":47,"to":47},{"from":47,"to":47},{"from":47,"to":5},{"from":47,"to":10},{"from":47,"to":14},{"from":47,"to":23},{"from":53,"to":40},{"from":54,"to":40},{"from":53,"to":40},{"from":56,"to":40},{"from":53,"to":55},{"from":57,"to":40},{"from":58,"to":40},{"from":57,"to":40},{"from":59,"to":40},{"from":60,"to":40},{"from":61,"to":40},{"from":60,"to":40},{"from":62,"to":40},{"from":77,"to":35},{"from":82,"to":82},{"from":82,"to":82},{"from":83,"to":83},{"from":83,"to":83},{"from":83,"to":78},{"from":86,"to":83},{"from":86,"to":83},{"from":90,"to":47},{"from":90,"to":14}]}
|
@@ -1,174 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Society::AssociationProcessor do
|
4
|
-
|
5
|
-
describe "#associations" do
|
6
|
-
|
7
|
-
context "`belongs_to`, `has_many`, and `has_many through:` associations" do
|
8
|
-
let(:code) {
|
9
|
-
<<-CODE
|
10
|
-
class Assembly < ActiveRecord::Base
|
11
|
-
has_many :manifests
|
12
|
-
has_many :parts, through: :manifests
|
13
|
-
end
|
14
|
-
|
15
|
-
class Manifest < ActiveRecord::Base
|
16
|
-
belongs_to :assembly
|
17
|
-
belongs_to :part
|
18
|
-
end
|
19
|
-
|
20
|
-
class Part < ActiveRecord::Base
|
21
|
-
has_many :manifests
|
22
|
-
has_many :assemblies, through: :manifests
|
23
|
-
end
|
24
|
-
CODE
|
25
|
-
}
|
26
|
-
|
27
|
-
let(:parser) { Society::Parser.for_source(code) }
|
28
|
-
let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
|
29
|
-
let(:assembly) { processor.classes.detect {|c| c.name == "Assembly" } }
|
30
|
-
let(:manifest) { processor.classes.detect {|c| c.name == "Manifest" } }
|
31
|
-
let(:part) { processor.classes.detect {|c| c.name == "Part" } }
|
32
|
-
|
33
|
-
it "records `has_many` and `has_many through:` associations" do
|
34
|
-
assembly_associations = processor.associations.select { |edge| edge.from == assembly }
|
35
|
-
expect(assembly_associations.map(&:to)).to match_array [manifest, part]
|
36
|
-
|
37
|
-
part_associations = processor.associations.select { |edge| edge.from == part }
|
38
|
-
expect(part_associations.map(&:to)).to eq [manifest, assembly]
|
39
|
-
end
|
40
|
-
|
41
|
-
it "records `belongs_to` associations" do
|
42
|
-
manifest_associations = processor.associations.select { |edge| edge.from == manifest }
|
43
|
-
expect(manifest_associations.map(&:to)).to match_array [assembly, part]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context "associations with `class_name` specified" do
|
48
|
-
let(:code) {<<-CODE
|
49
|
-
class Post < ActiveRecord::Base
|
50
|
-
belongs_to :author, :class_name => "User"
|
51
|
-
belongs_to :editor, :class_name => "User"
|
52
|
-
end
|
53
|
-
|
54
|
-
class User < ActiveRecord::Base
|
55
|
-
has_many :authored_posts, :foreign_key => "author_id", :class_name => "Post"
|
56
|
-
has_many :edited_posts, :foreign_key => "editor_id", :class_name => "Post"
|
57
|
-
end
|
58
|
-
CODE
|
59
|
-
}
|
60
|
-
let(:parser) { Society::Parser.for_source(code) }
|
61
|
-
let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
|
62
|
-
let(:user) { processor.classes.detect {|c| c.name == "User" } }
|
63
|
-
let(:post) { processor.classes.detect {|c| c.name == "Post" } }
|
64
|
-
|
65
|
-
it "records belongs_to's" do
|
66
|
-
post_associations = processor.associations.select { |edge| edge.from == post }
|
67
|
-
expect(post_associations.map(&:to)).to match_array [user, user]
|
68
|
-
end
|
69
|
-
|
70
|
-
it "records has_many's" do
|
71
|
-
user_associations = processor.associations.select { |edge| edge.from == user }
|
72
|
-
expect(user_associations.map(&:to)).to match_array [post, post]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context "associations with `class_name` or `source` specified" do
|
77
|
-
let(:code) { <<-CODE
|
78
|
-
class Post < ActiveRecord::Base
|
79
|
-
has_many :post_authorings, :foreign_key => :authored_post_id
|
80
|
-
has_many :authors, :through => :post_authorings, :source => :post_author
|
81
|
-
belongs_to :editor, :class_name => "User"
|
82
|
-
end
|
83
|
-
|
84
|
-
class User < ActiveRecord::Base
|
85
|
-
has_many :post_authorings, :foreign_key => :post_author_id
|
86
|
-
has_many :authored_posts, :through => :post_authorings
|
87
|
-
has_many :edited_posts, :foreign_key => :editor_id, :class_name => "Post"
|
88
|
-
end
|
89
|
-
|
90
|
-
class PostAuthoring < ActiveRecord::Base
|
91
|
-
belongs_to :post_author, :class_name => "User"
|
92
|
-
belongs_to :authored_post, :class_name => "Post"
|
93
|
-
end
|
94
|
-
CODE
|
95
|
-
}
|
96
|
-
let(:parser) { Society::Parser.for_source(code) }
|
97
|
-
let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
|
98
|
-
let(:user) { processor.classes.detect {|c| c.name == "User" } }
|
99
|
-
let(:post) { processor.classes.detect {|c| c.name == "Post" } }
|
100
|
-
let(:post_authoring) { processor.classes.detect {|c| c.name == "PostAuthoring" } }
|
101
|
-
|
102
|
-
it "handles `source` and `class_name` correctly" do
|
103
|
-
post_associations = processor.associations.select { |edge| edge.from == post }
|
104
|
-
expect(post_associations.map(&:to)).to match_array [post_authoring, user, user]
|
105
|
-
end
|
106
|
-
|
107
|
-
it "handles `through` and `class_name` correctly" do
|
108
|
-
user_associations = processor.associations.select { |edge| edge.from == user }
|
109
|
-
expect(user_associations.map(&:to)).to match_array [post_authoring, post, post]
|
110
|
-
end
|
111
|
-
|
112
|
-
it "handles `class_name` correctly" do
|
113
|
-
pa_associations = processor.associations.select { |edge| edge.from == post_authoring }
|
114
|
-
expect(pa_associations.map(&:to)).to match_array [user, post]
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
context "polymorphic associations" do
|
119
|
-
let(:code) { <<-CODE
|
120
|
-
class Picture < ActiveRecord::Base
|
121
|
-
belongs_to :imageable, polymorphic: true
|
122
|
-
end
|
123
|
-
|
124
|
-
class Employee < ActiveRecord::Base
|
125
|
-
has_many :pictures, as: :imageable
|
126
|
-
end
|
127
|
-
|
128
|
-
class Product < ActiveRecord::Base
|
129
|
-
has_many :pictures, as: :imageable
|
130
|
-
end
|
131
|
-
CODE
|
132
|
-
}
|
133
|
-
let(:parser) { Society::Parser.for_source(code) }
|
134
|
-
let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
|
135
|
-
let(:picture) { processor.classes.detect {|c| c.name == "Picture" } }
|
136
|
-
let(:employee) { processor.classes.detect {|c| c.name == "Employee" } }
|
137
|
-
let(:product) { processor.classes.detect {|c| c.name == "Product" } }
|
138
|
-
|
139
|
-
it "records the `polymorphic: true` side of the association" do
|
140
|
-
picture_associations = processor.associations.select { |edge| edge.from == picture }
|
141
|
-
expect(picture_associations.map(&:to)).to match_array [employee, product]
|
142
|
-
end
|
143
|
-
|
144
|
-
it "records the `as: :something` side of the asociation" do
|
145
|
-
employee_associations = processor.associations.select { |edge| edge.from == employee }
|
146
|
-
expect(employee_associations.map(&:to)).to match_array [picture]
|
147
|
-
|
148
|
-
product_associations = processor.associations.select { |edge| edge.from == product }
|
149
|
-
expect(product_associations .map(&:to)).to match_array [picture]
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
context "self joins" do
|
154
|
-
let(:code) {<<-CODE
|
155
|
-
class Employee < ActiveRecord::Base
|
156
|
-
has_many :subordinates, class_name: "Employee",
|
157
|
-
foreign_key: "manager_id"
|
158
|
-
|
159
|
-
belongs_to :manager, class_name: "Employee"
|
160
|
-
end
|
161
|
-
CODE
|
162
|
-
}
|
163
|
-
let(:parser) { Society::Parser.for_source(code) }
|
164
|
-
let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
|
165
|
-
let(:employee) { processor.classes.detect {|c| c.name == "Employee" } }
|
166
|
-
|
167
|
-
it "records self joins" do
|
168
|
-
employee_associations = processor.associations.select { |edge| edge.from == employee }
|
169
|
-
expect(employee_associations.map(&:to)).to match_array [employee, employee]
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
data/spec/clusterer_spec.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require_relative 'fixtures/clustering/clusterer_fixtures.rb'
|
3
|
-
|
4
|
-
describe Society::Clusterer do
|
5
|
-
|
6
|
-
describe "#cluster" do
|
7
|
-
let(:clusterer) { Society::Clusterer.new }
|
8
|
-
|
9
|
-
it "detects clusters" do
|
10
|
-
clusters = clusterer.cluster(MCL::GRAPH_1).map(&:sort)
|
11
|
-
expected = MCL::CLUSTERS_1.map(&:sort)
|
12
|
-
expect(clusters).to match_array(expected)
|
13
|
-
end
|
14
|
-
|
15
|
-
context "with inflation parameter set to 1.7" do
|
16
|
-
let(:clusterer) { Society::Clusterer.new(inflation: 1.7) }
|
17
|
-
|
18
|
-
it "detects clusters at coarser granularity" do
|
19
|
-
clusters = clusterer.cluster(MCL::GRAPH_1).map(&:sort)
|
20
|
-
expected = MCL::CLUSTERS_1_I17.map(&:sort)
|
21
|
-
expect(clusters).to match_array(expected)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "with inflation parameter set to 4.0" do
|
26
|
-
let(:clusterer) { Society::Clusterer.new(inflation: 4.0) }
|
27
|
-
|
28
|
-
it "detects clusters at finer granularity" do
|
29
|
-
clusters = clusterer.cluster(MCL::GRAPH_1).map(&:sort)
|
30
|
-
expected = MCL::CLUSTERS_1_I40.map(&:sort)
|
31
|
-
expect(clusters).to match_array(expected)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
@@ -1,144 +0,0 @@
|
|
1
|
-
# Reference results were calculated using the `minimcl` perl script from
|
2
|
-
# release 14-137 of the MCL-edge library. http://micans.org/mcl
|
3
|
-
|
4
|
-
require 'set'
|
5
|
-
|
6
|
-
module MCL
|
7
|
-
|
8
|
-
def self.graph_from_edges(path)
|
9
|
-
full_path = File.join(File.dirname(__FILE__), path)
|
10
|
-
nodes = Set.new
|
11
|
-
edges = []
|
12
|
-
edges_with_weights = /(\S+)\s+(\S+)\s+(\S+)/
|
13
|
-
edges_only = /(\S+)\s+(\S+)/
|
14
|
-
File.readlines(full_path).each do |line|
|
15
|
-
if data = line.match(edges_with_weights)
|
16
|
-
a,b,weight = data[1..-1]
|
17
|
-
# FIXME: disregarding weight for now -- include it once we have
|
18
|
-
# weights in Edges
|
19
|
-
edges << Society::Edge.new(from: a.to_i, to: b.to_i)
|
20
|
-
nodes << a << b
|
21
|
-
elsif data = line.match(edges_only)
|
22
|
-
a,b = data[1..-1]
|
23
|
-
edges << Society::Edge.new(from: a.to_i, to: b.to_i)
|
24
|
-
nodes << a << b
|
25
|
-
end
|
26
|
-
end
|
27
|
-
Society::ObjectGraph.new(nodes: nodes.to_a, edges: edges)
|
28
|
-
end
|
29
|
-
|
30
|
-
GRAPH_1 = graph_from_edges('edges_1.txt')
|
31
|
-
|
32
|
-
CLUSTERS_1 = [
|
33
|
-
[9, 10, 19, 24, 25, 26, 28, 29, 30, 39, 58, 68, 102, 189, 198, 200],
|
34
|
-
[46, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139],
|
35
|
-
[0, 1, 4, 11, 38, 49, 52, 59, 60, 61, 71, 125],
|
36
|
-
[109, 110, 144, 145, 146, 147, 148, 149],
|
37
|
-
[81, 121, 122, 184, 185, 188],
|
38
|
-
[12, 31, 32, 79, 91, 124],
|
39
|
-
[7, 47, 53, 54, 78, 82],
|
40
|
-
[33, 83, 96, 113, 119],
|
41
|
-
[17, 55, 56, 57, 65],
|
42
|
-
[13, 42, 115, 120],
|
43
|
-
[14, 15, 27, 190],
|
44
|
-
[43, 90, 99, 118],
|
45
|
-
[173, 175, 193],
|
46
|
-
[88, 116, 117],
|
47
|
-
[73, 143, 176],
|
48
|
-
[77, 114, 116],
|
49
|
-
[20, 69, 103],
|
50
|
-
[41, 44, 112],
|
51
|
-
[16, 97, 98],
|
52
|
-
[3, 23, 126],
|
53
|
-
[183, 186],
|
54
|
-
[100, 101],
|
55
|
-
[107, 108],
|
56
|
-
[70, 104],
|
57
|
-
[87, 106],
|
58
|
-
[86, 105],
|
59
|
-
[50, 140],
|
60
|
-
[67, 95],
|
61
|
-
[85, 94],
|
62
|
-
[8, 111],
|
63
|
-
[66, 92],
|
64
|
-
[2, 84],
|
65
|
-
[182],
|
66
|
-
[93]]
|
67
|
-
|
68
|
-
CLUSTERS_1_I17 = [
|
69
|
-
[8, 9, 10, 13, 14, 15, 17, 19, 20, 24, 25, 26, 27, 28, 29, 30, 39, 41, 42, 43, 44, 55, 56, 57, 58, 65, 68, 69, 93, 102, 103, 111, 112, 115, 120, 189, 190, 198, 200],
|
70
|
-
[46, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139],
|
71
|
-
[0, 1, 4, 11, 38, 49, 52, 59, 60, 61, 71, 125],
|
72
|
-
[33, 83, 90, 96, 99, 113, 118, 119],
|
73
|
-
[109, 110, 144, 145, 146, 147, 148, 149],
|
74
|
-
[2, 7, 47, 53, 54, 78, 82, 84],
|
75
|
-
[70, 86, 87, 104, 105, 106],
|
76
|
-
[81, 121, 122, 184, 185, 188],
|
77
|
-
[12, 31, 32, 79, 91, 124],
|
78
|
-
[73, 143, 173, 176],
|
79
|
-
[88, 116, 117],
|
80
|
-
[77, 114, 116],
|
81
|
-
[16, 97, 98],
|
82
|
-
[3, 23, 126],
|
83
|
-
[183, 186],
|
84
|
-
[100, 101],
|
85
|
-
[50, 140],
|
86
|
-
[175, 193],
|
87
|
-
[107, 108],
|
88
|
-
[66, 92],
|
89
|
-
[67, 95],
|
90
|
-
[85, 94],
|
91
|
-
[182]]
|
92
|
-
|
93
|
-
CLUSTERS_1_I40 = [
|
94
|
-
[46, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139],
|
95
|
-
[10, 28, 29, 30, 39, 58, 146, 198, 200],
|
96
|
-
[109, 110, 144, 145, 147, 148, 149],
|
97
|
-
[81, 121, 122, 184, 185, 188],
|
98
|
-
[7, 47, 53, 54, 78, 82],
|
99
|
-
[14, 15, 24, 27, 190],
|
100
|
-
[0, 4, 38, 60, 125],
|
101
|
-
[13, 42, 115, 120],
|
102
|
-
[31, 32, 91, 124],
|
103
|
-
[17, 55, 56, 65],
|
104
|
-
[73, 143, 176],
|
105
|
-
[20, 69, 103],
|
106
|
-
[41, 44, 112],
|
107
|
-
[33, 83, 113],
|
108
|
-
[3, 23, 126],
|
109
|
-
[16, 97, 98],
|
110
|
-
[175, 193],
|
111
|
-
[107, 108],
|
112
|
-
[183, 186],
|
113
|
-
[100, 101],
|
114
|
-
[70, 104],
|
115
|
-
[43, 118],
|
116
|
-
[68, 102],
|
117
|
-
[88, 117],
|
118
|
-
[19, 189],
|
119
|
-
[50, 140],
|
120
|
-
[86, 105],
|
121
|
-
[77, 114],
|
122
|
-
[87, 106],
|
123
|
-
[8, 111],
|
124
|
-
[67, 95],
|
125
|
-
[25, 26],
|
126
|
-
[85, 94],
|
127
|
-
[12, 79],
|
128
|
-
[11, 49],
|
129
|
-
[52, 71],
|
130
|
-
[66, 92],
|
131
|
-
[2, 84],
|
132
|
-
[1, 61],
|
133
|
-
[119],
|
134
|
-
[173],
|
135
|
-
[116],
|
136
|
-
[182],
|
137
|
-
[57],
|
138
|
-
[93],
|
139
|
-
[90],
|
140
|
-
[99],
|
141
|
-
[59],
|
142
|
-
[96],
|
143
|
-
[9]]
|
144
|
-
end
|