# Combine multiple ranges with different resolution

I’m computing a function that is smooth in some domain and has quite a few fine features somewhere else, so I would like my x variable to have a different resolution in different regions. I’m using `range()` for each subdomain, and while doing my numerical explorations and adjusting those ranges I want the computer to automatically remove duplicates and sort the different ranges without worrying about it myself.

I’ve come up with this, but it feels very clumsy,

``````sort(unique(hcat([collect(range(0.3,1,length=100)),
collect(range(1,3,length=500)),
collect(range(1,10,length=100)),
collect(range(10,100,length=100))]...)))
``````

Am I missing a more straightforward approach? (in Base Julia – I’m asking this to learn better practice, rather than for the end-result).

To do explicitly what you want, no. Ranges are basically stored as `(start,stop,steplength)`, so they can’t really be “mixed”.

To address your actual problem, you should consider something like log-spacing. Create a linearly spaced grid, then log transform it. You will get more points closer to zero, and the points at the other end of the grid space out more (because now the growth rate between two points is equal, not the distance between them). You can also apply this transformation as many times as you want.

1 Like

log isn’t really helping because there are various regions of interest requiring high def (sure, I could invent an ad hoc transform). I guess what bothers me is the need to `collect()` each range explicitly just to be able to call unique and sort on the resulting array. Is there an alternative to `range()` that already expands to an explicit array?

Is this what you want? https://github.com/SciML/MultiScaleArrays.jl

Speed up your original code? That we can do.

You can drop all of the `collect`s. Try this instead.

``````sort(unique(Iterators.flatten((range(0.3,1,length=100),
range(1,3,length=500),
range(1,10,length=100),
range(10,100,length=100)))))
``````

Many things in Julia just need arguments that are iterable. They don’t need actual arrays.

2 Likes

If you are willing to invest a bit more into this, you could make a partition of n adjacent ranges by storing n+1 endpoints and n lengths. This should be easy to code in a custom type, and you can reuse a lot of optimized code for ranges (eg `searchsortedfirst` for locating bins).

There are a few things wrong with this code.

Firstly, I guess you mean `vcat` not `hcat`, since `hcat` will try to do horizontal concatenation and create a matrix. It will actually error here.

Secondly, you put each range in a vector and then splat that vector, like this:

``````vcat([a, b, c]...)
``````

This is identical to writing

``````vcat(a, b, c)
``````

except that it creates a temporary array for no reason that you just throw away again, and which reduces performance and increases allocations.

Thirdly, you are using `collect`, which you should basically never do. `collect` is a function you should rarely need to use, and certainly not here, it just takes a fast, lightweight, cheap, smart range-vector, and turns it into a heavy, expensive and dumb vector.

``````sort(unique(vcat(range(0.3,1,length=100),
range(1,3,length=500),
range(1,10,length=100),
range(10,100,length=100))))
``````

There are still probably better ways of doing what you want.

Rule of thumb: Don’t use `collect`. Only use it if your code fails otherwise.

Edit: You can also write `[a; b; c]` instead of `vcat(a, b, c)` if you prefer. They are the same thing.

3 Likes

Thanks – indeed, `collect()` struck me as something to be avoided, but since I was wrongly using hcat* instead of vcat I mistakenly assumed I needed to explicitly expand the ranges before combining them.

It often seems obvious in hindsight, but not when typing new things in a new language.

*: (Though by some unfortunate coincidence my code with hcat did work – all the ranges had the same number of elements in my original example)

True, I can imagine a few ways to abstract this problem out, but here I really wanted to clarify my understanding of those low-level basic functions. Thanks, though.

In the spirit of premature optimization, I tried to eliminate allocations for you by defining an iterator that is equivalent to `range` when `collect`ed. Surprisingly, it doesn’t appear to be faster when you substitute it into your problem, but there could be some benchmarking trickery going on.

``````linspace(a,b,N)= (a+ (b-a)*(i)/(N-1) for i=0:(N-1))
``````

You could play around with it though.

1 Like