Skip to content
92 changes: 77 additions & 15 deletions __tests__/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,22 +561,39 @@ test('LastOrDefault', t => {
t.is(new List<string>().LastOrDefault('default'), 'default')
})


test('Max', t => {
const people = new List<IPerson>([
{ Age: 50, Name: 'Bob' },
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
{ Age: 25, Name: 'Alice' }

])
t.is(
people.Max(x => x.Age ?? 0),
people.Last()
50
)
t.is(
new List<number>([1, 2, 3, 4, 5]).Max(),
5
new List<number>([5, 4, 3, 2, 1]).Min(),
1
)
})

test('Max_invalid_function_provided', t => {
const people = new List<IPerson>([
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
])

// Provide an invalid selector (wrong type) to trigger the error
let invalidFn = () => 0;

t.throws(() => people.Max(invalidFn), {
message: /InvalidOperationException: Invalid comparer or selector function provided./
})
})

test('Max_undefinedComparer', t => {
const people = new List<IPerson>([
{ Age: 15, Name: 'Cathy' },
Expand All @@ -598,10 +615,25 @@ test('Max_emptyElements', t => {
)
})

test('Max_comparer', t => {
const people = new List<IPerson>([
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
])

let comparer = ((a: IPerson, b: IPerson) => (a.Age ?? 0) - (b.Age ?? 0));

t.is(
people.Max(comparer),
people.Last()
);
})

test('Max_number', t => {
const nums = new List<number>([
10,
5,
10,
-5
])
t.is(
Expand Down Expand Up @@ -635,22 +667,38 @@ test('Max_boolean', t => {
)
})


test('Min', t => {
const people = new List<IPerson>([
{ Age: 50, Name: 'Bob' },
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
{ Age: 25, Name: 'Alice' }

])
t.is(
people.Min(x => x.Age ?? 0),
people.First()
15
)
t.is(
new List<number>([1, 2, 3, 4, 5]).Min(),
new List<number>([5, 4, 3, 2, 1]).Min(),
1
)
})

test('Min_invalid_function_provided', t => {
const people = new List<IPerson>([
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
])

// Provide an invalid selector (wrong type) to trigger the error
let invalidFn = () => 0;

t.throws(() => people.Min(invalidFn), {
message: /InvalidOperationException: Invalid comparer or selector function provided./
})
})

test('Min_undefinedComparer', t => {
const people = new List<IPerson>([
Expand All @@ -664,6 +712,21 @@ test('Min_undefinedComparer', t => {
})
})

test('Min_comparer', t => {
const people = new List<IPerson>([
{ Age: 15, Name: 'Cathy' },
{ Age: 25, Name: 'Alice' },
{ Age: 50, Name: 'Bob' }
])

let comparer = ((a: IPerson, b: IPerson) => (a.Age ?? 0) - (b.Age ?? 0));

t.is(
people.Min(comparer),
people.First()
);
})

test('Min_emptyElements', t => {
const people = new List<IPerson>([])

Expand Down Expand Up @@ -710,7 +773,6 @@ test('Min_boolean', t => {
)
})


test('OfType', t => {
const pets = new List<Pet>([
new Dog({ Age: 8, Name: 'Barley', Vaccinated: true }),
Expand Down Expand Up @@ -1177,12 +1239,12 @@ test('ToDictionary', t => {
// t.is(dictionary2['Alice'], 25)
// Dictionary should behave just like in C#
t.is(
dictionary.Max(x => x?.Value?.Age ?? 0)?.Value,
people.Last()
dictionary.Max(x => x?.Value?.Age ?? 0),
50
)
t.is(
dictionary.Min(x => x?.Value?.Age ?? 0)?.Value,
people.First()
dictionary.Min(x => x?.Value?.Age ?? 0),
15
)
const expectedKeys = new List(['Cathy', 'Alice', 'Bob'])
t.deepEqual(
Expand Down
140 changes: 119 additions & 21 deletions src/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,46 +330,144 @@ class List<T> {
}

/**
* Returns the maximum value in a generic sequence.
* Gets the maximum value in a generic sequence.
* @returns The maximum value in the sequence, or undefined if the sequence is empty.
*/
public Max(): T | undefined;

/**
* Gets the maximum value in a generic sequence.
* @returns The maximum value in the sequence, or undefined if the sequence is empty.
* @param selector - A function to select a value from each element for comparison.
*/
public Max<R>(selector: (e: T) => R): R | undefined;

/**
* Gets the maximum value in a generic sequence.
* @returns The maximum value in the sequence, or undefined if the sequence is empty.
* @param comparer - A custom comparison function
*/
public Max(comparer?: (element: T) => number): T | undefined {
public Max(comparer: (a: T, b: T) => number): T | undefined;

/**
* Gets the maximum value in a generic sequence.
* @returns The maximum value in the sequence, or undefined if the sequence is empty.
* @param comparerOrSelector - An optional custom comparison function or a selector function to select a value from each element for comparison.
*/
public Max<R>(
comparerOrSelector?: ((a: T, b: T) => number) | ((e: T) => R)
): T | R | undefined {
if (this._elements.length === 0) return undefined;

let maxElem = this._elements[0];
let comparerToUse = comparer || List.getComparer<T>(maxElem);
if (!comparerOrSelector) {
// Max(): T | undefined;
return this.getMaxElement<T>(this._elements);

}

const fn = comparerOrSelector as Function;

if (!fn || fn.length > 2 || fn.length === 0) {
throw new Error('InvalidOperationException: Invalid comparer or selector function provided.')
}

if (fn.length === 1) {
// Max<R>(selector: (e: T) => R): R | undefined;
return this.getMaxElement<R>(this._elements.map(fn as (e: T) => R));
}

//fn.length === 2
// Max(comparer: (a: T, b: T) => number): T | undefined;
return this.getMaxElement<T>(this._elements, fn as (a: T, b: T) => number);
}

/**
* Returns the maximum value in a generic sequence.
* @param elements - The array of elements to find the maximum from.
* @param customComparer - An optional custom comparison function.
*/
private getMaxElement<R>(elements: R[], customComparer?: ((a: R, b: R) => number)) {
const comparerToUse = customComparer || List.getComparer<R>(elements[0]);

if (!comparerToUse) {
throw new Error('InvalidOperationException: No comparer available.')
}

this._elements.forEach(elem => {
if (comparerToUse(elem, maxElem) > 0) {
maxElem = elem;
}
})

return maxElem;
return elements.reduce(
(currentMax, elem) =>
comparerToUse(elem, currentMax) > 0 ? elem : currentMax,
elements[0]
);
}

/**
* Returns the minimum value in a generic sequence.
* Gets the minimum value in a generic sequence.
* @returns The minimum value in the sequence, or undefined if the sequence is empty.
*/
public Min(): T | undefined;

/**
* Gets the minimum value in a generic sequence.
* @returns The minimum value in the sequence, or undefined if the sequence is empty.
* @param selector - A function to select a value from each element for comparison.
*/
public Min<R>(selector: (e: T) => R): R | undefined;

/**
* Gets the minimum value in a generic sequence.
* @returns The minimum value in the sequence, or undefined if the sequence is empty.
* @param comparer - A custom comparison function
*/
public Min(comparer: (a: T, b: T) => number): T | undefined;

/**
* Gets the minimum value in a generic sequence.
* @returns The minimum value in the sequence, or undefined if the sequence is empty.
* @param comparerOrSelector - An optional custom comparison function or a selector function to select a value from each element for comparison.
*/
public Min(comparer?: (element: T) => number): T | undefined {
public Min<R>(
comparerOrSelector?: ((a: T, b: T) => number) | ((e: T) => R)
): T | R | undefined {
if (this._elements.length === 0) return undefined;

let minElem = this._elements[0];
let comparerToUse = comparer || List.getComparer<T>(minElem);
if (!comparerOrSelector) {
// Min(): T | undefined;
return this.getMinElement<T>(this._elements);
}

const fn = comparerOrSelector as Function;

if (!fn || fn.length > 2 || fn.length === 0) {
throw new Error('InvalidOperationException: Invalid comparer or selector function provided.')
}

if (fn.length === 1) {
// Min<R>(selector: (e: T) => R): R | undefined;
return this.getMinElement<R>(this._elements.map(fn as (e: T) => R));
}

//fn.length === 2
// Min(comparer: (a: T, b: T) => number): T | undefined;
return this.getMinElement<T>(this._elements, fn as (a: T, b: T) => number);
}

/**
* Returns the minimum value in a generic sequence.
* @param elements - The array of elements to find the minimum from.
* @param customComparer - An optional custom comparison function.
*/
private getMinElement<R>(elements: R[], customComparer?: ((a: R, b: R) => number)) {
const comparerToUse = customComparer || List.getComparer<R>(elements[0]);

if (!comparerToUse) {
throw new Error('InvalidOperationException: No comparer available.')
}

this._elements.forEach(elem => {
if (comparerToUse(elem, minElem) < 0) {
minElem = elem;
}
})
return minElem;
return elements.reduce(
(currentMin, elem) =>
comparerToUse(elem, currentMin) < 0 ? elem : currentMin,
elements[0]
);
}

/**
Expand Down