Exercism - Strain
This post shows you how to get Strain exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Lists
Lists are ordered collections of items. You can iterate over lists, add elements, and create new lists.
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// Create empty list
List<int> result = [];
// Iterate and add elements
for (var num in numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
print(result); // [2, 4]
}
Functions as Parameters
Functions can be passed as parameters to other functions. These are called higher-order functions. A predicate is a function that returns a boolean.
void main() {
// Predicate function
bool isEven(int x) => x % 2 == 0;
// Function that takes a predicate
List<int> filter(List<int> list, bool Function(int) predicate) {
List<int> result = [];
for (var item in list) {
if (predicate(item)) {
result.add(item);
}
}
return result;
}
List<int> numbers = [1, 2, 3, 4, 5];
List<int> evens = filter(numbers, isEven);
print(evens); // [2, 4]
}
For-in Loops
For-in loops allow you to iterate through each element in a collection without needing to manage indices.
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// Iterate through each element
for (var num in numbers) {
print(num);
}
// With conditional
for (var num in numbers) {
if (num > 3) {
print('Large: $num');
}
}
}
Conditional Logic
You can use if statements to check conditions and decide whether to include elements in the result.
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
List<int> result = [];
// Keep even numbers
for (var num in numbers) {
if (num % 2 == 0) {
result.add(num);
}
}
print(result); // [2, 4]
}
Boolean Logic
Boolean values (true/false) are returned by predicates. You can use logical operators to combine or negate conditions.
void main() {
bool isEven(int x) => x % 2 == 0;
int num = 4;
bool result = isEven(num);
print(result); // true
// Negate the predicate
bool isOdd(int x) => !isEven(x);
print(isOdd(4)); // false
print(isOdd(5)); // true
}
Generics
Generics allow you to write code that works with different types. The <T> syntax creates a type parameter that can be replaced with any type.
void main() {
// Generic function that works with any type
List<T> filter<T>(List<T> list, bool Function(T) predicate) {
List<T> result = [];
for (var item in list) {
if (predicate(item)) {
result.add(item);
}
}
return result;
}
// Works with integers
List<int> numbers = [1, 2, 3, 4, 5];
List<int> evens = filter(numbers, (x) => x % 2 == 0);
// Works with strings
List<String> words = ["apple", "banana", "cherry"];
List<String> longWords = filter(words, (w) => w.length > 5);
}
List Comprehensions (Collection For)
List comprehensions (also called collection for) provide a concise way to create lists by iterating and conditionally including elements. The syntax [for (item in collection) if (condition) item] creates a new list in a single expression.
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// List comprehension: keep even numbers
List<int> evens = [for (final num in numbers) if (num % 2 == 0) num];
print(evens); // [2, 4]
// List comprehension: keep numbers greater than 3
List<int> large = [for (final num in numbers) if (num > 3) num];
print(large); // [4, 5]
// Works with any type
List<String> words = ["apple", "banana", "cherry"];
List<String> longWords = [for (final word in words) if (word.length > 5) word];
print(longWords); // [banana, cherry]
// Can transform elements too
List<int> doubled = [for (final num in numbers) num * 2];
print(doubled); // [2, 4, 6, 8, 10]
}
Introduction
Implement the keep and discard operation on collections. Given a collection and a predicate on the collection’s elements, keep returns a new collection containing those elements where the predicate is true, while discard returns a new collection containing those elements where the predicate is false.
For example, given the collection of numbers:
1, 2, 3, 4, 5
And the predicate:
is the number even?
Then your keep operation should produce:
2, 4
While your discard operation should produce:
1, 3, 5
Note that the union of keep and discard is all the elements.
The functions may be called keep and discard, or they may need different names in order to not clash with existing functions or concepts in your language.
Restrictions
Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead.
What is filtering?
Filtering is the process of selecting elements from a collection based on a condition (predicate). The
keepoperation selects elements where the predicate is true, whilediscardselects elements where the predicate is false.This is a fundamental operation in functional programming and data processing. Many languages provide built-in filter functions, but implementing it manually helps understand the underlying logic.
— Functional Programming
How can we implement keep and discard?
To implement keep and discard manually:
- Keep: Iterate through the collection, check each element with the predicate, and include elements where the predicate returns
truein the result - Discard: Iterate through the collection, check each element with the predicate, and include elements where the predicate returns
falsein the result
The key insight is that discard is the opposite of keep - it keeps elements where the predicate is false.
We can use Dart’s list comprehensions (collection for) to create the filtered lists in a concise way. The syntax [for (value in collection) if (condition) value] allows us to build a new list by iterating and conditionally including elements.
For example, with [1, 2, 3, 4, 5] and predicate isEven:
- Keep:
[for (final num in numbers) if (isEven(num)) num]→[2, 4] - Discard:
[for (final num in numbers) if (!isEven(num)) num]→[1, 3, 5]
Solution
class Strain {
List<T> keep<T>(List<T> values, bool Function(T) predicate) =>
[for (final value in values) if (predicate(value)) value];
List<T> discard<T>(List<T> values, bool Function(T) predicate) =>
[for (final value in values) if (!predicate(value)) value];
}
Let’s break down the solution:
-
keep<T>- Keeps elements where the predicate is true:List<T> values- The input collection (generic type T works with any type)bool Function(T) predicate- A function that takes an element and returns a boolean- Uses a list comprehension:
[for (final value in values) if (predicate(value)) value] - Iterates through each value in the collection
- If
predicate(value)returnstrue, includes the value in the result list - Returns the filtered list in a single, concise expression
-
discard<T>- Discards elements where the predicate is true (keeps where it’s false):- Same structure as
keep - Uses
!predicate(value)to negate the predicate - Includes items where the predicate returns
false - Returns elements that don’t match the predicate
- Same structure as
The solution uses Dart’s list comprehensions (collection for) to manually implement filtering in a concise, functional style. This approach:
- Creates a new list in a single expression
- Iterates through the collection and conditionally includes elements
- Works with any type thanks to generics
- Avoids using Dart’s built-in
filtermethod as required by the exercise
A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉
Visit My YouTube Channel