Skip to content

Commit 77b0de6

Browse files
lipchevangularsen
authored andcommitted
UnitMath extension methods (Min, Max, Sum, Average) (#692)
* UnitMath extension methods (Min, Max, Sum, Average) Added a set of extension methods for some of the most common Math operations, such as Min, Max, Sum and Average (with selector overloads)
1 parent d85c011 commit 77b0de6

File tree

2 files changed

+398
-0
lines changed

2 files changed

+398
-0
lines changed

UnitsNet.Tests/UnitMathTests.cs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using UnitsNet.Units;
4+
using Xunit;
5+
6+
namespace UnitsNet.Tests
7+
{
8+
public class UnitMathTests
9+
{
10+
[Fact]
11+
public void AverageOfDifferentUnitsThrowsException()
12+
{
13+
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
14+
15+
Assert.Throws<ArgumentException>(() => units.Average(LengthUnit.Centimeter));
16+
}
17+
18+
[Fact]
19+
public void AverageOfEmptySourceThrowsException()
20+
{
21+
var units = new Length[] { };
22+
23+
Assert.Throws<InvalidOperationException>(() => units.Average(LengthUnit.Centimeter));
24+
}
25+
26+
[Fact]
27+
public void AverageOfLengthsCalculatesCorrectly()
28+
{
29+
var units = new[] {Length.FromMeters(1), Length.FromCentimeters(50)};
30+
31+
Length average = units.Average(LengthUnit.Centimeter);
32+
33+
Assert.Equal(75, average.Value);
34+
Assert.Equal(LengthUnit.Centimeter, average.Unit);
35+
}
36+
37+
[Fact]
38+
public void AverageOfLengthsWithNullSelectorThrowsException()
39+
{
40+
var units = new[]
41+
{
42+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
43+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
44+
};
45+
46+
Assert.Throws<ArgumentNullException>(() => units.Average((Func<KeyValuePair<string, Length>, Length>) null, LengthUnit.Centimeter));
47+
}
48+
49+
[Fact]
50+
public void AverageOfLengthsWithSelectorCalculatesCorrectly()
51+
{
52+
var units = new[]
53+
{
54+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
55+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
56+
};
57+
58+
Length average = units.Average(x => x.Value, LengthUnit.Centimeter);
59+
60+
Assert.Equal(75, average.Value);
61+
Assert.Equal(LengthUnit.Centimeter, average.Unit);
62+
}
63+
64+
[Fact]
65+
public void MaxOfDifferentUnitsThrowsException()
66+
{
67+
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
68+
69+
Assert.Throws<ArgumentException>(() => units.Max(LengthUnit.Centimeter));
70+
}
71+
72+
[Fact]
73+
public void MaxOfEmptySourceThrowsException()
74+
{
75+
var units = new Length[] { };
76+
77+
Assert.Throws<InvalidOperationException>(() => units.Max(LengthUnit.Centimeter));
78+
}
79+
80+
[Fact]
81+
public void MaxOfLengthsCalculatesCorrectly()
82+
{
83+
var units = new[] {Length.FromMeters(1), Length.FromCentimeters(50)};
84+
85+
Length max = units.Max(LengthUnit.Centimeter);
86+
87+
Assert.Equal(100, max.Value);
88+
Assert.Equal(LengthUnit.Centimeter, max.Unit);
89+
}
90+
91+
[Fact]
92+
public void MaxOfLengthsWithNullSelectorThrowsException()
93+
{
94+
var units = new[]
95+
{
96+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
97+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
98+
};
99+
100+
Assert.Throws<ArgumentNullException>(() => units.Max((Func<KeyValuePair<string, Length>, Length>) null, LengthUnit.Centimeter));
101+
}
102+
103+
[Fact]
104+
public void MaxOfLengthsWithSelectorCalculatesCorrectly()
105+
{
106+
var units = new[]
107+
{
108+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
109+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
110+
};
111+
112+
Length max = units.Max(x => x.Value, LengthUnit.Centimeter);
113+
114+
Assert.Equal(100, max.Value);
115+
Assert.Equal(LengthUnit.Centimeter, max.Unit);
116+
}
117+
118+
[Fact]
119+
public void MinOfDifferentUnitsThrowsException()
120+
{
121+
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
122+
123+
Assert.Throws<ArgumentException>(() => units.Min(LengthUnit.Centimeter));
124+
}
125+
126+
[Fact]
127+
public void MinOfEmptySourceThrowsException()
128+
{
129+
var units = new Length[] { };
130+
131+
Assert.Throws<InvalidOperationException>(() => units.Min(LengthUnit.Centimeter));
132+
}
133+
134+
[Fact]
135+
public void MinOfLengthsCalculatesCorrectly()
136+
{
137+
var units = new[] {Length.FromMeters(1), Length.FromCentimeters(50)};
138+
139+
Length min = units.Min(LengthUnit.Centimeter);
140+
141+
Assert.Equal(50, min.Value);
142+
Assert.Equal(LengthUnit.Centimeter, min.Unit);
143+
}
144+
145+
[Fact]
146+
public void MinOfLengthsWithNullSelectorThrowsException()
147+
{
148+
var units = new[]
149+
{
150+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
151+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
152+
};
153+
154+
Assert.Throws<ArgumentNullException>(() => units.Min((Func<KeyValuePair<string, Length>, Length>) null, LengthUnit.Centimeter));
155+
}
156+
157+
[Fact]
158+
public void MinOfLengthsWithSelectorCalculatesCorrectly()
159+
{
160+
var units = new[]
161+
{
162+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
163+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
164+
};
165+
166+
Length min = units.Min(x => x.Value, LengthUnit.Centimeter);
167+
168+
Assert.Equal(50, min.Value);
169+
Assert.Equal(LengthUnit.Centimeter, min.Unit);
170+
}
171+
172+
[Fact]
173+
public void SumOfDifferentUnitsThrowsException()
174+
{
175+
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
176+
177+
Assert.Throws<ArgumentException>(() => units.Sum(LengthUnit.Centimeter));
178+
}
179+
180+
[Fact]
181+
public void SumOfEmptySourceReturnsZero()
182+
{
183+
var units = new Length[] { };
184+
185+
Length sum = units.Sum(Length.BaseUnit);
186+
187+
Assert.Equal(Length.Zero, sum);
188+
}
189+
190+
[Fact]
191+
public void SumOfLengthsCalculatesCorrectly()
192+
{
193+
var units = new[] {Length.FromMeters(1), Length.FromCentimeters(50)};
194+
195+
Length sum = units.Sum(LengthUnit.Centimeter);
196+
197+
Assert.Equal(150, sum.Value);
198+
Assert.Equal(LengthUnit.Centimeter, sum.Unit);
199+
}
200+
201+
[Fact]
202+
public void SumOfLengthsWithNullSelectorThrowsException()
203+
{
204+
var units = new[]
205+
{
206+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
207+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
208+
};
209+
210+
Assert.Throws<ArgumentNullException>(() => units.Sum((Func<KeyValuePair<string, Length>, Length>) null, LengthUnit.Centimeter));
211+
}
212+
213+
[Fact]
214+
public void SumOfLengthsWithSelectorCalculatesCorrectly()
215+
{
216+
var units = new[]
217+
{
218+
new KeyValuePair<string, Length>("1", Length.FromMeters(1)),
219+
new KeyValuePair<string, Length>("2", Length.FromCentimeters(50))
220+
};
221+
222+
Length sum = units.Sum(x => x.Value, LengthUnit.Centimeter);
223+
224+
Assert.Equal(150, sum.Value);
225+
Assert.Equal(LengthUnit.Centimeter, sum.Unit);
226+
}
227+
}
228+
}

UnitsNet/UnitMath.cs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace UnitsNet
6+
{
7+
/// <summary>
8+
/// A set of extension methods for some of the most common Math operations, such as Min, Max, Sum and Average
9+
/// </summary>
10+
public static class UnitMath
11+
{
12+
/// <summary>Computes the sum of a sequence of <typeparamref name="TQuantity" /> values.</summary>
13+
/// <param name="source">A sequence of <typeparamref name="TQuantity" /> values to calculate the sum of.</param>
14+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
15+
/// <returns>The sum of the values in the sequence, represented in the specified unit type.</returns>
16+
/// <exception cref="T:System.ArgumentNullException">
17+
/// <paramref name="source">source</paramref> is null.
18+
/// </exception>
19+
/// <exception cref="ArgumentException">
20+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
21+
/// </exception>
22+
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
23+
where TQuantity : IQuantity
24+
{
25+
return (TQuantity) Quantity.From(source.Sum(x => x.As(unitType)), unitType);
26+
}
27+
28+
/// <summary>
29+
/// Computes the sum of the sequence of <typeparamref name="TQuantity" /> values that are obtained by invoking a
30+
/// transform function on each element of the input sequence.
31+
/// </summary>
32+
/// <param name="source">A sequence of values that are used to calculate a sum.</param>
33+
/// <param name="selector">A transform function to apply to each element.</param>
34+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
35+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
36+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
37+
/// <returns>The sum of the projected values, represented in the specified unit type.</returns>
38+
/// <exception cref="T:System.ArgumentNullException">
39+
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
40+
/// </exception>
41+
/// <exception cref="ArgumentException">
42+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
43+
/// </exception>
44+
public static TQuantity Sum<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
45+
where TQuantity : IQuantity
46+
{
47+
return source.Select(selector).Sum(unitType);
48+
}
49+
50+
/// <summary>Computes the min of a sequence of <typeparamref name="TQuantity" /> values.</summary>
51+
/// <param name="source">A sequence of <typeparamref name="TQuantity" /> values to calculate the min of.</param>
52+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
53+
/// <returns>The min of the values in the sequence, represented in the specified unit type.</returns>
54+
/// <exception cref="T:System.ArgumentNullException">
55+
/// <paramref name="source">source</paramref> is null.
56+
/// </exception>
57+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
58+
/// <exception cref="ArgumentException">
59+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
60+
/// </exception>
61+
public static TQuantity Min<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
62+
where TQuantity : IQuantity
63+
{
64+
return (TQuantity) Quantity.From(source.Min(x => x.As(unitType)), unitType);
65+
}
66+
67+
/// <summary>
68+
/// Computes the min of the sequence of <typeparamref name="TQuantity" /> values that are obtained by invoking a
69+
/// transform function on each element of the input sequence.
70+
/// </summary>
71+
/// <param name="source">A sequence of values that are used to calculate a min.</param>
72+
/// <param name="selector">A transform function to apply to each element.</param>
73+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
74+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
75+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
76+
/// <returns>The min of the projected values, represented in the specified unit type.</returns>
77+
/// <exception cref="T:System.ArgumentNullException">
78+
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
79+
/// </exception>
80+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
81+
/// <exception cref="ArgumentException">
82+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
83+
/// </exception>
84+
public static TQuantity Min<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
85+
where TQuantity : IQuantity
86+
{
87+
return source.Select(selector).Min(unitType);
88+
}
89+
90+
/// <summary>Computes the max of a sequence of <typeparamref name="TQuantity" /> values.</summary>
91+
/// <param name="source">A sequence of <typeparamref name="TQuantity" /> values to calculate the max of.</param>
92+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
93+
/// <returns>The max of the values in the sequence, represented in the specified unit type.</returns>
94+
/// <exception cref="T:System.ArgumentNullException">
95+
/// <paramref name="source">source</paramref> is null.
96+
/// </exception>
97+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
98+
/// <exception cref="ArgumentException">
99+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
100+
/// </exception>
101+
public static TQuantity Max<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
102+
where TQuantity : IQuantity
103+
{
104+
return (TQuantity) Quantity.From(source.Max(x => x.As(unitType)), unitType);
105+
}
106+
107+
/// <summary>
108+
/// Computes the max of the sequence of <typeparamref name="TQuantity" /> values that are obtained by invoking a
109+
/// transform function on each element of the input sequence.
110+
/// </summary>
111+
/// <param name="source">A sequence of values that are used to calculate a max.</param>
112+
/// <param name="selector">A transform function to apply to each element.</param>
113+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
114+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
115+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
116+
/// <returns>The max of the projected values, represented in the specified unit type.</returns>
117+
/// <exception cref="T:System.ArgumentNullException">
118+
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
119+
/// </exception>
120+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
121+
/// <exception cref="ArgumentException">
122+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
123+
/// </exception>
124+
public static TQuantity Max<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
125+
where TQuantity : IQuantity
126+
{
127+
return source.Select(selector).Max(unitType);
128+
}
129+
130+
/// <summary>Computes the average of a sequence of <typeparamref name="TQuantity" /> values.</summary>
131+
/// <param name="source">A sequence of <typeparamref name="TQuantity" /> values to calculate the average of.</param>
132+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
133+
/// <returns>The average of the values in the sequence, represented in the specified unit type.</returns>
134+
/// <exception cref="T:System.ArgumentNullException">
135+
/// <paramref name="source">source</paramref> is null.
136+
/// </exception>
137+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
138+
/// <exception cref="ArgumentException">
139+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
140+
/// </exception>
141+
public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
142+
where TQuantity : IQuantity
143+
{
144+
return (TQuantity) Quantity.From(source.Average(x => x.As(unitType)), unitType);
145+
}
146+
147+
/// <summary>
148+
/// Computes the average of the sequence of <typeparamref name="TQuantity" /> values that are obtained by invoking
149+
/// a transform function on each element of the input sequence.
150+
/// </summary>
151+
/// <param name="source">A sequence of values that are used to calculate an average.</param>
152+
/// <param name="selector">A transform function to apply to each element.</param>
153+
/// <param name="unitType">The desired unit type for the resulting quantity</param>
154+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
155+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
156+
/// <returns>The average of the projected values, represented in the specified unit type.</returns>
157+
/// <exception cref="T:System.ArgumentNullException">
158+
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
159+
/// </exception>
160+
/// <exception cref="T:System.InvalidOperationException"><paramref name="source">source</paramref> contains no elements.</exception>
161+
/// <exception cref="ArgumentException">
162+
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
163+
/// </exception>
164+
public static TQuantity Average<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
165+
where TQuantity : IQuantity
166+
{
167+
return source.Select(selector).Average(unitType);
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)