Skip to content

Commit

Permalink
refactored merge and diff with chunk
Browse files Browse the repository at this point in the history
  • Loading branch information
dominictarr committed Mar 13, 2012
1 parent 3535a3b commit b6cc718
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 11 deletions.
Binary file added .index.js.swp
Binary file not shown.
Binary file added .test.js.swp
Binary file not shown.
111 changes: 104 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ function head (a) {
return a[0]
}

function last (a) {
return a[a.length - 1]
}

function tail(a) {
return a.slice(1)
}
Expand All @@ -11,6 +15,10 @@ function advance (e) {
return e.shift()
}

function retreat (e) {
return e.pop()
}

function hasLength (e) {
return e.length
}
Expand Down Expand Up @@ -74,11 +82,89 @@ function lcs(a, b) {
return recurse.apply(null, args)
}

// given n sequences, calc the lcs, and then chunk strings into stable and unstable sections.
// chunk(sequences, build)
// build should be (stable, unstable)
// either stable or unstable will be passed. but never both.
exports.chunk =
function (q, build) {
var q = q.map(function (e) { return e.slice() })
var lcs = exports.lcs.apply(null, q)

function matchLcs (e) {
return last(e) == last(lcs) || ((e.length + lcs.length) === 0)
}

while(any(q, hasLength)) {
//if each element is at the lcs then this chunk is stable.
var stable = [], unstable = []
while(q.every(matchLcs) && q.every(hasLength) ) {
stable.unshift(retreat(lcs))
q.forEach(retreat)
console.log('.', q)
}

build(q[0].length, stable)

//collect the changes in each array upto the next match with the lcs
var c = false
var unstable = q.map(function (e) {
var change = []
while(!matchLcs(e)) {
change.unshift(retreat(e))
c = true
}
return change
})
if(c)
build(q[0].length, null, unstable)

}
}

exports.diff =
function (a, b) {
var changes = []
// hmm, don't use stable here...
// do i even need it?
exports.chunk([a, b], function (index, _, unstable) {
if(unstable) {
var del = unstable.shift().length
var insert = unstable.shift()
changes.push([index, del].concat(insert))
}
})
return changes
}

// http://en.wikipedia.org/wiki/Concestor
// me, concestor, you...
exports.merge = function () {
var args = [].slice.call(arguments)
var r = []
exports.chunk(args, function (index, stable, unstable) {

if(stable)
r = stable.concat(r)
else {
unstable = unstable.slice()
var _o = unstable.splice(1, 1)[0]
console.log('CONCESTOR', _o, unstable)
console.log('INDEX', index)
// there are special rules when for changes at the end?
r = [].concat(resolve(_o, unstable)).concat(r)

}
})
return r
}

// generate a list of changes between a and b.
// changes are stored in the format of arguments to splice.
// index, numToDelete, ...itemsToInstert
// THIS IS NEARLY EXACTLY LIKE merge() !!
exports.diff =
/*
exports._diff =
function diff (a , b) {
a = a.slice()
b = b.slice()
Expand Down Expand Up @@ -118,7 +204,7 @@ function diff (a , b) {
}
return changes
}

*/
exports.patch = function (a, changes, mutate) {
if(mutate !== true) a = a.slice(a)//copy a
changes.forEach(function (change) {
Expand Down Expand Up @@ -161,8 +247,19 @@ var rules = [
console.log('c = ', c, 'odd:', odd)
if(c == 0) //this means that the concestor was the odd one.
return changes[1] //that means the changes are the same 'false conflict'
else
return // full confilct
else {
var nonempty
for (var i = 1; i < changes.length; i++)
if(changes[i].length)
if(!nonempty)
nonempty = changes[i]
else
return

return nonempty// full confilct
//hang on, if there is only one (decendant) item not empty
//then merge that, because the others where deletes
}
}
else // c must be 1
return odd
Expand All @@ -181,8 +278,8 @@ function resolve (concestor, changes) {
//if there is only one non empty change, use that.
return {'?': changes}
}

exports.merge =
/*
exports._merge =
function () { //mine, concestor, yours
var args = [].slice.call(arguments).map(function (e) { return e.slice() })
var lcs = exports.lcs.apply(null, args)
Expand Down Expand Up @@ -218,4 +315,4 @@ function () { //mine, concestor, yours
}
return r
}

*/
5 changes: 4 additions & 1 deletion readme.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ refactors:

should I generate a new sequence from the merge?
or generate a patch?
or just make the new sequence and then make the patch.
or just make the new sequence and then make the patch?

todo: maybe change how resolve works so that blame, or line age
will be possible.

#next

commit tree!



17 changes: 14 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ if(!module.parent) {
b = split(b)
lcs = split(lcs)
var _lcs = d.lcs(a, b)
d.chunk([a, b], console.log)
console.log(a.join(''),b.join(''),_lcs.join(''))
assert.deepEqual(_lcs, lcs)
var changes = d.diff(a,b)
console.log('changes>', changes)
console.log('changes>', changes, 'applyTo:',a)
var newA = d.patch(a, changes)
assert.deepEqual(newA, b)
}
Expand Down Expand Up @@ -46,8 +47,9 @@ if(!module.parent) {
}

// [this, concestor, other], expected
test3way(['abaaaa','aaaaa', 'aacaa'], 'abacaa') // simple change
test3way(['abaaa','aaaa', 'aacca'], 'abacca') // simple change
test3way(['abaaaa','aaaaa', 'aaacaa'], 'abaacaa') // simple change
// return
test3way(['abaa','aaa', 'aacca'], 'abacca') // simple change
test3way(['abaaa','aaaaa', 'abaaa'], 'abaaa') // same change aka 'false conflict'
test3way(['aaaaa','aaccaaa', 'aaccaaba'], 'aaaaba') // simple delete
// since b is deleted.
Expand All @@ -56,4 +58,13 @@ if(!module.parent) {
test3way(['aaa','axaa', 'axaab'], 'aaab')
test3way(['abaaba','aaaaa', 'aaacca'],
['a', 'b', 'a', 'a', {'?': [['b'], ['c','c']]}, 'a'])

// in these tests, i've replaced something, but you have just deleted it.
// it makes sense to merge my replace over your delete
test3way(['aBc', 'abc', 'acD'], 'aBcD')
test3way(['abaaa', 'aaaa', 'aacca'], 'abaacca')

//note, it's possible for this case to occur in a
//n-way merge where there is a delete and a false conflict.
//most merges will be only 3 ways, so lets leave that for now.
}

0 comments on commit b6cc718

Please sign in to comment.