tdiff 0.1.0 → 0.2.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.
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