No For Loops
"In computer science, a for-loop (or simply for loop) is a control flow statement for specifying iteration, which allows code to be executed repeatedly." - Wikipedia
Why
There are a lot of advantages of using specific methods over generic for
or foreach
version.
- It’s easy to write and others can interpret it easily
- It’s easy to maintain, extend, and test
- You can write pure functions without any side effects
- Helps you think in terms of functional programming
Problems
- How may I reduce code complexity?
- How may I write more understandable/readable code?
How to
Let's take an example, imagine we want to identify which Post
contained in Blog
contains a given Tag
Nested for-loops
private IEnumerable<Post> FilterByTag(string searchedTag)
{
var result = new List<Post>();
for (int i = 0; i <= _blogs.Count - 1; i++)
{
var currentBlog = _blogs[i];
for (int j = 0; j <= currentBlog.Posts.Length - 1; j++)
{
var currentPost = currentBlog.Posts[j];
if (currentPost.Tags.Contains(searchedTag))
{
result.Add(currentPost);
}
}
}
return result;
}
In kotlin
private fun filterByTagWithFor(searchedTag: String): List<Post> {
val result = ArrayList<Post>()
for (i in 0 until blogs.size) {
val currentBlog: Blog = blogs[i]
for (j in 0 until currentBlog.posts.size) {
val currentPost = currentBlog.posts[j]
if (currentPost.tags.contains(searchedTag)) {
result.add(currentPost)
}
}
}
return result
}
What can be improved here?
- A lot of variables:
searchedTag
,i
,currentBlog
,j
,currentPost
,result
- Mutation everywhere
Foreach
Using foreach
makes our code much easier to understand and reduce the number of variables and mutations
private IEnumerable<Post> FilterByTag(string searchedTag)
{
var result = new List<Post>();
foreach (var currentBlog in _blogs)
{
foreach (var currentPost in currentBlog.Posts)
{
if (currentPost.Tags.Contains(searchedTag))
{
result.Add(currentPost);
}
}
}
return result;
}
In kotlin
private fun filterByTag(searchedTag: String): List<Post> {
val result = ArrayList<Post>()
for (currentBlog in blogs) {
for (currentPost in currentBlog.posts) {
if (currentPost.tags.contains(searchedTag)) {
result.add(currentPost)
}
}
}
return result
}
Yield
Using foreach
with yield
keyword allows us to accumulate without having to instantiate a new List
by ourselves
private IEnumerable<Post> FilterByTag(string searchedTag)
{
foreach (var currentBlog in _blogs)
{
foreach (var currentPost in currentBlog.Posts)
{
if (currentPost.Tags.Contains(searchedTag))
{
yield return currentPost;
}
}
}
}
Recursion
We could use recursion as well
private IEnumerable<Post> FilterByTag(string searchedTag)
=> FilterRecursively(
new List<Post>(),
_blogs.SelectMany(_ => _.Posts).ToImmutableList(),
0,
(post) => post.Tags.Contains(searchedTag));
private static List<T> FilterRecursively<T>(
List<T> result,
ImmutableList<T> initialList,
int index,
Func<T, bool> filterFunction)
{
if (index == initialList.Count)
return result;
var item = initialList[index];
if (filterFunction(item))
result.Add(item);
return FilterRecursively(result, initialList, index + 1, filterFunction);
}
In kotlin
private fun filterByTag(searchedTag: String): List<Post> =
filter(
mutableListOf(),
blogs.flatMap { it.posts },
0
) { it.tags.contains(searchedTag) }
private tailrec fun <T> filter(
result: MutableList<T>,
initialList: List<T>,
index: Int,
filter: (item: T) -> Boolean
): List<T> {
if (index == initialList.size)
return result
if (filter(initialList[index]))
result.add(initialList[index])
return filter(result, initialList, index + 1, filter)
}
Using List pure functions
Using Linq
makes our code evern simpler to read and identify the intent
private IEnumerable<Post> FilterByTag(string searchedTag)
=> _blogs
.SelectMany(blog => blog.Posts)
.Where(post => post.Tags.Contains(searchedTag));
Alternatively, you can use the other Linq
syntax
private IEnumerable<Post> FilterByTag(string searchedTag)
=> from currentBlog in _blogs
from currentPost in currentBlog.Posts
where currentPost.Tags.Contains(searchedTag)
select currentPost;
In kotlin
private fun filterByTag(searchedTag: String): List<Post> =
blogs.flatMap { it.posts }
.filter { it.tags.contains(searchedTag) }
In this example, we should also encapsulate some logic inside
Blog
andPost
classes instead of envying data from them in theFilter
method Let's keep this discussion for another flavour 😉
Constraint
Replace a for-loop
by an alternative described here or another.