Is there a function for creating n (nearly) evenly-spaced integers for indexing?

This seems to be different from the OP example, as it produces indexes: 1:3:88, and not variable step (= 3 or 4) indexes.

1 Like

From the proposed solutions I seemed to understand that this was the expected result.

A=rand(100)
A[range(begin, step=end Γ· len, length=len)]

It is not so?
I just wanted to write one of the solutions a little differently

1 Like

Let’s say I want an integer difference between integers, and I care that that each step between each number is an integer. Julia has a convenient syntax for this: begin:step:end.

My criterion may be distinct from yours.

  1. I want my indices to be exactly spaced apart by the same step.
  2. I do not care if the last index is included.
julia> function helper(A; step)
           B = A[begin:step:end]
           @info "B = A[begin:step:end]" B[1] B[2] B[end] length(B)
           return B
       end
helper (generic function with 1 method)

julia> A = 1:100
1:100

julia> helper(A, step = 1)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 2
β”‚   B[end] = 100
β””   length(B) = 100
1:1:100

julia> helper(A, step = 2)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 3
β”‚   B[end] = 99
β””   length(B) = 50
1:2:99

julia> helper(A, step = 3)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 4
β”‚   B[end] = 100
β””   length(B) = 34
1:3:100

julia> helper(A, step = 4)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 5
β”‚   B[end] = 97
β””   length(B) = 25
1:4:97

julia> helper(A, step = 5)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 6
β”‚   B[end] = 96
β””   length(B) = 20
1:5:96

julia> helper(A, step = 6)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 7
β”‚   B[end] = 97
β””   length(B) = 17
1:6:97

julia> helper(A, step = 7)
β”Œ Info: B = A[begin:step:end]
β”‚   B[1] = 1
β”‚   B[2] = 8
β”‚   B[end] = 99
β””   length(B) = 15
1:7:99

For many arrays, 1:step:end might do, but not all Julia arrays start at index 1.

Next we need to determine step. It could be length(A) Γ· desired_length. Note that Γ· is an alias for div, which will do integer division, rounding towards zero. Γ· is similar to \\ in Python.

Simply setting step as above would create an array that is longer than desired. Thus we have to further truncate the result to the desired length.

Below I will use the idiom end-begin+1 to stand in for length.

julia> function helper2(A; length)
           B = A[begin:(end-begin+1)Γ·length:end]
           B = B[begin:begin+length-1]
           @info "B:" B[1] B[2] B[end] Base.length(B)
           return B
       end
helper2 (generic function with 1 method)

julia> helper2(A, length = 100)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 2
β”‚   B[end] = 100
β””   Base.length(B) = 100
1:1:100

julia> helper2(A, length = 95)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 2
β”‚   B[end] = 95
β””   Base.length(B) = 95
1:1:95

julia> helper2(A, length = 50)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 3
β”‚   B[end] = 99
β””   Base.length(B) = 50
1:2:99

julia> helper2(A, length = 45)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 3
β”‚   B[end] = 89
β””   Base.length(B) = 45
1:2:89

julia> helper2(A, length = 32)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 4
β”‚   B[end] = 94
β””   Base.length(B) = 32
1:3:94

julia> helper2(A, length = 16)
β”Œ Info: B:
β”‚   B[1] = 1
β”‚   B[2] = 7
β”‚   B[end] = 91
β””   Base.length(B) = 16

In summary, if you are flexible on the last index but want an integer step, there is an easy syntax if you can specify step.

1 Like

Great idea

Thank you for your help! I like you and @rocco_sprmnt21 using the div function, though I haven’t quite made it work.

I tried

helper2(1:41, 23)

and got back 1:1:23. This would downsample the long array but instead give only the first section of it.

This is very similar to @mbauman’s solution - which is equally good. I slightly prefer using div rather than round (and rather than Γ· which I can’t find on my keyboard).

So in the original notation, from now on I’ll use

sampleindices = range(start=1, step=div(length(longarray), numsamples), length=numsamples)
downsampled = longarray[sampleindices]

The reason for making sampleindices is that other parallel arrays need indexing my typical use cases.

Thank you everyone!

In one case (round) the range 1:100 is sampled with variable step (3 or 4) but does cover the full interval from 1 to 100, while in the simpler/trivial case, a constant step of 3 is used to produce 30 indexes: 1:3:88, leaving a big gap in the tail (from 89 to 100).

2 Likes
help?> Γ·
"Γ·" can be typed by \div<tab>
2 Likes

These are important nuances to keep in mind for each use case! I’d mark both as solutions if I could.

I wrote a more general function for similar usecases:

julia> using DataManipulation

julia> discreterange(identity, 1, 100; length=30)
30-element Vector{Int64}:
   1
   4
   8
...
  90
  93
  97
 100

Its main target are transformed ranges, like logarithmically-spaced discreterange(log, 1, 1000; length=30). There, you cannot just create a float range and round it to integers.
But discreterange works for regular linear ranges as well, as shown in the example.

3 Likes

I noticed that the initial indexes don’t really follow a logarithmic proportion.
In this specific example, the output is equivalent to:

[1:4; round.(Int, exp.(range(log(5), log(1000), length=30-4)))]

Of course, because that would be impossible (:

In some applications, it makes sense to allow repeated indexes at the beginning:

round.(Int, exp.(range(log(1), log(1000), length=30)))

Sure, the whole point of discreterange() is to give distinct integers. So both variants are available, either for regular linear ranges of for mapped ones:

julia> round.(Int, maprange(log, 1, 100; length=20))
[1, 1, 2, 2, 3, 3, 4, 5, 7, 9, 11, 14, 18, 23, 30, 38, 48, 62, 78, 100]

julia> discreterange(log, 1, 100; length=20)
[1, 2, 3, 4, 5, 6, 7, 9, 11, 14, 17, 20, 25, 30, 37, 45, 55, 67, 82, 100]
1 Like

Any particular reason for the name discreterange, as opposed to something like integerrange? I suppose β€œdiscrete” is meant to imply integers, in the sense that floats are (nearly) continuous. But discrete mathematics can refer to finite countable objects, not necessarily one-to-one with all integers (or Int64).

Any particular reason for the name discreterange , as opposed to something like integerrange ?

Not really, just the first name that came to mind when I needed such a function (:
uniqueintegerrange would be the most descriptive, even though a bit on the longer side…

3 Likes