tdiff 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.2.0 / 2010-11-14
2
+
3
+ * Added {TDiff::Unordered}.
4
+
1
5
  ### 0.1.0 / 2010-11-13
2
6
 
3
7
  * Initial release:
data/README.md CHANGED
@@ -13,9 +13,10 @@ module.
13
13
  ## Features
14
14
 
15
15
  * Provides the {TDiff} mixin.
16
+ * Provides the {TDiff::Unordered} mixin for unordered diffing.
16
17
  * Allows custom node equality and traversal logic by overriding the
17
18
  {TDiff#tdiff_equal} and {TDiff#tdiff_each_child} methods.
18
- * Implements the [Longest Common Subsequence (LCS)](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem).
19
+ * Implements the [Longest Common Subsequence (LCS)](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem) algorithm.
19
20
 
20
21
  ## Examples
21
22
 
data/gemspec.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: tdiff
2
- version: 0.1.0
2
+ version: 0.2.0
3
3
  summary: Calculates the differences between two tree-like structures.
4
4
  description:
5
5
  Calculates the differences between two tree-like structures. Similar to
data/lib/tdiff.rb CHANGED
@@ -1,133 +1,2 @@
1
- #
2
- # {TDiff} adds the ability to calculate the differences between two tree-like
3
- # objects. Simply include {TDiff} into the class which represents the tree
4
- # nodes and define the {#tdiff_each_child} and {#tdiff_equal} methods.
5
- #
6
- module TDiff
7
- #
8
- # Default method which will enumerate over every child of a parent node.
9
- #
10
- # @param [Object] node
11
- # The parent node.
12
- #
13
- # @yield [child]
14
- # The given block will be passed each child of the parent node.
15
- #
16
- def tdiff_each_child(node,&block)
17
- node.each(&block) if node.kind_of?(Enumerable)
18
- end
19
-
20
- #
21
- # Default method which compares two nodes.
22
- #
23
- # @param [Object] original_node
24
- # A node from the original tree.
25
- #
26
- # @param [Object] new_node
27
- # A node from the new tree.
28
- #
29
- # @return [Boolean]
30
- # Specifies whether the two nodes are equal.
31
- #
32
- def tdiff_equal(original_node,new_node)
33
- original_node == new_node
34
- end
35
-
36
- #
37
- # Finds the differences between `self` and another tree.
38
- #
39
- # @param [#tdiff_each_child] tree
40
- # The other tree.
41
- #
42
- # @yield [change, node]
43
- # The given block will be passed the added or removed nodes.
44
- #
45
- # @yieldparam [' ', '+', '-'] change
46
- # The state-change of the node.
47
- #
48
- # @yieldparam [Object] node
49
- # A node from one of the two trees.
50
- #
51
- # @return [Enumerator]
52
- # If no block is given, an Enumerator object will be returned.
53
- #
54
- def tdiff(tree,&block)
55
- return enum_for(:tdiff,tree) unless block
56
-
57
- # check if the nodes differ
58
- unless tdiff_equal(self,tree)
59
- yield '-', self
60
- yield '+', tree
61
- return self
62
- end
63
-
64
- c = Hash.new { |hash,key| hash[key] = Hash.new(0) }
65
- x = enum_for(:tdiff_each_child,self)
66
- y = enum_for(:tdiff_each_child,tree)
67
-
68
- x.each_with_index do |xi,i|
69
- y.each_with_index do |yi,j|
70
- c[i][j] = if tdiff_equal(xi,yi)
71
- c[i-1][j-1] + 1
72
- else
73
- if c[i][j-1] > c[i-1][j]
74
- c[i][j-1]
75
- else
76
- c[i-1][j]
77
- end
78
- end
79
- end
80
- end
81
-
82
- unchanged = []
83
- changes = []
84
-
85
- x_backtrack = x.each_with_index.reverse_each
86
- y_backtrack = y.each_with_index.reverse_each
87
-
88
- next_child = lambda { |children|
89
- begin
90
- children.next
91
- rescue StopIteration
92
- # end of iteration, return a -1 index
93
- [nil, -1]
94
- end
95
- }
96
-
97
- xi, i = next_child[x_backtrack]
98
- yi, j = next_child[y_backtrack]
99
-
100
- until (i == -1 && j == -1)
101
- if (i != -1 && j != -1 && tdiff_equal(xi,yi))
102
- changes.unshift [' ', xi]
103
- unchanged.unshift [xi, yi]
104
-
105
- xi, i = next_child[x_backtrack]
106
- yi, j = next_child[y_backtrack]
107
- else
108
- if (j >= 0 && (i == -1 || c[i][j-1] >= c[i-1][j]))
109
- changes.unshift ['+', yi]
110
-
111
- yi, j = next_child[y_backtrack]
112
- elsif (i >= 0 && (j == -1 || c[i][j-1] < c[i-1][j]))
113
- changes.unshift ['-', xi]
114
-
115
- xi, i = next_child[x_backtrack]
116
- end
117
- end
118
- end
119
-
120
- # explicitly discard the c matrix
121
- c = nil
122
-
123
- # sequentially iterate over the changed nodes
124
- changes.each(&block)
125
- changes = nil
126
-
127
- # recurse down through unchanged nodes
128
- unchanged.each { |x,y| x.tdiff(y,&block) }
129
- unchanged = nil
130
-
131
- return self
132
- end
133
- end
1
+ require 'tdiff/tdiff'
2
+ require 'tdiff/unordered'
@@ -0,0 +1,133 @@
1
+ #
2
+ # {TDiff} adds the ability to calculate the differences between two tree-like
3
+ # objects. Simply include {TDiff} into the class which represents the tree
4
+ # nodes and define the {#tdiff_each_child} and {#tdiff_equal} methods.
5
+ #
6
+ module TDiff
7
+ #
8
+ # Default method which will enumerate over every child of a parent node.
9
+ #
10
+ # @param [Object] node
11
+ # The parent node.
12
+ #
13
+ # @yield [child]
14
+ # The given block will be passed each child of the parent node.
15
+ #
16
+ def tdiff_each_child(node,&block)
17
+ node.each(&block) if node.kind_of?(Enumerable)
18
+ end
19
+
20
+ #
21
+ # Default method which compares two nodes.
22
+ #
23
+ # @param [Object] original_node
24
+ # A node from the original tree.
25
+ #
26
+ # @param [Object] new_node
27
+ # A node from the new tree.
28
+ #
29
+ # @return [Boolean]
30
+ # Specifies whether the two nodes are equal.
31
+ #
32
+ def tdiff_equal(original_node,new_node)
33
+ original_node == new_node
34
+ end
35
+
36
+ #
37
+ # Finds the differences between `self` and another tree.
38
+ #
39
+ # @param [#tdiff_each_child] tree
40
+ # The other tree.
41
+ #
42
+ # @yield [change, node]
43
+ # The given block will be passed the added or removed nodes.
44
+ #
45
+ # @yieldparam [' ', '+', '-'] change
46
+ # The state-change of the node.
47
+ #
48
+ # @yieldparam [Object] node
49
+ # A node from one of the two trees.
50
+ #
51
+ # @return [Enumerator]
52
+ # If no block is given, an Enumerator object will be returned.
53
+ #
54
+ def tdiff(tree,&block)
55
+ return enum_for(:tdiff,tree) unless block
56
+
57
+ # check if the nodes differ
58
+ unless tdiff_equal(self,tree)
59
+ yield '-', self
60
+ yield '+', tree
61
+ return self
62
+ end
63
+
64
+ c = Hash.new { |hash,key| hash[key] = Hash.new(0) }
65
+ x = enum_for(:tdiff_each_child,self)
66
+ y = enum_for(:tdiff_each_child,tree)
67
+
68
+ x.each_with_index do |xi,i|
69
+ y.each_with_index do |yj,j|
70
+ c[i][j] = if tdiff_equal(xi,yj)
71
+ c[i-1][j-1] + 1
72
+ else
73
+ if c[i][j-1] > c[i-1][j]
74
+ c[i][j-1]
75
+ else
76
+ c[i-1][j]
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ unchanged = []
83
+ changes = []
84
+
85
+ x_backtrack = x.each_with_index.reverse_each
86
+ y_backtrack = y.each_with_index.reverse_each
87
+
88
+ next_child = lambda { |children|
89
+ begin
90
+ children.next
91
+ rescue StopIteration
92
+ # end of iteration, return a -1 index
93
+ [nil, -1]
94
+ end
95
+ }
96
+
97
+ xi, i = next_child[x_backtrack]
98
+ yj, j = next_child[y_backtrack]
99
+
100
+ until (i == -1 && j == -1)
101
+ if (i != -1 && j != -1 && tdiff_equal(xi,yj))
102
+ changes.unshift [' ', xi]
103
+ unchanged.unshift [xi, yj]
104
+
105
+ xi, i = next_child[x_backtrack]
106
+ yj, j = next_child[y_backtrack]
107
+ else
108
+ if (j >= 0 && (i == -1 || c[i][j-1] >= c[i-1][j]))
109
+ changes.unshift ['+', yj]
110
+
111
+ yj, j = next_child[y_backtrack]
112
+ elsif (i >= 0 && (j == -1 || c[i][j-1] < c[i-1][j]))
113
+ changes.unshift ['-', xi]
114
+
115
+ xi, i = next_child[x_backtrack]
116
+ end
117
+ end
118
+ end
119
+
120
+ # explicitly discard the c matrix
121
+ c = nil
122
+
123
+ # sequentially iterate over the changed nodes
124
+ changes.each(&block)
125
+ changes = nil
126
+
127
+ # recurse down through unchanged nodes
128
+ unchanged.each { |x,y| x.tdiff(y,&block) }
129
+ unchanged = nil
130
+
131
+ return self
132
+ end
133
+ end
@@ -0,0 +1,88 @@
1
+ require 'tdiff/tdiff'
2
+
3
+ module TDiff
4
+ #
5
+ # Calculates the differences between two trees, without respecting the
6
+ # order of children nodes.
7
+ #
8
+ module Unordered
9
+ #
10
+ # Includes {TDiff}.
11
+ #
12
+ def self.included(base)
13
+ base.send :include, TDiff
14
+ end
15
+
16
+ #
17
+ # Finds the differences between `self` and another tree, not respecting
18
+ # the ordering of children.
19
+ #
20
+ # @param [#tdiff_each_child] tree
21
+ # The other tree.
22
+ #
23
+ # @yield [change, node]
24
+ # The given block will be passed the added or removed nodes.
25
+ #
26
+ # @yieldparam [' ', '+', '-'] change
27
+ # The state-change of the node.
28
+ #
29
+ # @yieldparam [Object] node
30
+ # A node from one of the two trees.
31
+ #
32
+ # @return [Enumerator]
33
+ # If no block is given, an Enumerator object will be returned.
34
+ #
35
+ # @since 0.2.0
36
+ #
37
+ def tdiff_unordered(tree,&block)
38
+ return enum_for(:tdiff_unordered,tree) unless block
39
+
40
+ # check if the nodes differ
41
+ unless tdiff_equal(self,tree)
42
+ yield '-', self
43
+ yield '+', tree
44
+ return self
45
+ end
46
+
47
+ x = enum_for(:tdiff_each_child,self)
48
+ y = enum_for(:tdiff_each_child,tree)
49
+
50
+ unchanged = {}
51
+ changes = []
52
+
53
+ x.each_with_index do |xi,i|
54
+ y.each_with_index do |yj,j|
55
+ if (!unchanged.has_value?(yj) && tdiff_equal(xi,yj))
56
+ unchanged[xi] = yj
57
+ changes << [i, ' ', xi]
58
+ break
59
+ end
60
+ end
61
+
62
+ unless unchanged.has_key?(xi)
63
+ changes << [i, '-', xi]
64
+ end
65
+ end
66
+
67
+ y.each_with_index do |yj,j|
68
+ unless unchanged.has_key?(yj)
69
+ changes << [j, '+', yj]
70
+ end
71
+ end
72
+
73
+ # order the changes by index to match the behavior of `tdiff`
74
+ changes.sort_by { |change| change[0] }.each do |index,change,node|
75
+ yield change, node
76
+ end
77
+
78
+ # explicitly release the changes variable
79
+ changes = nil
80
+
81
+ # recurse down the unchanged nodes
82
+ unchanged.each { |xi,yj| xi.tdiff_unordered(yj,&block) }
83
+ unchanged = nil
84
+
85
+ return self
86
+ end
87
+ end
88
+ end
data/spec/classes/node.rb CHANGED
@@ -3,6 +3,7 @@ require 'tdiff'
3
3
  class Node < Struct.new(:name, :children)
4
4
 
5
5
  include TDiff
6
+ include TDiff::Unordered
6
7
 
7
8
  def tdiff_each_child(node,&block)
8
9
  node.children.each(&block)
@@ -0,0 +1,61 @@
1
+ require 'classes/node'
2
+
3
+ module Helpers
4
+ module Trees
5
+ def self.included(base)
6
+ base.module_eval do
7
+ before(:all) do
8
+ @tree = Node.new('root', [
9
+ Node.new('leaf1', [
10
+ Node.new('subleaf1', []),
11
+ Node.new('subleaf2', [])
12
+ ]),
13
+
14
+ Node.new('leaf2', [
15
+ Node.new('subleaf1', []),
16
+ Node.new('subleaf2', [])
17
+ ])
18
+ ])
19
+
20
+ @different_root = Node.new('wrong', [])
21
+
22
+ @added = Node.new('root', [
23
+ Node.new('leaf1', [
24
+ Node.new('subleaf1', []),
25
+ Node.new('subleaf3', []),
26
+ Node.new('subleaf2', [])
27
+ ]),
28
+
29
+ Node.new('leaf2', [
30
+ Node.new('subleaf1', []),
31
+ Node.new('subleaf2', [])
32
+ ])
33
+ ])
34
+
35
+ @removed = Node.new('root', [
36
+ Node.new('leaf1', [
37
+ Node.new('subleaf1', [])
38
+ ]),
39
+
40
+ Node.new('leaf2', [
41
+ Node.new('subleaf1', []),
42
+ Node.new('subleaf2', [])
43
+ ])
44
+ ])
45
+
46
+ @changed_order = Node.new('root', [
47
+ Node.new('leaf2', [
48
+ Node.new('subleaf1', []),
49
+ Node.new('subleaf2', [])
50
+ ]),
51
+
52
+ Node.new('leaf1', [
53
+ Node.new('subleaf1', []),
54
+ Node.new('subleaf2', [])
55
+ ])
56
+ ])
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'helpers/trees'
3
+
4
+ shared_examples_for 'TDiff' do |method|
5
+ include Helpers::Trees
6
+
7
+ it "should tell if two trees are identical" do
8
+ @tree.send(method,@tree).all? { |change,node|
9
+ change == ' '
10
+ }.should == true
11
+ end
12
+
13
+ it "should stop if the root nodes have changed" do
14
+ changes = @tree.send(method,@different_root).to_a
15
+
16
+ changes.length.should == 2
17
+
18
+ changes[0][0].should == '-'
19
+ changes[0][1].should == @tree
20
+
21
+ changes[1][0].should == '+'
22
+ changes[1][1].should == @different_root
23
+ end
24
+
25
+ it "should tell when sub-nodes are added" do
26
+ changes = @tree.tdiff(@added).select { |change,node| change == '+' }
27
+
28
+ changes.length.should == 1
29
+ changes[0][0].should == '+'
30
+ changes[0][1].should == @added.children[0].children[1]
31
+ end
32
+
33
+ it "should tell when sub-nodes are removed" do
34
+ changes = @tree.tdiff(@removed).select { |change,node| change == '-' }
35
+
36
+ changes.length.should == 1
37
+ changes[0][0].should == '-'
38
+ changes[0][1].should == @tree.children[0].children[1]
39
+ end
40
+ end
data/spec/tdiff_spec.rb CHANGED
@@ -1,93 +1,12 @@
1
1
  require 'spec_helper'
2
- require 'classes/node'
3
- require 'tdiff'
2
+ require 'helpers/trees'
3
+ require 'tdiff_examples'
4
+ require 'tdiff/tdiff'
4
5
 
5
6
  describe TDiff do
6
- before(:all) do
7
- @tree = Node.new('root', [
8
- Node.new('leaf1', [
9
- Node.new('subleaf1', []),
10
- Node.new('subleaf2', [])
11
- ]),
7
+ include Helpers::Trees
12
8
 
13
- Node.new('leaf2', [
14
- Node.new('subleaf1', []),
15
- Node.new('subleaf2', [])
16
- ])
17
- ])
18
-
19
- @different_root = Node.new('wrong', [])
20
-
21
- @added = Node.new('root', [
22
- Node.new('leaf1', [
23
- Node.new('subleaf1', []),
24
- Node.new('subleaf3', []),
25
- Node.new('subleaf2', [])
26
- ]),
27
-
28
- Node.new('leaf2', [
29
- Node.new('subleaf1', []),
30
- Node.new('subleaf2', [])
31
- ])
32
- ])
33
-
34
- @removed = Node.new('root', [
35
- Node.new('leaf1', [
36
- Node.new('subleaf1', [])
37
- ]),
38
-
39
- Node.new('leaf2', [
40
- Node.new('subleaf1', []),
41
- Node.new('subleaf2', [])
42
- ])
43
- ])
44
-
45
- @changed_order = Node.new('root', [
46
- Node.new('leaf2', [
47
- Node.new('subleaf1', []),
48
- Node.new('subleaf2', [])
49
- ]),
50
-
51
- Node.new('leaf1', [
52
- Node.new('subleaf1', []),
53
- Node.new('subleaf2', [])
54
- ])
55
- ])
56
- end
57
-
58
- it "should tell if two trees are identical" do
59
- @tree.tdiff(@tree).all? { |change,node|
60
- change == ' '
61
- }.should == true
62
- end
63
-
64
- it "should stop if the root nodes have changed" do
65
- changes = @tree.tdiff(@different_root).to_a
66
-
67
- changes.length.should == 2
68
-
69
- changes[0][0].should == '-'
70
- changes[0][1].should == @tree
71
-
72
- changes[1][0].should == '+'
73
- changes[1][1].should == @different_root
74
- end
75
-
76
- it "should tell when sub-nodes are added" do
77
- changes = @tree.tdiff(@added).select { |change,node| change == '+' }
78
-
79
- changes.length.should == 1
80
- changes[0][0].should == '+'
81
- changes[0][1].should == @added.children[0].children[1]
82
- end
83
-
84
- it "should tell when sub-nodes are removed" do
85
- changes = @tree.tdiff(@removed).select { |change,node| change == '-' }
86
-
87
- changes.length.should == 1
88
- changes[0][0].should == '-'
89
- changes[0][1].should == @tree.children[0].children[1]
90
- end
9
+ it_should_behave_like 'TDiff', :tdiff
91
10
 
92
11
  it "should detect when the order of children has changed" do
93
12
  changes = @tree.tdiff(@changed_order).to_a
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'helpers/trees'
3
+ require 'tdiff/unordered'
4
+
5
+ describe TDiff::Unordered do
6
+ include Helpers::Trees
7
+
8
+ it "should include TDiff when included" do
9
+ base = Class.new do
10
+ include TDiff::Unordered
11
+ end
12
+
13
+ base.should include(TDiff)
14
+ end
15
+
16
+ it_should_behave_like 'TDiff', :tdiff_unordered
17
+
18
+ it "should not detect when the order of children has changed" do
19
+ changes = @tree.tdiff_unordered(@changed_order).select do |change,node|
20
+ change != ' '
21
+ end
22
+
23
+ changes.should be_empty
24
+ end
25
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Postmodern
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-13 00:00:00 -08:00
17
+ date: 2010-11-14 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -82,9 +82,14 @@ files:
82
82
  - Rakefile
83
83
  - gemspec.yml
84
84
  - lib/tdiff.rb
85
+ - lib/tdiff/tdiff.rb
86
+ - lib/tdiff/unordered.rb
85
87
  - spec/classes/node.rb
88
+ - spec/helpers/trees.rb
86
89
  - spec/spec_helper.rb
90
+ - spec/tdiff_examples.rb
87
91
  - spec/tdiff_spec.rb
92
+ - spec/unordered_spec.rb
88
93
  - tdiff.gemspec
89
94
  has_rdoc: yard
90
95
  homepage: http://github.com/postmodern/tdiff
@@ -120,3 +125,4 @@ specification_version: 3
120
125
  summary: Calculates the differences between two tree-like structures.
121
126
  test_files:
122
127
  - spec/tdiff_spec.rb
128
+ - spec/unordered_spec.rb