Created
October 14, 2011 15:16
-
-
Save jmcd/1287394 to your computer and use it in GitHub Desktop.
General purpose move operation on entities with an integer rank
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace jmcd | |
{ | |
public class MoveOperation<T> | |
{ | |
private readonly Func<T, int> _positionOf; | |
private readonly Action<T, int> _reposition; | |
private readonly List<T> _moved = new List<T>(); | |
public MoveOperation(Func<T, int> positionOf, Action<T, int> reposition) | |
{ | |
_positionOf = positionOf; | |
_reposition = reposition; | |
} | |
public IList<T> MovedItems { get { return _moved; } } | |
public void Execute(IList<T> collection, T subject, int offset) | |
{ | |
if (offset == 0) return; | |
var currentIndex = _positionOf(subject); | |
var targetPosition = currentIndex + offset; | |
var siblingStrategy = offset > 0 ? | |
new MoveSiblingStrategy(t => _positionOf(t) > currentIndex && _positionOf(t) <= targetPosition, -1) : | |
new MoveSiblingStrategy(t => _positionOf(t) >= targetPosition && _positionOf(t) < currentIndex, 1); | |
var siblings = collection.Where(siblingStrategy.SelectorPredecate); | |
foreach (var sibling in siblings) | |
{ | |
MoveByOffsetAndRecord(sibling, siblingStrategy.Offset); | |
} | |
MoveByOffsetAndRecord(subject, offset); | |
} | |
private class MoveSiblingStrategy | |
{ | |
public readonly Func<T, bool> SelectorPredecate; | |
public readonly int Offset; | |
public MoveSiblingStrategy(Func<T, bool> selectorPredecate, int offset) | |
{ | |
SelectorPredecate = selectorPredecate; | |
Offset = offset; | |
} | |
} | |
private void MoveByOffsetAndRecord(T item, int offset) | |
{ | |
var newPosition = _positionOf(item); | |
newPosition += offset; | |
_reposition(item, newPosition); | |
_moved.Add(item); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Linq; | |
using Events.Web.Core.Util; | |
using NUnit.Framework; | |
namespace jmcd | |
{ | |
public class MoveOperationTestFixtures | |
{ | |
public class Fruit | |
{ | |
public int Position; | |
public string Name; | |
} | |
public class FruitContext | |
{ | |
public List<Fruit> Fruits; | |
private IList<Fruit> _movedItems; | |
private class Counter | |
{ | |
private int _next = 0; | |
public int Next() | |
{ | |
var result = _next; | |
_next++; | |
return result; | |
} | |
} | |
public FruitContext() | |
{ | |
var counter = new Counter(); | |
Fruits = new[] {"apple", "banana", "pear"}.Select(name => new Fruit {Name = name, Position = counter.Next()}).ToList(); | |
} | |
public void AssertPositions(params string[] names) | |
{ | |
var orderedFruits = Fruits.OrderBy(x => x.Position); | |
var actualNames = orderedFruits.Select(x => x.Name).ToArray(); | |
CollectionAssert.AreEqual(names, actualNames); | |
var distinctPositions = orderedFruits.Select(x => x.Position).ToList(); | |
Assert.AreEqual(Fruits.Count, distinctPositions.Count); | |
} | |
public void Move(string name, int offset) | |
{ | |
var subject = Fruits.Where(x => x.Name == name).Single(); | |
var moveOperation = new MoveOperation<Fruit>(fruit => fruit.Position, (fruit, newPosition) => fruit.Position = newPosition); | |
moveOperation.Execute(Fruits, subject, offset); | |
_movedItems = moveOperation.MovedItems; | |
} | |
public void AssertMovedItemCount(int expected) | |
{ | |
Assert.AreEqual(expected, _movedItems.Count); | |
} | |
public void AssertMoved(string name) | |
{ | |
Assert.IsTrue(_movedItems.Any(x => x.Name == name)); | |
} | |
} | |
[TestFixture] | |
public class InitialFruitContext | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("apple", "banana", "pear"); | |
} | |
} | |
[TestFixture] | |
public class MoveFirstDown | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("apple", 1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("banana", "apple", "pear"); | |
} | |
[Test] | |
public void TwoItemsMoved() | |
{ | |
_context.AssertMovedItemCount(2); | |
} | |
[Test] | |
public void FirstItemMoved() | |
{ | |
_context.AssertMoved("apple"); | |
} | |
[Test] | |
public void MiddleItemMoved() | |
{ | |
_context.AssertMoved("banana"); | |
} | |
} | |
[TestFixture] | |
public class MoveFirstUp | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("apple", -1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("apple", "banana", "pear"); | |
} | |
[Test] | |
public void OneItemsMoved() | |
{ | |
_context.AssertMovedItemCount(1); | |
} | |
[Test] | |
public void FirstItemMoved() | |
{ | |
_context.AssertMoved("apple"); | |
} | |
} | |
[TestFixture] | |
public class MoveMiddleDown | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("banana", 1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("apple", "pear", "banana"); | |
} | |
[Test] | |
public void TwoItemsMoved() | |
{ | |
_context.AssertMovedItemCount(2); | |
} | |
[Test] | |
public void MiddleItemMoved() | |
{ | |
_context.AssertMoved("banana"); | |
} | |
[Test] | |
public void LastItemMoved() | |
{ | |
_context.AssertMoved("pear"); | |
} | |
} | |
[TestFixture] | |
public class MoveMiddleUp | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("banana", -1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("banana", "apple", "pear"); | |
} | |
[Test] | |
public void TwoItemsMoved() | |
{ | |
_context.AssertMovedItemCount(2); | |
} | |
[Test] | |
public void FirstItemMoved() | |
{ | |
_context.AssertMoved("apple"); | |
} | |
[Test] | |
public void MiddleItemMoved() | |
{ | |
_context.AssertMoved("banana"); | |
} | |
} | |
[TestFixture] | |
public class MoveLastDown | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("pear", 1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("apple", "banana", "pear"); | |
} | |
[Test] | |
public void OneItemsMoved() | |
{ | |
_context.AssertMovedItemCount(1); | |
} | |
[Test] | |
public void LastItemMoved() | |
{ | |
_context.AssertMoved("pear"); | |
} | |
} | |
[TestFixture] | |
public class MoveLastUp | |
{ | |
private readonly FruitContext _context = new FruitContext(); | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
_context.Move("pear", -1); | |
} | |
[Test] | |
public void PositionsCorrect() | |
{ | |
_context.AssertPositions("apple", "pear", "banana"); | |
} | |
[Test] | |
public void TwoItemsMoved() | |
{ | |
_context.AssertMovedItemCount(2); | |
} | |
[Test] | |
public void MiddleItemMoved() | |
{ | |
_context.AssertMoved("banana"); | |
} | |
[Test] | |
public void LastItemMoved() | |
{ | |
_context.AssertMoved("pear"); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment