Skip to main content

Algorithmic Adventures: Exploring the Art of Number Sequences

·1098 words·6 mins
Math
Table of Contents

I’ve always been fascinated by number sequences and the possibility of deriving a simple algorithm to generate them.

What is a Number Sequence?
#

A number sequence is essentially a list of numbers arranged in a specific order, where each number is related to the others according to a particular rule or pattern. This defining rule determines how the sequence progresses and is key to understanding the relationship between its elements.
These sequences serve various purposes, ranging from simple mathematical exercises to complex problem-solving tools in areas such as computing and algorithm development. They are foundational elements in the study of mathematics and have vast applications in real-world scenarios as well.

Let’s dive into a simple example:

$$ 2\thickspace4\thickspace6\thickspace8\thickspace? $$

It’s an arithmetic sequence where each number increases by \(2\): $$ 2\textcolor{red}{+2} = 4 \textcolor{red}{+2} = 6 \textcolor{red}{+2} = 8 \textcolor{red}{+2} = 10 $$

Number Sequence Examples
#

Let’s look at some more examples:

Sequence 1: $$ 3\thickspace9\thickspace27\thickspace81\thickspace? $$

Solution This is a geometric sequence, where each number is the previous one multiplied by \(3\): $$ 3\textcolor{red}{*3} = 9 \textcolor{red}{*3} = 27 \textcolor{red}{*3} = 81 \textcolor{red}{*3} = 243 $$

Sequence 2: $$ 0\thickspace1\thickspace1\thickspace2\thickspace3\thickspace5\thickspace? $$

Solution Here, we can see a Fibonacci sequence: $$ 0\thickspace1\thickspace1\thickspace2\thickspace3\thickspace5\thickspace\textcolor{red}{8} $$

Sequence 3: $$ 2\thickspace6\thickspace3\thickspace9\thickspace6\thickspace? $$

Solution This sequence alternates between multiplying by \(3\) and subtracting \(3\): $$ 2\textcolor{red}{*3} = 6 \textcolor{red}{-3} = 3 \textcolor{red}{*3} = 9 \textcolor{red}{-3} = 6 \textcolor{red}{*3} = 18 $$

How to generate a Number Sequence
#

Let’s break down how a sequence can be generated, using the examples above.

Starting Number
#

Every sequence begins with a starting number, which could be randomly selected. This number might or might not relate to the rest of the sequence.

Factor
#

For sequences like Sequence 1, a factor is involved. In that example, the factor is \(3\), which could also be randomly chosen.

Operator
#

Operators are crucial in defining how each number in a sequence is generated from its predecessors. Understanding these can help in predicting sequence progression and even in algorithm design. Let’s explore the various types of operators that link numbers in sequences based on the examples above:

Binary Operator
#

Binary operators take two inputs: the current number in the sequence and the factor. They apply a mathematical operation to these two inputs to produce the next number.
Examples include addition, subtraction, or multiplication.

$$ 3\textcolor{red}{*3} = 9 \textcolor{red}{*3} = 27 \textcolor{red}{*3} = 81 \textcolor{red}{*3} = 243 $$

Unary Operator
#

Unary operators need only one input - the current number - to determine the next one. This type is less common in simple sequences.
An example is squaring the number:

$$ 2 \textcolor{red}{^2}= 4 \textcolor{red}{^2} = 16\textcolor{red}{^2} = 256 \textcolor{red}{^2} = 65536 $$

List Operator
#

List operators consider the entire list of previous numbers in the sequence to generate the next one. An example is summing the previous numbers:

$$ 3 = 3 \textcolor{red}{+3} = 6\textcolor{red}{+3+3} = 12 \textcolor{red}{+6+3+3} = 24 $$

Operator Implementation
#

Here’s an example of how an operator could be implemented in Kotlin:

interface Operator<T> {
    fun apply(t: T): Int
}

enum class BinaryOperator : Operator<Pair<Int, Int>> {
    PLUS {
        override fun apply(t: Pair<Int, Int>): Int {
            return t.first + t.second
        }
    },
    MINUS {
        override fun apply(t: Pair<Int, Int>): Int {
            return t.first - t.second
        }
    },
    TIMES {
        override fun apply(t: Pair<Int, Int>): Int {
            return t.first * t.second
        }
    },
    REMAINDER {
        override fun apply(t: Pair<Int, Int>): Int {
            return t.first % t.second
        }
    }
}

enum class UnaryOperator : Operator<Int> {
    SQUARE {
        override fun apply(t: Int): Int {
            return t * t
        }
    },
    DIGIT_SUM {
        override fun apply(t: Int): Int {
            if (t == 0) return 0
            val sign = if (t < 0) -1 else 1
            var digitSum = 0
            var n = t * sign
            while (n > 0) {
                digitSum += n % 10
                n /= 10
            }
            return digitSum * sign
        }
    }
}

enum class ListOperator : Operator<List<Int>> {
    SUM {
        override fun apply(t: List<Int>): Int {
            return t.sum()
        }
    }
}

Additional Operator Properties
#

Alternating Operator
Besides the type of operator, we should also consider alternating operators. This aspect is present in Sequence 3 in Number sequence examples.

Two Operators per iteration
It’s also possible for the next number in a sequence to be determined by using two operators. However, using more than two operators can make the sequence overly complex and difficult to solve.

$$ 2\textcolor{red}{*3+3} = 9 \textcolor{red}{*3+3} = 30 \textcolor{red}{*3+3} = 93 \textcolor{red}{*3+3} = 282 $$

Sequence Length
#

It’s crucial to balance the sequence length to avoid too much simplicity or complexity, which could either obscure the pattern or make it too obvious.

Plausibility Checks
#

When generating sequences, it’s also crucial to ensure they are logical and coherent. This includes avoiding excessively large numbers, preventing too many zeros, and ensuring there is no repetitive pattern that could make the sequence trivial.

Weighted Operators
#

Weighted operators introduce an element of control over the difficulty of a sequence by influencing the likelihood of selecting certain operators during the sequence generation.
In the context of sequence generation, each operator (like addition, multiplication, or another operator) is assigned a weight - a numerical value that indicates how often the operator should be chosen relative to others. Operators with higher weights are more likely to be selected, thus appearing more frequently in sequences.

A simple implementation in Kotlin could look like the following:

class WeightedOperator<T>(val operator: Operator<T>, val weight: Int)

fun getRandomOperatorsFromWeightedOperators(
    availableOperators: List<WeightedOperator<*>>,
    operatorsToSelect: Int
): List<Operator<*>> {
    val operators = mutableListOf<Operator<*>>()
    val overallWeight = availableOperators.sumOf { it.weight }
    val filteredOperators = availableOperators.filter { weightedOperator -> weightedOperator.weight > 0 }
    while (operators.size < min(operatorsToSelect, filteredOperators.size)) {
        var currentWeight = 0
        val targetWeight = Random.nextDouble() * overallWeight
        for (operator in filteredOperators) {
            currentWeight += operator.weight
            if (currentWeight >= targetWeight) {
                if (!operators.contains(operator.operator)) {
                    operators.add(operator.operator)
                }
                break

            }
        }
    }
    return operators
}

Implementation
#

Let’s bring all aspects together. An implementation of a number sequence generator wrapped in an Android app can be found in:

achawki/number-sequence-trainer-android

An Android app for practising number sequences.

Kotlin
0
0

The core aspect of the sequence generation can be found in LocalSequenceGenerator.kt.

Feel also free to directly download the Android app: Get it on Google Play

Summary
#

Exploring number sequences, whether for improving algorithmic logic or simply for fun, can enhance both mathematical insight and programming skills. By examining different types of sequences and learning how to generate them, you gain a deeper appreciation for the underlying patterns that influence much of our digital world.

Amin Chawki
Author
Amin Chawki
Senior Software Engineer