Skip to content

Commit

Permalink
Remove reduceType in favour of mapToType, similar to Java Stream. Ren…
Browse files Browse the repository at this point in the history
…ame Relation to BinaryRelation, to reduce the chance of confusion with SObject relations.

Collection's `mapToDouble` and `mapToDecimal` return DoubleCollection and DecimalCollection, which offer further functions like `sum`, `average` and `filter`.
  • Loading branch information
ipavlic committed Feb 1, 2019
1 parent 042195f commit bd44aed
Show file tree
Hide file tree
Showing 35 changed files with 887 additions and 82 deletions.
51 changes: 39 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Collection remaining = mapped.remove(Match.record(new Account(Name = 'Bar')));
- [`pluck`](#pluck)
- [`mapAll`](#map-all)
- [`mapSome`](#map-some)
- [`reduce`](#reduce)
- [`mapToDecimal`](#map-to-decimal)
- [`mapToDouble`](#map-to-double)

### `filter`
<a name="filter"></a>
Expand Down Expand Up @@ -310,32 +311,58 @@ Collection.of(opps).mapSome(Match.field('Amount').gt(120), new DoubleAmount());
| `Collection` | `mapAll(SObjectToSObjectFunction fn)` | Returns a new `Collection` view formed by mapping current view elements that satisfy `predicate` with `fn`, and keeping those that do not satisfy `predicate` unchanged. |


### Reduce
<a name="reduce"></a>
### `mapToDecimal`
<a name="map-to-decimal"></a>

Reducing works through _reduceType_ functions on `Collection`, for the appropriate _type_. For example, `reduceDecimals` is used to reduce records to a `Decimal` value.
<img src="images/mapToDecimal.png" height="100">

Maps a numeric field to a `DecimalCollection`. This is similar to `pluckDecimals`, but unlike a raw `List<Decimal>` returns a `DecimalCollection` which provides further functions.

| Modifier and type | Method | Description |
|-------------------|--------|-------------|
| `DecimalCollection` | `mapToDecimal(Schema.SObjectField field)` | Plucks Decimal `field` values into a `DecimalCollection`. |
| `DecimalCollection` | `mapToDecimal(String relation)` | Plucks Decimal values at `relation` into a `DecimalCollection`. |

Functions on `DecimalCollection` include `sum` and `average`.

| Modifier and type | Method | Description |
|-------------------|--------|-------------|
| `Decimal` | `sum()` | Sums non-null Decimal values. Returns `null` if no such values exist in the collection. |
| `Decimal` | `average()` | Averages non-null Decimal values. Returns `null` if no such values exist in the collection. |
| `DecimalCollection` | `filter(ObjectPredicate predicate)` | Filters all values satisfying the `predicate` into a new `DecimalCollection` view. |
| `DecimalCollection` | `filter(DecimalPredicate predicate)` | Filters all values satisfying the `predicate` into a new `DecimalCollection` view. |


### `mapToDouble`
<a name="map-to-double"></a>

<img src="images/mapToDouble.png" height="100">

Maps a numeric field to a `DoubleCollection`. This is similar to `pluckDoubles`, but unlike a raw `List<Double>` returns a `DoubleCollection` which provides further functions.

| Modifier and type | Method | Description |
|-------------------|--------|-------------|
| `Decimal` | `reduceDecimals(SObjectDecimalToDecimalFunction fn)` | Returns a `Decimal` result of reducing the collection with function `fn`. |
| `DoubleCollection` | `mapToDouble(Schema.SObjectField field)` | Plucks Double `field` values into a `DoubleCollection`. |
| `DoubleCollection` | `mapToDouble(String relation)` | Plucks Double values at `relation` into a `DoubleCollection`. |

Functions on `DoubleCollection` include `sum` and `average`.

Lambda comes with sample reducers built in which can be accessed through the `Reducers` class. Custom reducers can be written as required.
| Modifier and type | Method | Description |
|-------------------|--------|-------------|
| `Double` | `sum()` | Sums non-null Double values. Returns `null` if no such values exist in the collection. |
| `Double` | `average()` | Averages non-null Double values. Returns `null` if no such values exist in the collection. |
| `DoubleCollection` | `filter(ObjectPredicate predicate)` | Filters all values satisfying the `predicate` into a new `DoubleCollection` view. |
| `DoubleCollection` | `filter(DoublePredicate predicate)` | Filters all values satisfying the `predicate` into a new `DoubleCollection` view. |

```apex
List<Opportunity> opps = new List<Opportunity>{
new Opportunity(Amount = 100),
new Opportunity(Amount = 150)
};
Decimal total = Collection.of(opps).reduceDecimals(Reducers.sumDecimals(Opportunity.Amount));
Double average = Collection.of(opps).mapToDouble(Opportunity.Amount).average();
```

| Modifier and type | Method | Description |
|-------------------|--------|-------------|
| `Decimal` | `reduceDecimals(SObjectDecimalToDecimalFunction fn)` | Returns a `Decimal` result of reducing the collection with function `fn`. |


## Important notes on the type system in Apex
<a name="type-system"></a>

Expand Down
Binary file removed images/map-all-transform.png
Binary file not shown.
Binary file removed images/map-some-transform.png
Binary file not shown.
257 changes: 257 additions & 0 deletions images/mapToDecimal.graphml

Large diffs are not rendered by default.

Binary file added images/mapToDecimal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
257 changes: 257 additions & 0 deletions images/mapToDouble.graphml

Large diffs are not rendered by default.

Binary file added images/mapToDouble.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
public enum Relation {
public enum BinaryRelation {
LESS_THAN, GREATER_THAN, EQUALS, NOT_EQUALS, LESS_THAN_OR_EQUALS, GREATER_THAN_OR_EQUALS, IS_IN, NOT_IN
}
File renamed without changes.
25 changes: 20 additions & 5 deletions src/classes/Collection.cls
Original file line number Diff line number Diff line change
Expand Up @@ -374,14 +374,29 @@ public with sharing class Collection {
return Collection.of(transformed);
}

public Decimal reduceDecimals(SObjectDecimalToDecimalFunction fn) {
return reduceDecimals(fn, 0);
public DecimalCollection mapToDecimal(Schema.SObjectField field) {
return mapToDecimal(field.getDescribe().getName());
}

public Decimal reduceDecimals(SObjectDecimalToDecimalFunction fn, Decimal accumulator) {
public DecimalCollection mapToDecimal(String relation) {
SObjectFieldReader reader = new SObjectFieldReader();
List<Decimal> decimals = new List<Decimal>();
for (SObject record : records) {
decimals.add((Decimal) reader.read(record, relation));
}
return new DecimalCollection(decimals);
}

public DoubleCollection mapToDouble(Schema.SObjectField field) {
return mapToDouble(field.getDescribe().getName());
}

public DoubleCollection mapToDouble(String relation) {
SObjectFieldReader reader = new SObjectFieldReader();
List<Double> doubles = new List<Double>();
for (SObject record : records) {
accumulator = fn.apply(record, accumulator);
doubles.add((Double) reader.read(record, relation));
}
return accumulator;
return new DoubleCollection(doubles);
}
}
20 changes: 16 additions & 4 deletions src/classes/CollectionTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -565,12 +565,24 @@ public class CollectionTest {
}

@IsTest
static void testReduce() {
Decimal sum = Collection.of(new List<Opportunity>{
static void testMapToDecimal() {
List<Decimal> amounts = Collection.of(new List<Opportunity>{
new Opportunity(Amount = 100),
new Opportunity(Amount = 150)
}).reduceDecimals(Reducers.sumDecimals(Opportunity.Amount));
}).mapToDecimal(Opportunity.Amount).asList();

System.assertEquals(250, sum);
System.assertEquals(100, amounts[0]);
System.assertEquals(150, amounts[1]);
}

@IsTest
static void testMapToDouble() {
List<Double> amounts = Collection.of(new List<Opportunity>{
new Opportunity(Amount = 100),
new Opportunity(Amount = 150)
}).mapToDouble(Opportunity.Amount).asList();

System.assertEquals(100, amounts[0]);
System.assertEquals(150, amounts[1]);
}
}
58 changes: 58 additions & 0 deletions src/classes/DecimalCollection.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
public class DecimalCollection {
private List<Decimal> decimals;
private List<Decimal> nonNulls;

public DecimalCollection(List<Decimal> decimals) {
this.decimals = decimals;
nonNulls = filter(ObjectPredicates.NotNull).asList();
}

public List<Decimal> asList() {
return new List<Decimal>(decimals);
}

public Set<Decimal> asSet() {
return new Set<Decimal>(decimals);
}

public Decimal sum() {
if (nonNulls.isEmpty()) {
return null;
}
Decimal sum = 0;
for (Decimal d : nonNulls) {
sum += d;
}
return sum;
}

public Decimal average(Integer scale) {
Decimal s = sum();
return s != null ? s.divide(nonNulls.size(), scale) : null;
}

public Decimal average(Integer scale, System.RoundingMode roundingMode) {
Decimal s = sum();
return s != null ? s.divide(nonNulls.size(), scale, roundingMode) : null;
}

public DecimalCollection filter(ObjectPredicate predicate) {
List<Decimal> filtered = new List<Decimal>();
for (Decimal d : decimals) {
if (predicate.apply(d)) {
filtered.add(d);
}
}
return new DecimalCollection(filtered);
}

public DecimalCollection filter(DecimalPredicate predicate) {
List<Decimal> filtered = new List<Decimal>();
for (Decimal d : decimals) {
if (predicate.apply(d)) {
filtered.add(d);
}
}
return new DecimalCollection(filtered);
}
}
File renamed without changes.
44 changes: 44 additions & 0 deletions src/classes/DecimalCollectionTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@IsTest
public class DecimalCollectionTest {
@IsTest
static void testDecimalCollectionSum() {
DecimalCollection c = new DecimalCollection(new List<Decimal>{100, 150});
System.assertEquals(250, c.sum());
}

@IsTest
static void testDecimalCollectionAverage() {
DecimalCollection c = new DecimalCollection(new List<Decimal>{100, 150});
System.assertEquals(125, c.average(0));
}

@IsTest
static void testDecimalCollectionAverageWithRoundingMode() {
DecimalCollection c = new DecimalCollection(new List<Decimal>{1, 2});
System.assertEquals(1, c.average(0, System.RoundingMode.DOWN));
}

@IsTest
static void testFilterWithObjectPredicate() {
DecimalCollection c = new DecimalCollection(new List<Decimal>{null, 100, null, 150, null});
List<Decimal> filtered = c.filter(ObjectPredicates.NotNull).asList();
System.assertEquals(2, filtered.size());
System.assertEquals(100, filtered[0]);
System.assertEquals(150, filtered[1]);
}

private class LowPassPredicate implements DecimalPredicate {
public Boolean apply(Decimal d) {
return d < 100;
}
}

@IsTest
static void testFilterWithDecimalPredicate() {
DecimalCollection c = new DecimalCollection(new List<Decimal>{50, 101, 500, 25, 300});
List<Decimal> filtered = c.filter(new LowPassPredicate()).asList();
System.assertEquals(2, filtered.size());
System.assertEquals(50, filtered[0]);
System.assertEquals(25, filtered[1]);
}
}
File renamed without changes.
3 changes: 3 additions & 0 deletions src/classes/DecimalPredicate.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public interface DecimalPredicate {
Boolean apply(Decimal d);
}
5 changes: 5 additions & 0 deletions src/classes/DecimalPredicate.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>44.0</apiVersion>
<status>Active</status>
</ApexClass>
58 changes: 58 additions & 0 deletions src/classes/DoubleCollection.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
public class DoubleCollection {
private List<Double> doubles;
private List<Double> nonNulls;

public DoubleCollection(List<Double> doubles) {
this.doubles = doubles;
nonNulls = filter(ObjectPredicates.NotNull).asList();
}

public List<Double> asList() {
return new List<Double>(doubles);
}

public Set<Double> asSet() {
return new Set<Double>(doubles);
}

public Double sum() {
if (nonNulls.isEmpty()) {
return null;
}
Double sum = 0;
for (Double d : nonNulls) {
sum += d;
}
return sum;
}

public Double average() {
Double s = sum();
return s != null ? s / nonNulls.size() : null;
}

public Double average(Integer scale, System.RoundingMode roundingMode) {
Double s = sum();
return s != null ? s / nonNulls.size() : null;
}

public DoubleCollection filter(ObjectPredicate predicate) {
List<Double> filtered = new List<Decimal>();
for (Double d : doubles) {
if (predicate.apply(d)) {
filtered.add(d);
}
}
return new DoubleCollection(filtered);
}

public DoubleCollection filter(DoublePredicate predicate) {
List<Double> filtered = new List<Decimal>();
for (Double d : doubles) {
if (predicate.apply(d)) {
filtered.add(d);
}
}
return new DoubleCollection(filtered);
}
}
5 changes: 5 additions & 0 deletions src/classes/DoubleCollection.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>44.0</apiVersion>
<status>Active</status>
</ApexClass>
38 changes: 38 additions & 0 deletions src/classes/DoubleCollectionTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@IsTest
public class DoubleCollectionTest {
@IsTest
static void testSum() {
DoubleCollection c = new DoubleCollection(new List<Double>{100, 150});
System.assertEquals(250, c.sum());
}

@IsTest
static void testAverage() {
DoubleCollection c = new DoubleCollection(new List<Double>{100, 150});
System.assertEquals(125, c.average());
}

@IsTest
static void testFilterWithObjectPredicate() {
DoubleCollection c = new DoubleCollection(new List<Double>{null, 100, null, 150, null});
List<Double> filtered = c.filter(ObjectPredicates.NotNull).asList();
System.assertEquals(2, filtered.size());
System.assertEquals(100, filtered[0]);
System.assertEquals(150, filtered[1]);
}

private class LowPassPredicate implements DoublePredicate {
public Boolean apply(Double d) {
return d < 100;
}
}

@IsTest
static void testFilterWithDoublePredicate() {
DoubleCollection c = new DoubleCollection(new List<Double>{50, 101, 500, 25, 300});
List<Double> filtered = c.filter(new LowPassPredicate()).asList();
System.assertEquals(2, filtered.size());
System.assertEquals(50, filtered[0]);
System.assertEquals(25, filtered[1]);
}
}
5 changes: 5 additions & 0 deletions src/classes/DoubleCollectionTest.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>44.0</apiVersion>
<status>Active</status>
</ApexClass>
3 changes: 3 additions & 0 deletions src/classes/DoublePredicate.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public interface DoublePredicate {
Boolean apply(Double d);
}
5 changes: 5 additions & 0 deletions src/classes/DoublePredicate.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>44.0</apiVersion>
<status>Active</status>
</ApexClass>
4 changes: 2 additions & 2 deletions src/classes/FieldMatchCondition.cls
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
public class FieldMatchCondition {

public String fieldPath { get; set;}
public Relation relation { get; set;}
public BinaryRelation relation { get; set;}
public Object value { get; set;}

public FieldMatchCondition(String fieldPath, Relation relation, Object value) {
public FieldMatchCondition(String fieldPath, BinaryRelation relation, Object value) {
this.fieldPath = fieldPath;
this.relation = relation;
this.value = value;
Expand Down
Loading

0 comments on commit bd44aed

Please sign in to comment.