# Replacing components with indices

**URL:** https://discourse.julialang.org/t/replacing-components-with-indices/113412
**Category:** Performance
**Tags:** indexing, dictionary, arrays, for-loop, map
**Created:** [April 24, 2024, 12:39am UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412 "2024-04-24T00:39:43Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![KeepLearning](https://avatars.discourse-cdn.com/v4/letter/k/50afbb/32.png) [@KeepLearning](https://discourse.julialang.org/u/KeepLearning)
#### Post date: [April 24, 2024, 12:39am UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/1 "2024-04-24T00:39:43Z")

</div>

I am wondering if the following can be done in a better way (without for loop, perhaps): given a vector and a vector of vectors, e.g.

```julia
v = [1, 2, 3, 3, 4, 4, 5]
u = [[1,2], [3, 5], [4]]

```

I like to get

```julia
[1, 1, 2, 2, 3, 3, 2]

```

by mapping the components in `v` to the indices of `u`. I came up with two ways as follows:

```julia
for i in eachindex(u)
           v[findall(x -> x in u[i], v)] .= i
end

```

and

```julia
cond = Iterators.flatmap(enumerate(u) |> collect) do (i, x) map(reverse, i .=> x) end
for c in cond
           replace!(v, c)
end

```

The reason why I have a question is that I wonder whether I could do somthing like

```julia
for i in eachindex(u)
           replace!(v, u[i] .=> i)
end

```

, since `replace(v, 1=>0)` (for example) is possible and I thought of some extension and a possibility of avoiding using a for loop.

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [April 24, 2024, 12:51am UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/2 "2024-04-24T00:51:54Z")

</div>

> [@KeepLearning](#):
>
> I am wondering if the following can be done in a better way (without for loop, perhaps):

I would worry less about getting rid of loops (which are perfectly fine in Julia) and more about making your complexity better. Right now, your computational cost scales proportionally to `length(v) * sum(length, u)`, which is pretty bad if the vectors are long. You can do _much_ better.

For example, make a single pass over `u` first to make a dictionary mapping values to indices:

```julia
d = Dict{eltype(eltype(u)),Int}()
for i in eachindex(u), x in u[i]
    d[x] = i
end

```

then you can just do

```julia
julia> v .= get.(Ref(d), v, v)
7-element Vector{Int64}:
 1
 1
 2
 2
 3
 3
 2

```

which has nearly linear complexity in the two lengths. You might also want to consider using the dictionary (or similar) in the first place to store your mapping (so that you never create `u` at all).

(And you can do even better if you have more knowledge of `u`. e.g. if you know that the elements of `u` cover all of the values of `v`, and that these are small integers — as in your example above — then you can just store an array mapping values to indices, rather than a dictionary.)

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [April 24, 2024, 4:52am UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/3 "2024-04-24T04:52:54Z")

</div>

Another option:

```julia
foldl((r,(i,e)) -> (r[e] .= i; r), pairs(u); init=fill(0,5))[v]

```

This option uses the fact `u` is a partition of the values of `v`, and the size `5` of the value set of `v` is encoded in the initialization. But it feels this knowledge is implicit in the OP setup.

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [April 24, 2024, 9:44pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/4 "2024-04-24T21:44:26Z")

</div>

```julia
in.(v, permutedims(u))*axes(u,1)

```

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [April 24, 2024, 10:26pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/5 "2024-04-24T22:26:14Z")

</div>

Shorter:  
`in.(v,u') * axes(u,1)`

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [April 24, 2024, 11:27pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/6 "2024-04-24T23:27:15Z")

</div>

> [@rafael.guerra](#):
>
> `in.(v,u') * axes(u,1)`

This is cute, but still has poor time complexity proportional to `length(v) * sum(length, u)`, as well as poor space complexity due to allocating a temporary matrix of size `length(v) * length(u)`.

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [April 25, 2024, 7:25am UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/7 "2024-04-25T07:25:16Z")

</div>

```julia
indexinin(v,u)=[findfirst(eu->in(e, eu), u) for e in v]

```

this variant seems to be a little more efficient

```julia

function indexinin!(v,u)
    for i in eachindex(v)
        v[i]=findfirst(eu->in(v[i], eu), u)
    end
end

```

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [April 25, 2024, 12:21pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/8 "2024-04-25T12:21:23Z")

</div>

> [@KeepLearning](#):
>
> The reason why I have a question is that I wonder whether I could do somthing like
> 
> ```julia
> for i in eachindex(u)
> replace!(v, u[i] .=> i)
> end
> 
> ```
> 
> , since `replace(v, 1=>0)` (for example) is possible and I thought of some extension and a possibility of avoiding using a for loop.

Such a strategy would have the problem that the changes you apply can also change the part of the vector that has already been modified.

Unless you use kwarg count=1 😀

```julia
foreach(e->replace!(v, e=>findfirst(eu->in(e, eu), u),count=1),v)

```

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [April 25, 2024, 12:55pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/9 "2024-04-25T12:55:47Z")

</div>

> [@rocco\_sprmnt21](#):
>
> this variant seems to be a little more efficient

It still has poor time complexity proportional to `length(v) * sum(length, u)`.

> [@rocco\_sprmnt21](#):
>
> `foreach(e->replace!(v, e=>findfirst(eu->in(e, eu), u),count=1),v)`

Also has the same poor time complexity.

Realize that nearly all of the solutions posted so far (excluding mine) correspond to three nested loops, hence the multiplicative complexity. Squeezing the code into a minimal number of library calls is fine if you want to minimize code size, but it’s too easy to fall into the myth of “library calls = fast”, or to think only about code size and not about [computational complexity](https://en.wikipedia.org/wiki/Computational_complexity).

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [April 25, 2024, 1:18pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/10 "2024-04-25T13:18:17Z")

</div>

In general terms I would say yes.  
In my experience (I often try to independently create scripts that replace/improve library calls) and with my level of knowledge, library calls work better.  
But I wonder if in the specific case, being u something like a partition of unique[v], the evaluation of the spatial or temporal complexity is not different from the general worst case.

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [April 25, 2024, 1:26pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/11 "2024-04-25T13:26:38Z")

</div>

> [@stevengj](#):
>
> Realize that nearly all of the solutions posted so far (excluding mine) correspond to three nested loops,

Hopefully, the “nearly” refers to my solution too. Which has the same low complexity. In fact, due to implementation of Dicts, assuming, as it seems, that `u` is a partition of `1:n` (n=5 here), my solution would keep a multlipicative advantage in larger scales (both in memory and speed). stevengj already mentions this possibility at the end of his solution, so perhaps I’ve added nothing novel.

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [April 25, 2024, 1:44pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/12 "2024-04-25T13:44:40Z")

</div>

I wanted to compare this with the script that uses findfirst, but I can’t measure it with @benchmark f(…) setup=(…)

```julia
function richcomplexity!(u,vv)
    d=zeros(Int, length(unique(vv)))
    for i in eachindex(u), x in u[i]
        d[x] = i
    end
    vv .= getindex.(Ref(d), vv)
end

```

I tried with larger vectors than the ones provided in the OP.

```julia
bigv=rand(1:100, 10^3)

uu=unique(bigv)  

p=sort(rand(uu),10))
pl=[1;p]
pr=[p.-1;length(uu)
bigu=getindex.([uu], (:).(pl,pr))

```

```julia
ulia> function richcomplexity(u,v)
           d = Dict{eltype(eltype(u)),Int}()
           for i in eachindex(u), x in u[i]
               d[x] = i
           end
           v .= get.(Ref(d), v, v)
       end
richcomplexity (generic function with 1 method)

julia> @benchmark richcomplexity($bigu,cv) setup=(cv=copy($bigv))     
BenchmarkTools.Trial: 10000 samples with 6 evaluations.
 Range (min … max): 4.833 μs … 870.450 μs ┊ GC (min … max): 0.00% … 98.16%
 Time (median): 5.400 μs ┊ GC (median): 0.00%    
 Time (mean ± σ): 8.108 μs ± 24.920 μs ┊ GC (mean ± σ): 8.84% ± 2.96%

  █▇▅▄▄▄▁▁▁▁▁ ▂▂▁ ▁        
  █████████████▇██▇▇▇▇▆▇▇▇▇▆█████▆▆▆▆▆▇▇▅▅▅▅▄▄▅▅▂▃▃▃▄▅▅▅▃▂▅▃▂ █        
  4.83 μs Histogram: log(frequency) by time 31.8 μs <        

 Memory estimate: 6.36 KiB, allocs estimate: 10.

julia> function indexinin!(v,u)
           for i in eachindex(v)
               v[i]=findfirst(eu->in(v[i], eu), u)
           end
       end
indexinin! (generic function with 1 method)

julia> @benchmark indexinin!(cv,$bigu) setup=(cv=copy($bigv))
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 28.000 μs … 1.277 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 29.800 μs ┊ GC (median): 0.00%    
 Time (mean ± σ): 32.776 μs ± 20.407 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▇██▆▄▁ ▁ ▅▂▁ ▁▁▁▁▁▁▁ ▂        
  ████████▆█▆▇▇▆██████████████▇▇▆▄▅▄▄▄▃▄▄▃▃▁▄▄▄▄▃▃▄▄▄▄▁▅▁▃▄▃▆ █        
  28 μs Histogram: log(frequency) by time 78 μs <        

 Memory estimate: 0 bytes, allocs estimate: 0.

```

It seems like @stevenj said

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [April 25, 2024, 4:00pm UTC](https://discourse.julialang.org/t/replacing-components-with-indices/113412/13 "2024-04-25T16:00:55Z")

</div>

> [@rocco\_sprmnt21](#):
>
> In my experience (I often try to independently create scripts that replace/improve library calls) and with my level of knowledge, library calls work better.

“Library calls are faster” is an unreliable heuristic.

1. First, you should always try to have some sense of what the library calls are doing — at least know the expected complexity. (e.g. `findfirst(predicate, array)` or `x in array` are both sequential search, so have an average cost that scales with the array length multiplied by the cost of the predicate).
2. Second, as I [wrote in another blog post](https://julialang.org/blog/2017/01/moredots/#why_vectorized_code_is_not_as_fast_as_it_could_be): _There is a tension between two general principles in computing: on the one hand, **re-using** highly optimized code is good for performance; on the other other hand, optimized code that is **specialized** for your problem can usually beat general-purpose functions._

That being said, there is absolutely nothing wrong with writing code to minimize code length or maximize clarity (hopefully both) rather than optimizing speed. But you should be _aware_ that you are doing so, _especially_ if your code has suboptimal _complexity/scaling_ (not just a suboptimal constant factor, which requires a lot more expertise to deal with).

> [@Dan](#):
>
> Hopefully, the “nearly” refers to my solution too.

Right, your solution also does one linear-time pass over `u` and one linear-time pass over `v`. 👍
