Skip to content

Commit

Permalink
Added reduceRight implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrei15193 committed May 5, 2024
1 parent 21a1a4a commit eb0b92a
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 6 deletions.
18 changes: 16 additions & 2 deletions src/collections/IReadOnlyObservableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,22 @@ export interface IReadOnlyObservableCollection<TItem> extends Iterable<TItem>, I
*/
reduce<TResult>(callbackfn: (result: TResult, item: TItem, index: number, collection: this) => TResult, initialValue: TResult): TResult;

reduceRight(callback: (accumulator: TItem, item: TItem, index: number, colleciton: this) => TItem): TItem;
reduceRight<TResult>(callback: (accumulator: TResult, item: TItem, index: number, colleciton: this) => TItem, initialValue: TResult): TItem;
/**
* Aggregates the collection to a single item by iterating the collection from end to start.
* @param callbackfn The callback that aggregates two items at a time.
* @returns Returns a single aggregated item.
* @see [Array.reduceRight](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight)
*/
reduceRight(callbackfn: (previousItem: TItem, currentItem: TItem, currentIndex: number, collection: this) => TItem): TItem;
/**
* Aggregates the collection to a single value by iterating the collection from end to start.
* @template TResult The result value type to which items are aggregated.
* @param callbackfn The callback that aggregates one item and the previous value at a time.
* @param initialValue The initial value when aggregating the collection.
* @returns Returns the value containing the aggregated collection.
* @see [Array.reduceRight](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight)
*/
reduceRight<TResult>(callbackfn: (result: TResult, item: TItem, index: number, collection: this) => TResult, initialValue: TResult): TResult;

/**
* Converts the observable collection to a native JavaScript [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array).
Expand Down
21 changes: 17 additions & 4 deletions src/collections/ReadOnlyObservableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,6 @@ export class ReadOnlyObservableCollection<TItem> extends ViewModel implements IR
}

return result;

}

/**
Expand All @@ -566,7 +565,7 @@ export class ReadOnlyObservableCollection<TItem> extends ViewModel implements IR
* @returns Returns a single aggregated item.
* @see [Array.reduceRight](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight)
*/
public reduceRight(callbackfn: (previousValue: TItem, currentItem: TItem, currentIndex: number, collection: this) => TItem): TItem;
public reduceRight(callbackfn: (previousItem: TItem, currentItem: TItem, currentIndex: number, collection: this) => TItem): TItem;
/**
* Aggregates the collection to a single value by iterating the collection from end to start.
* @template TResult The result value type to which items are aggregated.
Expand All @@ -575,10 +574,24 @@ export class ReadOnlyObservableCollection<TItem> extends ViewModel implements IR
* @returns Returns the value containing the aggregated collection.
* @see [Array.reduceRight](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight)
*/
public reduceRight<TResult>(callbackfn: (previousValue: TResult, currentItem: TItem, currentIndex: number, collection: this) => TResult, initialValue: TResult): TResult;
public reduceRight<TResult>(callbackfn: (result: TResult, item: TItem, index: number, collection: this) => TResult, initialValue: TResult): TResult;

public reduceRight(callbackfn: any, initialValue?: any): any {
throw new Error('Method not implemented.');
if (arguments.length === 1 && this._length === 0)
throw new Error('Cannot reduce an empty collection without providing an initial value.');

const changeTokenCopy = this._changeToken;

let result = arguments.length === 1 ? this[this._length - 1] : initialValue;
const startIndex = arguments.length === 1 ? this._length - 2 : this._length - 1;
for (let index = startIndex; index >= 0; index--) {
result = callbackfn(result, this[index], index, this);

if (changeTokenCopy !== this._changeToken)
throw new Error('Collection has changed while being iterated.');
}

return result;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ObservableCollection } from '../../ObservableCollection';
import { testBlankMutatingOperation } from './common';

describe('ObserableCollection.reduceRight', (): void => {
it('reducing an empty collection without initial value throws exception', (): void => {
const observableCollection = new ObservableCollection<number>();

expect(
() => {
observableCollection.reduceRight((previous, current) => previous + current);
})
.toThrow(new Error('Cannot reduce an empty collection without providing an initial value.'));
});

it('reducing a collection goes through each item', (): void => {
testBlankMutatingOperation<number>({
initialState: [1, 2, 3],

applyOperation: {
applyArrayOperation: array => array.reduceRight((previous, current) => previous * 10 + current),
applyCollectionOperation: collection => collection.reduceRight((previous, current) => previous * 10 + current)
}
});
});

it('reducing an empty collection with initial value returns it', (): void => {
const initialValue: object = {};

testBlankMutatingOperation<number>({
initialState: [],

applyOperation: {
applyArrayOperation: array => array.reduceRight(result => result, initialValue),
applyCollectionOperation: collection => collection.reduceRight(result => result, initialValue)
}
});
});

it('reducing a collection with initial value goes through each item', (): void => {
testBlankMutatingOperation<number>({
initialState: [1, 2, 3],

applyOperation: {
applyArrayOperation: array => array.reduceRight((result, current) => result + '0' + current.toString(), '0'),
applyCollectionOperation: collection => collection.reduceRight((result, current) => result + '0' + current.toString(), '0')
}
});
});

it('calling reduceRight passes arguments to each parameter accordingly', (): void => {
let invocationCount = 0;
const observableCollection = new ObservableCollection<number>(1);
const initialValue = {};
observableCollection.reduceRight(
(result, item, index, collection) => {
invocationCount++;

expect(result).toBe(initialValue);
expect(item).toBe(1);
expect(index).toBe(0);
expect(collection).toStrictEqual(observableCollection);

return item;
},
initialValue
);

expect(invocationCount).toBe(1);
});

it('modifying the collection while executing reduceRight throws exception', (): void => {
expect(
() => {
const observableCollection = new ObservableCollection<number>(1, 2, 3);
observableCollection.reduceRight((previous, current) => {
observableCollection.pop();
return previous + current;
});
})
.toThrow(new Error('Collection has changed while being iterated.'));
});

it('calling reduceRight while iterating will not break iterators', (): void => {
expect(
() => {
const observableCollection = new ObservableCollection<number>(1, 2, 3);

for (const _ of observableCollection)
observableCollection.reduceRight((previous, current) => previous + current);
})
.not
.toThrow();
});
});

0 comments on commit eb0b92a

Please sign in to comment.