Seems a Bit Odd To Me 2013-1#

Problem: Write a Dfn to produce a vector of the first n odd numbers.

Video: https://youtu.be/Mj4wyLKrBho

Code: abrudz/apl_quest

Example Solutions#

F{1-2×⍳}
G(1 02×⊢) ⍝ Tacit
H{2|⍳2×} ⍝ Any ⎕IO
I{+\2-1} ⍝ Works with ⎕IO←0 or 1
J(⍳+⍳-≢) ⍝ Tacit

Solution F#

F{1-2×⍳}

This solution demonstrates the basic approach to generating odd numbers. It uses the index generator (⍳) to create a sequence of integers, multiplies them by 2 to get even numbers, and then subtracts 1 from each result to obtain odd numbers.

  1. ⍳⍵ - Use Iota to generate the first natural numbers (1, 2, 3, 4 etc.)

  2. 2×⍳⍵ Multiply the values by 2

  3. 1-⍨2×⍳⍵ - Swap ⍨ - new expression is parsed as (2×⍳⍵)-1 The expression changes from subtracting the result from 1, to subtracting 1 from the result.

  4. Subtract one from each of the results in step 2.

This solution works when APL is set to count from 1 by default (⎕IO←1).

Solution G#

G(1 02×⊢)

This tacit function solution uses a different approach, leveraging boolean masks and the Where (⍸) function.

  1. (2×⊢) - parses as (2×⍵) - Identity replaces

  2. 1 0⍴⍨2×⊢ Swap ⍨ - parses as ((2×⍵)⍴1 0)

  3. (2×⍵)⍴1 0 Reshape see above - This creates a boolean vector by repeating [1 0] as many times as needed to reach a length of 2×⍵.

  4. Where gives the indices of ones in a Boolean vector

This solution creates a boolean mask of alternating 1s and 0s, and then uses the Where function to find the indices of the 1s, which correspond to odd numbers. It’s designed to work when the index origin is set to 0 (⎕IO←0).

Solution H#

H{2|⍳2×} ⍝ Either Index Origin

This solution is notable for its flexibility, as it works with either setting of the index origin (⎕IO).

  1. ⍳2×⍵ - multiplys the argument by two and returns the Index

  2. 2| - Modulus 2 - returns 0 (even) and 1 (odd) see Parity

  3. Where gives the indices of ones(true values) in a Boolean vector

It works by generating a sequence of numbers, checking their parity (odd or even), and then finding the indices of the odd numbers. The beauty of this solution is that the index generator (⍳) and the Where function (⍸) balance each other out, making it work correctly regardless of the index origin setting.

Solution I#

I{+\2-1} ⍝ Works with ⎕IO←0 or 1

This solution takes a mathematical approach, leveraging the property that odd numbers increase by 2 each time.

  1. ⍵↑1 - Overtake 1 adds zeros to pad the array starting with 1 ex 5↑1 is 1 0 0 0 0

  2. 2-⍵↑1 2 minus (1 0 0 0 0) is (1 2 2 2 2)

  3. +\ - Plus Scan returns the running sums from the vector in step two. (1 3 5 7 9)

By using the cumulative sum (plus scan), it generates the sequence of odd numbers without relying on index-related functionality. This makes it immune to changes in the index origin, working correctly whether ⎕IO is 0 or 1.

Solution J#

J(⍳+⍳-≢) ⍝ {(⍳⍵)+((⍳⍵)-(≢⍵))}

This tacit function solution uses of APL’s Fork structure.

The function can be read as (f g h) or (f ⍵) g (h ⍵)

f is ⍳⍵

g is +

h is (⍳-≢)⍵.

In a 3 Train the two outer functions f and h are applied to the argument first, and then their results are used as the left and right arguments to the middle function g.

(⍳ ⍵) + ((⍳-≢) ⍵)

Note: ⍳-≢ is also a 3 train and evaluates as (⍳ ⍵) - (≢ ⍵)

⍳⍵ Index Generator creates a vector of integers from 1 to the value of ⍵.

≢⍵ calculates the Tally (length) of ⍵.

We then subtract the Tally of ⍵ from each element in the vector of integers generated by ⍳⍵.

The Tally of a scaler will always be 1.

ex. (⍳5)+(⍳5)-1 is (1 3 5 7 9)

Glyphs Used#

Concepts Used#

Transcript#

Welcome to the inaugural episode of “The APL Quest,” where we will examine a different problem each week from past APL problem-solving competitions. In Phase One, please refer to the APL Wiki for details. Today, we will focus on the first problem from 2013, which asks us to write a DFN to generate odd numbers.

Let us discuss the generation of numbers. Suppose we want to generate the first ten numbers. We can use iota to generate them however, the issue is that these are all consecutive integers.

To resolve this, we multiply by two to obtain every other number, which we can then offset by one. For example, we can subtract one from one.

We can switch the arguments on the minus operation. There are other options available, such as adding negative one or using parentheses.

We can construct our argument name omega to build this and try it out. As we can see, APL starts counting from one by default.

Many individuals, particularly computer scientists and some mathematicians, prefer starting from zero instead of one. APL allows you to choose this option. Therefore, we can set the index origin to zero, and we will now count from zero instead.

This has both advantages and disadvantages. The benefit is that you can choose whichever option fits your problem and personal preference. However, the disadvantage is that if you share your code with others, you must ensure that you obtain the correct index origin. You may need to set it yourself, and writing code that works with any index origin is a classic issue.

If we attempt to apply a function now, we will obtain an entirely incorrect result. The corresponding function for index origin zero would be to add one instead of subtracting one.

There are alternative methods for generating this output. Although the problem requests a DFN, we can also write tested functions in APL. Here is another approach.

We can begin by creating the framework for a tested function, and then multiply the argument by two to generate those integers. Next, we reshape a series of zeros and ones using the zero one reshape function, with the shape on the right and the content on the left. We must now determine the indices where the true or one values are located, which we can accomplish using the where function.

This was the index origin zero case. Let us now switch back to index origin one. Naturally, this will no longer work. Instead, we must merely switch the ones and zeros.

However, suppose we wished to write a function that could function with either setting of quad I o, the index origin. In that case, there are numerous ways to accomplish this. Here are some fantastic ideas that were generated during the live event.

Suppose we start by multiplying the argument by two and generating those integers. Then we take the parity of that, or the two residue or modulus two of that. Finally, we determine where the ones are. This approach is beneficial because it works with either origin.

Let us name this argument, and what is occurring is that the index generator Iota and the where function’s Iota underbar are counterbalancing one another. Therefore, quad io equals one, and this approach works. Similarly, quad io equals zero, and it still functions correctly. It is a clever solution because it does not matter which index origin you are employing.

The reason we are encountering this issue is that we are dealing with indices. We have the index generator and the where function, which is the indices of the true values. However, we could approach this in a completely different manner, namely in a mathematical sense. If we observe that we begin with one and then increase by two each time, this is a fascinating property.

Suppose we take overtaken here, and we are taking the first ten elements of a one. There are not ten elements in the one, so APL will pad with appropriate fill elements, which are zeros. We can then subtract this from two. Notice that we have the beginning element, the one, and then the offset to the next number repeatedly.

This means that if we ask for the cumulative sum or the plus scan of that, we will obtain all the odd numbers. As we did not utilize any index-related functionality, there is no influence from the index origin. We can also put this into a function.

Suppose we wanted to return to our original formulation, where we began with two times the indices. In that case, we would subtract one if quad io equals one and add one if quad io equals zero. How can we adjust this?

Both subtraction and addition can be viewed as an addition. We simply need to add negative one or one. Therefore, if quad io equals one, we will subtract one, and if quad io equals zero, we will add one, which we can map mathematically. We can accomplish this by raising negative one to the power of quad io.

This will take whatever global quad IO is currently in effect and add it to twice the indices. We can now test this for quad io equal to one and quad io equal to zero, and it will still function correctly.

Allow me to demonstrate another clever solution. It is as follows: iota plus Iota minus the tally. This is a tacit function. We can apply it and see that it functions correctly.

It is a fork where the right tie of the fork is a fork itself. We begin by subtracting the tally from the indices. The tally represents the number of major cells in the argument. A symbol number is just one, so this is a clever method of subtracting one from the indices.

We use this as the right argument for plus. The left argument is Iota applied to the argument or the indices once more. If we add these together, we will obtain precisely what we desire. We can write all of this as indices plus indices minus the tally and give it a name to apply it.

That concludes today’s discussion. Thank you for following along.