diff --git a/MoreLinq.Test/MoveEnd.cs b/MoreLinq.Test/MoveEnd.cs new file mode 100644 index 000000000..b346640a6 --- /dev/null +++ b/MoreLinq.Test/MoveEnd.cs @@ -0,0 +1,121 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Leandro F. Vieira (leandromoh). All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq.Test +{ + using System.Collections.Generic; + using NUnit.Framework; + + [TestFixture] + public class MoveEndTest + { + [Test] + public void MoveEndEndWithNegativeFromIndex() + { + AssertThrowsArgument.OutOfRangeException("fromIndex", () => + new[] { 1 }.MoveEnd(-1, 0, 0)); + } + + [Test] + public void MoveEndWithNegativeCount() + { + AssertThrowsArgument.OutOfRangeException("count", () => + new[] { 1 }.MoveEnd(0, -1, 0)); + } + + [Test] + public void MoveEndWithNegativeToIndex() + { + AssertThrowsArgument.OutOfRangeException("toIndex", () => + new[] { 1 }.MoveEnd(0, 0, -1)); + } + + [Test] + public void MoveEndIsLazy() + { + new BreakingSequence().MoveEnd(0, 0, 0); + } + + [TestCaseSource(nameof(MoveEndSource))] + public void MoveEnd(int length, int fromIndex, int count, int toIndex) + { + var source = Enumerable.Range(0, length); + + var exclude = source.Exclude(fromIndex, count); + var slice = source.Slice(fromIndex, count); + var expectations = exclude.SkipLast(toIndex).Concat(slice).Concat(exclude.TakeLast(toIndex)); + + using (var test = source.AsTestingSequence()) + { + var result = test.MoveEnd(fromIndex, count, toIndex); + Assert.That(result, Is.EquivalentTo(expectations)); + } + } + + public static IEnumerable MoveEndSource() + { + const int length = 10; + + return from index in Enumerable.Range(0, length) + from count in Enumerable.Range(0, length + 1) + select new TestCaseData(length, index, count, index); + } + + [TestCaseSource(nameof(MoveEndWithSequenceShorterThanToIndexSource))] + public void MoveEndWithSequenceShorterThanToIndex(int length, int fromIndex, int count, int toIndex) + { + var source = Enumerable.Range(0, length); + + var exclude = source.Exclude(fromIndex, count); + var slice = source.Slice(fromIndex, count); + + var expectations = slice.Concat(exclude); + + using (var test = source.AsTestingSequence()) + { + var result = test.MoveEnd(fromIndex, count, toIndex); + Assert.That(result, Is.EquivalentTo(expectations)); + } + } + + public static IEnumerable MoveEndWithSequenceShorterThanToIndexSource() + { + const int length = 10; + + return Enumerable.Range(length, length + 5) + .Select(toIndex => new TestCaseData(length, 5, 2, toIndex)); + } + + [Test] + public void MoveEndWithFromIndexEqualsToIndex() + { + var source = Enumerable.Range(0, 10); + var result = source.MoveEnd(5, 999, 5); + + Assert.That(source, Is.SameAs(result)); + } + + [Test] + public void MoveEndWithCountEqualsZero() + { + var source = Enumerable.Range(0, 10); + var result = source.MoveEnd(5, 0, 999); + + Assert.That(source, Is.SameAs(result)); + } + } +} diff --git a/MoreLinq/MoveEnd.cs b/MoreLinq/MoveEnd.cs new file mode 100644 index 000000000..89e2da8d7 --- /dev/null +++ b/MoreLinq/MoveEnd.cs @@ -0,0 +1,93 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Leandro F. Vieira (leandromoh). All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq +{ + using System; + using System.Collections.Generic; + + static partial class MoreEnumerable + { + /// + /// Returns a sequence with a range of elements in the source sequence + /// moved to a new offset. + /// + /// Type of the source sequence. + /// The source sequence. + /// + /// The zero-based index identifying the first element in the range of + /// elements to move. + /// The count of items to move. + /// + /// The index where the specified range will be moved. + /// + /// A sequence with the specified range moved to the new position. + /// + /// + /// This operator uses deferred execution and streams its results. + /// + /// + /// + /// The result variable will contain { 3, 4, 0, 1, 2, 5 }. + /// + + public static IEnumerable MoveEnd(this IEnumerable source, int fromIndex, int count, int toIndex) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fromIndex < 0) throw new ArgumentOutOfRangeException(nameof(fromIndex), "The source index cannot be negative."); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be negative."); + if (toIndex < 0) throw new ArgumentOutOfRangeException(nameof(toIndex), "Target index of range to move cannot be negative."); + + if (toIndex == fromIndex || count == 0) + return source; + + return _(); IEnumerable _() + { + var queue = new Queue(toIndex); + var i = -1; + var endIndex = fromIndex + count - 1; + var list = new List(); + + foreach (var item in source) + { + i++; + + if (fromIndex <= i && i <= endIndex) + { + list.Add(item); + continue; + } + + if (queue.Count < toIndex) + { + queue.Enqueue(item); + continue; + } + + yield return queue.Dequeue(); + queue.Enqueue(item); + } + + foreach (var item in list) yield return item; + + foreach (var item in queue) yield return item; + } + } + } +}