loading...
Cover image for Look ma', without loops!

Look ma', without loops!

chrisvasqm profile image Christian Vasquez ・5 min read
Cover image by @jordaneil from Unsplash

Before you read

I would highly recommend you, Mr/Mrs Reader, to be familiar with the following concepts in order to fully understand this article:

  1. Functions
  2. Classes
  3. Objects
  4. Properties/Fields/Member Variables
  5. Loops(for, while, for-each)

During one of my first few programming classes in college, I was introduced to an exercise/challenge by our teacher that said:

"Find the the amount of people that are above and below the average out of all the students's scores in a subject that are stored inside of an array."

He gave us clues as to how to start since he recently showed us about variables, functions and loops.

And the first thing that came to my mind was:

"We can use a few for-each loops here and there and then... WAIT! No. Let's do this without for/while loops!"

And as any Modern Developer Wannabe who thinks the only thing that exists is the JVM, I tried to do it in Kotlin πŸ€“

(What? did you think I was gonna use Java? Excuse you!)

The Plan

Like any millennial, I always go to the grocery store armed with a shopping list so I don't forget anything. So, let's plan out what we'll need to do for this exercise:

  1. We will need a way to represent a Student in our code. Let's keep it simple and declare a class with two properties: name and score.
  2. Later on, we will need to store each one of them inside of an array that we will operate on.
  3. And we will need to calculate the average of all of them.
  4. Then we can use this value to compare it against each and every Student in our array to count how many of them are above and below the average.

The Execution

Making the Student class

Why? Because we can! (It can actually make our code easier to read once you finally understand what a class is)

Now, remember: we need to have two properties: name and score. Which will be their data types?

Yeah, you are totally right! name will be a String and score will be Int.

Here's how that code would look like:

class Student(val name: String, val score: Int)

Beautiful!

For those that are not familiar with the val keyword, it means that this property is read only, so it's value can only be read, not modified by anyone after it has been set.

Making the array of students

Over on this step I'll be using one of Kotlin's "voodoo magic" to make our Array<Student happen, here's how it looks:

fun main(args: Array<String>) {
    val students = arrayOf(
            Student("Chris", 80),
            Student("Mark", 67),
            Student("Joseph", 98),
            Student("Carl", 74),
            Student("Katherine", 86)
    )
}

Ulala!

Here we made use of the arrayOf() function provided by the Standard Library which serves as a piece of syntactic sugar at which we pass in a bunch of Student objects that we will work with (Feel free to alter any of the names or scores if you like).

Notice that we don't have to specify a data type for our Array<T> because the compiler can infer it from each argument we pass in to this function.

Finding the average

This is where the fun starts, since we don't want to use any for/while/for-each loop, we will rely some Higher-Order functions.

Introducing... sumBy {...}

This function takes in a expression (or Lambda Expression to be more specific) which will then be used to calculate a single value, based on that expression.

Yeah, yeah, yeah... I know. It's sounds really boring.

Let's take a look at it:

students.sumBy { it.score }

What we are doing here is iterating over each individual Student (which we are referring as "it") inside our students array, then taking only their scores and then adding them together to get one single value.

So, we can use this in order to calculate the average.

In order to do so, we will need to not only add all the scores together, but also divide it by the amount of students.

We can get that value by simply calling students.size, which in this case would give us 5.

If we put it all together, we would have something like:


fun calculateAverageScore(students: Array<Student>) = students.sumBy { it.score } / students.size

Here I made use of a function in order to add more meaning to the operation we will be doing.

In case you might be wondering, we are allowed to remove the curly braces ({, }) and the return keyword if our function is only one line long.

Now we can use our trusty calculateAverageScore() function to help us out like this:

fun main(args: Array<String>) {
    val students = arrayOf(
            Student("Chris", 80),
            Student("Mark", 67),
            Student("Joseph", 98),
            Student("Carl", 74),
            Student("Katherine", 86)
    )

    val average = calculateAverageScore(students)
}

private fun calculateAverageScore(students: Array<Student>) = students.sumBy { it.score } / students.size

Awesome!

How many are above average?

In order to do this, we can use the filter {...} function.

Similar to the sumBy {...}, this function takes in what some people call a predicate, which is an expression that returns a Boolean (true or false) value.

In our case it would look like this:

val aboveAverage = students.filter { it.score > average }

But there's something in particular about filter, it will not return a single value. It will actually return a new array of Students whose scores are above average. But we just need to count how many there are.

Remember how we used the students.size property?

Oh yeeeeeaaaah!

We can also use it after the filter block:

val aboveAverage = students.filter { it.score > average }.size

_Fantastic!

How many are below average?

I think you might already know how to this one... As any good Software Developer, we can trust our natural instincts and just copy/paste the hell out of our previous code:

val belowAverage = students.filter { it.score < average }.size

What did we have to change?

  • aboveAvereage to belowAverage.
  • The > operator for the < operator.

And that's it!

The Final Test

Now we just need to give a try. But! we haven't done anything in order to display the results yet...

Let's fix that by adding this at the end:

println("With an average of $average, there are $aboveAverage students above average and $belowAverage below it.")

Great!

If you followed each step closely, you should have something like this:

fun main(args: Array<String>) {
    val students = arrayOf(
            Student("Chris", 80),
            Student("Mark", 67),
            Student("Joseph", 98),
            Student("Carl", 74),
            Student("Katherine", 86)
    )

    val average = calculateAverageScore(students)

    val aboveAverage = students.filter { it.score > average }.size

    val belowAverage = students.filter { it.score < average }.size

    println("With an average of $average, there are $aboveAverage students above average and $belowAverage below it.")
}

private fun calculateAverageScore(students: Array<Student>) = students.sumBy { it.score } / students.size

And the output should be:

With an average of 81, there are 2 students above average and 3 below it.

Recap:

We avoided any regular loop by using the filter and sumBy Higher-Order Functions provided by the Kotlin Standard Library with the help of our custom made Lambda Expressions to make our code really clean and concise.

And these features are not just available for us in Kotlin, many other languages support this kind of functions, you just have to Google how to write them in each particular one of them, but they are really similar, so don't chu worry :)

You can checkout the full sample project here

Discussion

pic
Editor guide
Collapse
qwewqa profile image
qwewqa

I believe you may be able to use the count function instead of the filter function to directly get the result.

Collapse
chrisvasqm profile image
Christian Vasquez Author

Hmmm πŸ€”

I'll check it out and update the post!

EDIT:

You are totally right 😲

The filter way:

val belowAverage = students.filter { it.score < average }.size

The count way:

val belowAverage = students.count { it.score < average }

πŸŽ‰πŸŽ‰πŸŽ‰

Collapse
moopet profile image
Ben Sinclair

What happens when there are no students in the class that term?

Collapse
chrisvasqm profile image
Christian Vasquez Author

As it is right now, when the calculateAverageScore() function gets executed, since the students.size is zero, it will cause a java.lang.ArithmeticException: / by zero πŸ’₯

Collapse
neonailol profile image
Valeriy Zhirnov

To avoid this you can use average function from stdlib, so istead of this:

fun calculateAverageScore(students: Array<Student>) = 
        students.sumBy { it.score } / students.size

You can write:

fun calculateAverageScore(students: Array<Student>) = 
        students.map { it.score }.average()