Concatenate/Merge two prettified json strings in Rust - rust

I have two strings that are derived from serde from two different sources
JSON String 1
let a: String =
"
{
"A": 100
}
";
JSON String 2
let b: String =
"
{
"B": "aA"
}
";
I would like to concatenate these two strings to 1 single json prettified string new_string which can be displayed like as follows:-
println!("{}", new_string)
Result:-
{
"A": 100,
"B": "aA"
}
I tried concatenating them using vectors but whenever I print
them I get newline characters which I want to avoid.Is this possible in Rust using serde.

In their current form as strings, it would be rather difficult to properly combine arbitrary JSON values.
Instead, you need to deserialize those strings into workable values, combine them, then serialize the final value back to a string:
use serde_json::Value;
use std::collections::HashMap;
fn main() {
let a = r#"
{
"A": 100
}
"#;
let b = r#"
{
"B": "aA"
}
"#;
let mut data_a: HashMap<String, Value> = serde_json::from_str(a).unwrap();
let mut data_b: HashMap<String, Value> = serde_json::from_str(b).unwrap();
data_a.extend(data_b);
let result = serde_json::to_string_pretty(&data_a).unwrap();
println!("{}", result);
}
Ideally, you wouldn't even serialize values to strings in the first place. Whatever values you get from your "two different sources" might also be possible to either combine directly, or be serialized into a serde_json::Value that you can work with.

Related

How to remove characters from specific index in String?

I have an application where I am receiving a string with some repetitive characters. I am receiving input as a String. How to remove the characters from specific index?
main.rs
fn main() {
let s:String = "{\"name\":\"xx/yyyy/machine/zzz/test_int4\",\"status\":\"online\",\"timestamp\":\"2021-06-11 18:20:42.231770800 UTC\",\"value\":7}8668982856274}".to_string();
println!("{}", s);
}
how can I get result
"{\"name\":\"xx/yyyy/machine/zzz/test_int4\",\"status\":\"online\",\"timestamp\":\"2021-06-11 18:20:42.231770800 UTC\",\"value\":7}"
instead of
"{\"name\":\"xx/yyyy/machine/zzz/test_int4\",\"status\":\"online\",\"timestamp\":\"2021-06-11 18:20:42.231770800 UTC\",\"value\":7}}8668982856274}"
String indexing works only with bytes, thus you need to find an index for the appropriate byte slice like this:
let mut s = "{\"name\":\"xx/yyyy/machine/zzz/test_int4\",\"status\":\"online\",\"timestamp\":\"2021-06-11 18:20:42.231770800 UTC\",\"value\":7}8668982856274}";
let closing_bracket_idx = s
.as_bytes()
.iter()
.position(|&x| x == b'}')
.map(|i| i + 1)
.unwrap_or_else(|| s.len());
let v: serde_json::Value = serde_json::from_str(&s[..closing_bracket_idx]).unwrap();
println!("{:?}", v);
However, keep in mind, this approach doesn't really work in general for more complex cases, for example } in a json string value, or nested objects, or a type other than an object at the upmost level (e.g. [1, {2: 3}, 4]). More neat way is using parser capabilities to ignore of the trailing, as an example for serde_json:
let v = serde_json::Deserializer::from_str(s)
.into_iter::<serde_json::Value>()
.next()
.expect("empty input")
.expect("invalid json value");
println!("{:?}", v);

Use char as &str in HashMap

I want to create a HashMap which maps words β€” a Vec of &str β€” and letters of those words each to another. For example, vec!["ab", "b", "abc"] will be converted to the following HashMap
{
// Letters are keys, words which contain the keys are values
"a" => ["ab", "abc"],
"b" => ["ab", "bc", "abc"],
"c" => ["bc", "abc"],
// Words are keys, letters which are in the words are values
"ab" => ["a", "b"],
"abc" => ["a", "b", "c"],
}
I tried this code [playground]:
let words = vec!["ab", "bc", "abc"];
let mut map: HashMap<&str, Vec<&str>> = HashMap::new();
for word in words.iter() {
for letter in word.chars() {
map.entry(letter).or_default().push(word);
map.entry(word).or_default().push(letter);
}
}
but there is a problem: letter is of type char but I need a &str because map accepts only &strs as keys. I also tried to convert letter to a &str:
for word in words.iter() {
for letter in word.chars() {
let letter = letter.to_string()
// no changes
but this code creates a new String which has a smaller lifetime than map's one. In other words, letter is dropped after the nested for loop but and I get compiler error.
How can I use a char in HashMap which accepts only &strs as keys?
I would separate the map into two:
One is char -> Vec<&str>. It maps letters to a list of words.
The second one would be &str -> Vec<char>, but I do not know if you really need it: Why not just iterate over the chars of a &str directly?
Storing a Vec<char> essentially just doubles the amount of memory you use, unless the Vec<char> is e.g. sorted in a particular order (which may or may not be necessary).
If you really want to keep them in one map, I think it is easier to have a HashMap<String, Vec<String>>.
The problem seems to be that chars gives you one char after another, but what you actually want is a &str after another, where each &str actually encompasses a single character. I did not find anything like that in the docs for &str, but maybe there is something somewhere that iterates like this.
You could work-around it using matches:
let words = vec!["ab", "bc", "abc"];
let mut map: HashMap<&str, Vec<&str>> = HashMap::new();
for word in words.iter() {
for letter in word.matches(|_| true) { // iterates over &str's that encompass one single character
map.entry(letter).or_default().push(word);
map.entry(word).or_default().push(letter);
}
}

How to fix mismatched types when adding two Strings?

With a list input of &str, I'm trying to create a String which contains a proverb based on the inputs.
Instead of String::from I also tried .to_string() but that doesn't seem to help matters.
pub fn build_proverb(list: &[&str]) -> String {
let mut string = String::from(format!("For want of a {} the {} was lost.\n", list[0], list[1]));
if list.len() > 2 {
(3..list.len() + 1).map(|x| string = string + String::from(format!("For want of a {} the {} was lost.\n", list[x], list[x-1])));
}
string = string + &(format!("And all for the want of a {}.", list[0])).to_string();
return string.to_string();
}
The error is:
error: mismatched types expected &str, found struct 'std::string::String'.
This is on the String::from(format!("For want of a {} the {} was lost.\n", list[x], list[x-1])) line.
What's confusing to me is that I'm adding a String to a String - why is it expecting a &str?
format! already returns a String, so there's no need for String::from(format!(...)), and it's also an error because it expects a &str, not a String returned by format!.
You'll also get an error in the lambda:
string = string + String::from(format!(...))
...even if you remove String::from, because it's not possible to add two Strings like that, but it is possible to add a String and a &str, so I think you should borrow like this:
string = string + &format!(...)
The same goes for this line:
string = string + &(format!("And all for the want of a {}.", list[0])).to_string();
Moreover, your usage of map won't actually execute the lambda for each element of the range. It'll just create a Map iterator, over which you'll have to iterate with a loop to actually make it execute the lambda, so you could as well iterate over the range itself and modify your string in the loop.
I'm also not terribly sure about why you're returning string.to_string() when you could've returned string itself.
I also think you have an off-by-one error in your range, so after fixing that, I ended up with this:
fn do_it(list: Vec<&str>) -> String {
let mut string = format!("For want of a {} the {} was lost.\n", list[0], list[1]);
// BTW, you don't need this `if` statement because empty ranges, like `2..2`, are perfectly fine
if list.len() > 2 {
// These ranges do not include `list.len()`, so your program won't panic, because this index doesn't exist
for x in 2 .. list.len() {
string += &format!("For want of a {} the {} was lost.\n", list[x], list[x-1])
}
}
string + &format!("And all for the want of a {}.", list[0]) // Return the result of concatenation
}
fn main() {
let list = vec!["something", "test", "StackOverflow"];
let result = do_it(list);
println!("The result is: {}", result)
}
Output (it works, but your post doesn't say what it should output, so I can't say if it's the correct result):
The result is: For want of a something the test was lost.
For want of a StackOverflow the test was lost.
And all for the want of a something.

How do I split a string in Rust?

From the documentation, it's not clear. In Java you could use the split method like so:
"some string 123 ffd".split("123");
Use split()
let mut split = "some string 123 ffd".split("123");
This gives an iterator, which you can loop over, or collect() into a vector.
for s in split {
println!("{}", s)
}
let vec = split.collect::<Vec<&str>>();
// OR
let vec: Vec<&str> = split.collect();
There are three simple ways:
By separator:
s.split("separator") | s.split('/') | s.split(char::is_numeric)
By whitespace:
s.split_whitespace()
By newlines:
s.lines()
By regex: (using regex crate)
Regex::new(r"\s").unwrap().split("one two three")
The result of each kind is an iterator:
let text = "foo\r\nbar\n\nbaz\n";
let mut lines = text.lines();
assert_eq!(Some("foo"), lines.next());
assert_eq!(Some("bar"), lines.next());
assert_eq!(Some(""), lines.next());
assert_eq!(Some("baz"), lines.next());
assert_eq!(None, lines.next());
There is a special method split for struct String:
fn split<'a, P>(&'a self, pat: P) -> Split<'a, P> where P: Pattern<'a>
Split by char:
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
Split by string:
let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
assert_eq!(v, ["lion", "tiger", "leopard"]);
Split by closure:
let v: Vec<&str> = "abc1def2ghi".split(|c: char| c.is_numeric()).collect();
assert_eq!(v, ["abc", "def", "ghi"]);
split returns an Iterator, which you can convert into a Vec using collect: split_line.collect::<Vec<_>>(). Going through an iterator instead of returning a Vec directly has several advantages:
split is lazy. This means that it won't really split the line until you need it. That way it won't waste time splitting the whole string if you only need the first few values: split_line.take(2).collect::<Vec<_>>(), or even if you need only the first value that can be converted to an integer: split_line.filter_map(|x| x.parse::<i32>().ok()).next(). This last example won't waste time attempting to process the "23.0" but will stop processing immediately once it finds the "1".
split makes no assumption on the way you want to store the result. You can use a Vec, but you can also use anything that implements FromIterator<&str>, for example a LinkedList or a VecDeque, or any custom type that implements FromIterator<&str>.
There's also split_whitespace()
fn main() {
let words: Vec<&str> = " foo bar\t\nbaz ".split_whitespace().collect();
println!("{:?}", words);
// ["foo", "bar", "baz"]
}
The OP's question was how to split with a multi-character string and here is a way to get the results of part1 and part2 as Strings instead in a vector.
Here splitted with the non-ASCII character string "β˜„β˜ƒπŸ€”" in place of "123":
let s = "β˜„β˜ƒπŸ€”"; // also works with non-ASCII characters
let mut part1 = "some string β˜„β˜ƒπŸ€” ffd".to_string();
let _t;
let part2;
if let Some(idx) = part1.find(s) {
part2 = part1.split_off(idx + s.len());
_t = part1.split_off(idx);
}
else {
part2 = "".to_string();
}
gets: part1 = "some string "
Β  Β  Β  Β  Β part2 = " ffd"
If "β˜„β˜ƒπŸ€”" not is found part1 contains the untouched original String and part2 is empty.
Here is a nice example in Rosetta Code -
Split a character string based on change of character - of how you can turn a short solution using split_off:
fn main() {
let mut part1 = "gHHH5YY++///\\".to_string();
if let Some(mut last) = part1.chars().next() {
let mut pos = 0;
while let Some(c) = part1.chars().find(|&c| {if c != last {true} else {pos += c.len_utf8(); false}}) {
let part2 = part1.split_off(pos);
print!("{}, ", part1);
part1 = part2;
last = c;
pos = 0;
}
}
println!("{}", part1);
}
into that
Task
Split a (character) string into comma (plus a blank) delimited strings based on a change of character (left to right).
If you are looking for the Python-flavoured split where you tuple-unpack the two ends of the split string, you can do
if let Some((a, b)) = line.split_once(' ') {
// ...
}

How to iterate through characters in a string in Rust to match words?

I'd like to iterate through a sentence to extract out simple words from the string. Here's what I have so far, trying to make the parse function first match world in the input string:
fn parse(input: String) -> String {
let mut val = String::new();
for c in input.chars() {
if c == "w".to_string() {
// guessing I have to test one character at a time
val.push_str(c.to_str());
}
}
return val;
}
fn main() {
let s = "Hello world!".to_string();
println!("{}", parse(s)); // should say "world"
}
What is the correct way to iterate through the characters in a string to match patterns in Rust (such as for a basic parser)?
Checking for words in a string is easy with the str::contains method.
As for writing a parser itself, I don't think it's any different in Rust than other languages. You have to create some sort of state machine.
For examples, you could check out serialize::json. I also wrote a CSV parser that uses a buffer with a convenient read_char method. The advantage of using this approach is that you don't need to load the whole input into memory at once.

Resources