Reverse byte order
This is a real world use case of how to reverse byte order.
It’s used in the code for Bitcoin.
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/download.png)
According to the book “Mastering Bitcoin” this may have been an “unintentional consequence of a design decision in early Bitcoin software”
If you want to try this in a bash shell :
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/anton-1024x427.png)
$ echo eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a \
| fold -w2 | tac | tr -d "\n"
4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb
Ok, so we know the problem, next, make a brew and then we’ll write some code:
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/ph.jpeg)
Code
Here is the code to reverse the byte order, let’s analyse it
fn main() {
let input = "eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a";
// Reverse the order of each byte
let reversed = input
.as_bytes()
.chunks(2)
.rev()
.flat_map(|chunk| chunk.iter())
.map(|&byte| byte as char)
.collect::<String>();
// Remove newline characters
let result = reversed.replace("\n", "");
println!("{}", result);
}
Let’s break down each part of the code:
as_bytes()
as_bytes()
is a method available for strings in Rust that converts a string into a sequence of bytes. Each character in the string is represented by one or more bytes.
.chunks(2)
chunks(2)
is a method provided by theIterator
trait in Rust. It transforms the sequence of bytes into an iterator of chunks, where each chunk contains 2 elements. In this case, it groups the bytes into pairs.
.rev()
rev()
is another method from theIterator
trait. It reverses the order of elements in the iterator. So, after calling.rev()
, the order of the pairs is reversed.
.flat_map(|chunk| chunk.iter())
flat_map
is a method that both flattens and maps. In this context, it is used to flatten the iterator of pairs into a single iterator of bytes. The closure(|chunk| chunk.iter())
is applied to each pair, anditer()
is used to iterate over the bytes within the pair.
.map(|&byte| byte as char)
map
is another method from theIterator
trait. It transforms each element in the iterator. Here, it is used to convert each byte into a character. The|&byte|
syntax is a closure that takes a reference to each byte and converts it to achar
.
In summary, this chain of methods takes a hexadecimal string, converts it to a sequence of bytes, groups the bytes into pairs, reverses the order of the pairs, flattens them into a single sequence of bytes, and finally, converts each byte to a character. This process effectively reverses the order of each byte in the original string.
Step by Step
as_bytes()
![as_bytes()](https://redandgreen.co.uk/wp-content/uploads/2024/02/bytes_1-1024x576.png)
chunks()
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/chunks-1024x576.png)
rev()
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/rev-1024x576.png)
Note: We only saw the actual effect of as_bytes, chunks, and rev once we had used collect() at the end. (line 11)
![Reversed Bytes](https://redandgreen.co.uk/wp-content/uploads/2024/02/reversed-1024x599.png)
So how does flat_map work?
fn main() {
// Define a vector of vectors of integers
let numbers = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
// Use flat_map to flatten the vector of vectors into integers
let flattened_numbers: Vec<i32> = numbers
.iter()
.flat_map(|vec| vec.iter().cloned())
.collect();
// Print the original vector of vectors and the flattened vector of integers
println!("Original numbers: {:?}", numbers);
println!("Flattened integers: {:?}", flattened_numbers);
}
Original numbers: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Flattened integers: [1, 2, 3, 4, 5, 6, 7, 8, 9]
[Process exited 0]
Note the use of cloned() in our code?
The use of cloned()
is often associated with flat_map
when you want to work with owned values rather than references.
The flat_map
method expects the closure passed to it to return an iterator, and it flattens the resulting iterators into a single iterator. If the original iterator yields references to values, using cloned()
is a way to create a new iterator where each element is cloned, turning references into owned values. This is necessary because the resulting iterator from flat_map
will combine elements from multiple iterators, and each element needs to have ownership.
In Rust, when you have a collection of references (&T
), and you want to transform them into owned values (T
), you often use the cloned()
method on the iterator. This ensures that you get a new iterator containing owned copies of the original values.
![](https://redandgreen.co.uk/wp-content/uploads/2024/02/bbbf8ad3-5eb6-4787-b936-fd3f816e753a.jpeg)
Thanks for reading!