For an array e.g. ary = [1, 2, 3, 4, 5]
I want to get the index value of each element.
i = ary.index(3) => 2
Using std::ops::Index
doesn't work as expected|desired.
What do I need to use|do?
For an array e.g. ary = [1, 2, 3, 4, 5]
I want to get the index value of each element.
i = ary.index(3) => 2
Using std::ops::Index
doesn't work as expected|desired.
What do I need to use|do?
Rust indices are generally[1] 0-based, if that's the confusion.
fn main() {
for (index, value) in [1, 2, 3, 4, 5].into_iter().enumerate() {
println!("index {index} value {value}");
}
}
/*
index 0 value 1
index 1 value 2
index 2 value 3
index 3 value 4
index 4 value 5
*/
any contiguous std
types like Vec
, arrays, slices... given that Index
is a trait, it doesn't have to be this way, e.g. for your own types ↩︎
That's not what I need.
For a given array value n
I want to find its index value in the array, as shown in the example.
In case that’s the problem, note that Index
trait is for operating the indexing operator ary[i]
, and it’s about getting to the element given the index, not the other way around.
If you want to find the index given a value to be searched in the array, you’d need to iterate over it, and could make use of methods like Iterator::position
, or if the array is sorted, use binary_search
.
arr.iter().position(|&i| i == 3) // Some(2)
Or if the contents are sorted,
arr.binary_search(&3) // Ok(2)
That's surprising this is missing from Rust.
That's a standard array method in so many languages, like Ruby, Crystal, etc.
Thanks. I'll do it another way.
It's not missing by accident.
Iterator::position()
handles this as well as cases not involving arrays or even slices. So, one function solves many problems, without needing to write the same algorithm over and over for different container types..iter()
reminds you about the performance — that this is a O(n) linear search.Ok then, can you provide the specific Rust code to perform that task in my code?
In Crystal, Ruby, et al:
residues = [7, 11, 13, 17, 19, 23, 29, 31]
i = residues.index(19) # => 4
It's what Quinedot gave to you above.
residues.iter().position(|&i| i == 19) // Some(4)
Your examples so far have all been sorted, so ... binary_search
may or may not be better (perhaps depending on how large these slices are;[1] test if you care). If you have multiple entries with the same value and care which is returned, you may want partition_point
instead of binary_search
. Compare/contrast their documentation and examples.
If you know the number exists, use .unwrap()
to get from Option<usize>
or Result<usize, usize>
to usize
.
all the examples so far are quite small ↩︎
Couldn't get none of that to work in my code, and don't have any more time to play with it.
I ended up doing this, which works with any language.
let mut posn = vec![0u8; md];
for i in 0..residues.len() { posn[residues[i]] = i as u8 }
I do this once at the top of my code.
Now the array posn
has the index positions for the elements in residues
.
And I use it within an inner loop like this.
let bit_r = 1 << posn[i];
Much more conceptually clearer, elegant, and faster (just simple array reads).
Rust is good to write in, once you know what you want to do.
But I would never use it to develop ideas with, because it doesn't come close to the ease of programmer usability and expressiveness that Ruby|Crystal provides, to quickly write code with.
But each language has its own optimum|unique use cases.
Thanks for the help though.
Just a few (scattered) comments:
This feels a bit off from where you started. Initially you were asking about looking up the index where a given value was found in an array.
Given the index
method in Ruby, how had you imagined using it to solve your problem? Using it in an up-front loop to build a lookup table, or inside the inner loop? (clearly one of the two of these is more efficient)
Based on your own conclusion, it seems like the array won't have any duplicate values (or if there are, that they should resolve to the higher-indexed entry.
Much more conceptually clearer, elegant, and faster (just simple array reads).
It definitely seems like it should be faster, but watch out for bounds-checking overhead.
You can make this slightly nicer with other iterator methods:
let mut posn = vec![0u8; md];
for (i, residue) in residues.iter().copied().enumerate() {
posn[residue] = i as u8;
}
See:
As you can see from that page, Iterator has a lot of nice functionality that can be shared and used with any collection, not just Vec. Methods are only directly on Vec if they're specific to how Vec is implemented (in fact, many are actually on the more general slice type [T]
that Vec<T>
dereferences to)
Let me try to explain the feelings I have about using Ruby|Crystal versus Rust.
I LOVE programming in Ruby|Crystal because I can basically do what I think in them.
I tolerate programming in Rust, but dread doing so, because it's not designed to be friendly.
I will always use Ruby|Crystal to design|implement ideas with.
Why? Because I can do things in seconds with them to verify if my ideas work!
If I need better performance|features Rust provides, then I'll translate their
implementations to Rust, with the other implementations acting as references to verify outputs.
Here's a specific example which predicated the question for this thread.
Here's a specific Crystal implementation of a VERY FAST prime sieve using Prime Generator Theory (PGT). It breaks the number line into groups of 240 integers, which have 64 residues.
All the primes exist on these residue tracks, and they can be indepentently sieved out.
This took minutes in Ruby|Crystal, as their idioms exist to make programmers happy (and productive).
Notice, to get the last element of an array you just do: res.last
, but can also do res[-1].
None of these idioms are available in Rust.
Also, to get the size of an array can just do: res.size
.
In Rust you do: res.len()
, but I frequently forget the ()
at the end (why make the compiler need that anyway?)
Now to the issue I created this thread for.
In Ruby|Crystal to get an array element index value you just do:
res.index(value) or res.index(value).not_nil!
This makes the implementation generic to any set of residues
, no matter how large (integer types).
Because nothing like this occurs in the Rust ecosystem, I was forced to use a work-around.
let mut bitn = vec![0u8; md];
for i in 0..rescnt { bitn[res[i]-2] = i as u8 }
I can use a byte array here because for this specific case as all the values fit within 0..255.
Of course I can change the type for larger values, but that's something I would always have to think about!
When I originally wrote it as res.index(value)
the friendly Rust compiler suggested maybe using std::os::Index
So I did, and found out the hard way (by the program continually crashing) that method doesn't work the same way it does in every other language I've used. It's the same as:
res.index(value) -> res[value]
.
This took nearly an hour to figure out, and was very frustrating|unpleasant. I was not happy.
So for this itty bitty simple task, I had to post to this forum to seek guidance on how to do this.
And lo and behold, I find out you can't, or rather the way to do it I couldn't get to work.
So I implemented an equivalent implementation, that was more elegant, clearer, and faster.
If you're just someone looking at these two implementations you might say, oh, they both look so simlilar and simple.
Yeh, but what you will never feel and experience is the joy and speed I felt doing it in Crystal versus the frustration, anguish, and much longer time, it took me to do it in Rust.
So, I will always design with Ruby|Crystal first, because I LOVE to use them.
I'll translate code to Rust, because I respect and acknowledge its usefulness, but dread using it.
Here's the Crystal code.
def sozp240(val, residues)
res = residues
md, rescnt = res.last-1, res.size
kmax = (val - 2) // md + 1
prms = Array(UInt64).new(kmax, 0)
sqrt = Math.isqrt(val)
sqrt.times do |i|
k, r = i.divmod rescnt
next if prms[k] & (1u64 << r) != 0
prm_r = res[r]
prime = md * k + prm_r
break if prime > sqrt
res.each do |ri|
kn,rn = (prm_r * ri - 2).divmod md
bit_r = 1u64 << res.index(rn+2).not_nil!
kpm = k * (prime + ri) + kn
while kpm < kmax; prms[kpm] |= bit_r; kpm += prime end
end end
prms
end
Here's the translated code to Rust.
fn sozp240(val: usize , residues: &[usize]) -> Vec<u64> {
let (res,rescnt) = (residues, residues.len());
let md = res[rescnt-1] - 1;
let kmax = (val - 2) / md + 1;
let mut prms = vec![0u64; kmax];
let sqrt = val.integer_sqrt();
let mut bitn = vec![0u8; md];
for i in 0..rescnt { bitn[res[i]-2] = i as u8 }
for i in 0..sqrt {
let (k, r) = (i/rescnt, i%rescnt);
if prms[k] & (1 << r) != 0 { continue }
let prm_r = res[r];
let prime = md * k + prm_r;
if prime > sqrt { break }
for ri in res {
let prod = prm_r * ri - 2;
let bit_r = 1 << bitn[prod % md];
let mut kpm = k * (prime + ri) + prod/md;
while kpm < kmax { prms[kpm] |= bit_r; kpm += prime };
} }
prms
}
What you are describing is very real and I think most programmers have similar experiences. But I think the reason is not necessarily that one language is better/easier/more intuitive than another.
By using a language regularly for a longer period of time, you get used to it's syntax, mental model, features, and also quirks. Over time, this shapes your mental model of code you have in your head that exists in an abstract way (without syntax). When you switch to another language, the "simple" actions your mental model is composed of no longer intuitively map to statements in the programming language. That means you'll have to go research each one of them first, and maybe even rearchitecture everything (which Rust is also famous for, if you're coming from an OOP background).
I have no data to back this up, but from my experience in this forum, people with different background struggle with very different things. Java people complain about the lack of inheritance, C++ people complain that their pointer shenanigans don't work. JS and python people struggle with static types in general, and someone who knows Haskell or Scala is missing higher-kinded types (a thing in their mental model).
Aside, many people say that writing code takes more time in Rust because the compiler forces you to think about certain stuff, but studies have shown that because of this, code has fewer bugs and you'll need to spend less time debugging. You can be more confident that you're code will be correct.
TLDR: You'll get familiar to Rust if you keep learning. And Rust is officially the most loved language.
You had me up to here.
I guess Stack Overflow is called it "most admired" now, instead of "most loved" -- Stack Overflow Developer Survey 2023
Now, a different opinion on the index
method naming:
I've written a lot of Ruby software over the years. I'm pretty happy the Rust standard library didn't borrow Ruby's method name, as I've been confused by it on many occasions. position
is great. index_of
would have been great. index
just doesn't read as "index of" in my brain. Ruby uses it (as does Crystal, of course). Apparently Python also has an index
method. Anything else?
Now, why would you not be able to use position
in the rust code? I'm not sure you should of course, because a linear search for every lookup seems unwise, in this situation.
fn main() {
let res = vec![1, 2, 7, 9, 11, 20, 27];
for val in 0..30 {
if let Some(pos) = res.iter().position(|&v| v == val) {
println!("position of value {}: {}", val, pos);
}
}
}
Aghhhhh, perl! I bet Ruby and Python inherited index
from perl...
I agree with everything you said. Familiarity is a really big factor. And in the end, the Rust code is usually just as simple and clear, or more so, than in other languages.
True. But I think almost everyone (coming from another language) struggles with single ownership and its related difficulties, since it doesn't exist in other mainstream languages. The safety (without performance compromises) you get is worth the trouble. But these troubles can be very frustrating, especially because they sometimes arise in fairly simple situations.
Rust gets better in this respect all the time and I'm optimistic. I'm looking forward to the day when it doesn't take a Rust type system expert to help with these problems, expect perhaps when using advanced features of course. And in the meantime I very much appreciate all the Rust experts here, who are incredibly good at helping people and solving even the most difficult problems, and are so willing to help!
Here's the Crystal code for its index
method for Arrays
.
# Returns the index of the first appearance of *object* in `self`
# starting from the given *offset*, or `nil` if *object* is not in `self`.
#
# ```
# [1, 2, 3, 1, 2, 3].index(2, offset: 2) # => 4
# ```
def index(object, offset : Int = 0)
index(offset) { |e| e == object }
end
# Returns the index of the first object in `self` for which the block
# is truthy, starting from the given *offset*, or `nil` if no match
# is found.
#
# ```
# [1, 2, 3, 1, 2, 3].index(offset: 2) { |x| x < 2 } # => 3
# ```
def index(offset : Int = 0, & : T ->)
offset += size if offset < 0
return nil if offset < 0
offset.upto(size - 1) do |i|
if yield unsafe_fetch(i)
return i
end
end
nil
end
This is my Rust version to mimic it, for only the parts I need for my code.
fn index(arry: &[usize], val: usize) -> usize {
let mut index = 0usize;
for (i, elem) in arry.iter().enumerate() {
if elem == &val { index = i; break }
}
index
}
This works in my Rust code, but is slower.
As an example, my Rust code using an array to store the index values, shown above,
takes 0.003323274 secs
while doing it this way takes 23.068229838 secs
.
But using index
significantly reduces memory usage, allowing me to run larger data
sets for my purposes, at the expense of speed. And right now, that's more important to me.
Because my arrays are sorted, and the vals
are valid array elements,
I know I could make it faster by doing a sort search.
If someone would like to try and speed this up I would appreciate it.