Splitting string into array string.components(separtedBy: ",") consumes more time - arrays

I have text file which contains 18000 lines which have cities names. Each line has city name, state, latitude, longitude etc. Below is the function which does that, if i don't implement string.components(separtedBy: ", ") loading function is pretty fast but with it implemented it takes time which makes my UI freeze. What is the right way of doing it? Is string.components(separtedBy: ", ") that costly?
I profiled the app, this line is taking string.components(separtedBy: ", ") 1.45s out of 2.09s in whole function.
func readCitiesFromCountry(country: String) -> [String] {
var cityArray: [String] = []
var flag = true
var returnedCitiesList: [String] = []
if let path = Bundle.main.path(forResource: country, ofType: "txt") {
guard let streamReader = StreamReader(path: path) else {fatalError()}
defer {
streamReader.close()
}
while flag {
if let nextLine = streamReader.nextLine() {
cityArray = nextLine.components(separatedBy: ",") // this is the line taking a lot of time, without this function runs pretty fast
if (country == "USA") {
returnedCitiesList.append("\(cityArray[0]) , \(cityArray[1]) , \(cityArray[2])")
} else {
returnedCitiesList.append("\(cityArray[0]) , \(cityArray[1])")
}
//returnedCitiesList.append(nextLine)
} else {
flag = false
}
}
} else {
fatalError()
}
return returnedCitiesList
}
StreamReader used in the code can be found here. It helps to read file line by line
Read a file/URL line-by-line in Swift
This question is not about how to split the string into array Split a String into an array in Swift? , rather why splitting is taking more time in the given function.

NSString.components(separatedBy:) returns a [String], which requires that all of the pieces' content be copied, from the original string, and pasted into new-ly allocated stringss. This slows things down.
You could address the symptoms (UI freezing) by putting this work on a background thread, but that just sweeps the problem under the wrong (the inefficient copying is still there), and complicates things (async code is never fun).
Instead, you should consider using String.split(separator:maxSplits:omittingEmptySubsequences:), which returns [Substring]. Each Substring is just a view into the original string's memory, which stores the relevant range so that you only see that portion of the String which is modeled by the Substring. The only memory allocation happening here is for the array.
Hopefully that should be enough to speed your code up to acceptable levels. If not, you should combine both solutions, and use split off-thread.

Related

collect bytes from string

This is decoded string from bytes, they are always different. Am not using it in the code, its just for shown what is all about.
"Random String; Tags:Value1:1,Value:2,Value3:value4"
This is array of bytes from above string which i get as input.
[&u8...&u8]
What i need is get the values fromthose. While every byte in the array is changing. but some bytes are always same. I was thinking if there is any way how to extract it without using any Strings... Thanks for any ideas
so the output would look like this:
let v1 = [&u8, &u8, &u8, &u8, &u8];
let v2 = [&u8, &u8];
let v3 = [&u8];
let v4 = [&u8];
let v5 = [&u8];
You can do all this without allocating any extra space using rusts iterators, using split and related functions
At the top level, your data is of the form (key:value;)*
This suggests first splitting on ;
Then splitting each of these pieces into key and value using :
In your case, all the information is when the key is "tags".
Then within the tags section, you again have (mostly) key-value pairs of the form (key-value,)* so we need to split on , then break into key-value pairs using -.
An example that does this but only prints all the tag key-value pairs is:
fn split_kv(v: &[u8], c: u8) -> Option<(&[u8], &[u8])> {
let n = v.iter().position(|&b| b == c)?;
let w = v.split_at(n);
Some((w.0, &(w.1)[1..]))
}
fn main() {
let s: &str = "Background:Sunfire Topo;Base:Eagle;Accessory3:None;Patch:Oreo;Jacket:Pink Bonez;Eyes:BloodShot;Beak:Drool;Accessory2:Nose Ring;Accessory1:None;Item:Knife;tags:Dope Eagles,ELEMENT-HYDRO,ATTACK-10,DEFENSE-5,HIGHNESS-4,SWAG-1;metadata:QmU7JcFDoGcUvNkDgsPz9cy13md4xHdNyD6itwmgVLuo7x/860.json";
let ss: &[u8] = s.as_bytes();
let tags = ss
.split(|&b| b == b';') // Break up at ';'
.filter_map(|s| split_kv(s, b':')) // Split each piece into key-value pairs using ':'
.filter_map(|(k, v)| { // Only keep the tags entry.
if k == "tags".as_bytes() {
Some(v)
} else {
None
}
})
.next() // And just take the first of those.
.unwrap();
// Split the tags by ','
for t in tags.split(|&b| b == b',') {
// Then try to convert each to a key-value using '-' as seperator.
if let Some((k, v)) = split_kv(t, b'-') {
println!(
"k={:?} v={:?}",
std::str::from_utf8(k).unwrap(),
std::str::from_utf8(v).unwrap()
);
} else {
println!("t={:?}", std::str::from_utf8(t).unwrap());
}
}
}
You can run this here

How do I append elements to a global array (in a for loop) in Swift?

I have an empty global array. The only simple thing I want to do is add an element to this array. It seems in swift this seemingly simple task is proving to be difficult. I am just left with an empty array and nothing is appending to my global array.
I can see that it prints out values in the for loop. So the values are actually there.
This is some stuff I have declared globally (Yes, I know global variables are bad but I will sort that out later):
struct HouseDetails: Decodable {
let median_price: String
let sale_year: String
let transaction_count: String
let type: String
}
var hsArray: [HouseDetails] = []
and in the viewDidLoad() function I have the data which I am storing in local variable "houses". When I loop through the array it prints median_price, showing that the values are there.
However when I do hsArray.append(h) it seems to do nothing.
let jsonUrlString = "https://data.melbourne.vic.gov.au/resource/i8px-csib.json"
guard let url = URL(string: jsonUrlString)
else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let houses = try JSONDecoder().decode([HouseDetails].self, from: data)
for h in houses {
hsArray.append(h)
print(h.median_price)
}
}
catch let jsonErr {
print("Error with json serialization", jsonErr)
}
}.resume()
Thank you for any help. In other languages I am used to being able to append an element to the end of an existing array, so I am sure it is just a small error.
Firstly, why don't you simply do
hsArray.append(contentsOf: houses)
instead of all that for loop
for h in houses {
hsArray.append(h)
print(h.median_price)
}
The issue might be the time at which you are using hsArray. See if the response is received after you use hsArray.

How do I move String values from an array to a tuple without copying?

I have a fixed size array of Strings: [String; 2]. I want to turn it into a (String, String). Can I do this without copying the values?
The piece of code that I'm working on in particular is the following:
let (basis, names_0, names_1) = if let Some(names) = self.arg_name {
(ComparisonBasis::Name, names[0], names[1])
} else {
(ComparisonBasis::File, self.arg_file[0], self.arg_file[1])
};
types:
self.arg_name: Option<[String; 2]>
self.arg_file: Vec<String>
Right now I'm getting errors
cannot move out of type `[std::string::String; 2]`, a non-copy fixed-size array [E0508]
and
cannot move out of indexed content [E0507]
for the two arms of the if
You've omitted a fair amount of context, so I'm taking a guess at a few aspects. I'm also hewing a little closer to the question you asked, rather than the vaguer one implied by your snippets.
struct NeverSpecified {
arg_names: Option<[String; 2]>,
arg_file: Vec<String>,
}
impl NeverSpecified {
fn some_method_i_guess(mut self) -> (String, String) {
if let Some(mut names) = self.arg_names {
use std::mem::replace;
let name_0 = replace(&mut names[0], String::new());
let name_1 = replace(&mut names[1], String::new());
(name_0, name_1)
} else {
let mut names = self.arg_file.drain(0..2);
let name_0 = names.next().expect("expected 2 names, got 0");
let name_1 = names.next().expect("expected 2 names, got 1");
(name_0, name_1)
}
}
}
I use std::mem::replace to switch the contents of the array, whilst leaving it in a valid state. This is necessary because Rust won't allow you to have a "partially valid" array. There are no copies or allocations involved in this path.
In the other path, we have to pull elements out of the vector by hand. Again, you can't just move values out of a container via indexing (this is actually a limitation of indexing overall). Instead, I use Vec::drain to essentially chop the first two elements out of the vector, then extract them from the resulting iterator. To be clear: this path doesn't involve any copies or allocations, either.
As an aside, those expect methods shouldn't ever be triggered (since drain does bounds checking), but better paranoid than sorry; if you want to replace them with unwrap() calls instead, that should be fine..
Since Rust 1.36, you can use slice patterns to bind to all the values of the array at once:
struct NeverSpecified {
arg_names: Option<[String; 2]>,
arg_file: Vec<String>,
}
impl NeverSpecified {
fn some_method_i_guess(mut self) -> (String, String) {
if let Some([name_0, name_1]) = self.arg_names.take() {
(name_0, name_1)
} else {
let mut names = self.arg_file.drain(0..2);
let name_0 = names.next().expect("expected 2 names, got 0");
let name_1 = names.next().expect("expected 2 names, got 1");
(name_0, name_1)
}
}
}
See also:
Method for safely moving all elements out of a generic array into a tuple with minimal overhead

Swift - Append to array in struct

I am currently learning swift and am experimenting with data structures. In may code I have certain routines with a name(String) and several tasks(Array of Strings). These values are in a structure.
So I am trying to add another value to the array after it has been initialized. My code is actually working, however I really think it very weird and odd and DO NOT think, that it is the way it should be done.
var routineMgr: routineManager = routineManager();
struct routine{
var name = "Name";
var tasks = [String]();
}
class routineManager: NSObject {
var routines = [routine]();
func addTask(name: String, desc: String){
//init routines with name and an array with certain values, here "Hallo" & "Moin"
routines.append(routine(name: name, tasks: ["Hallo","Moin"]));
//so i just put this part here to make the example shorter, but it would be in ad different function to make more sense
//adding a new value ("Salut") to the tasks array in the first routine
//getting current array
var tempArray = routines[0].tasks;
//appending new value to current value
tempArray.append("Salut");
//replacing old routine with a copy (same name), but the new array (with the appended salut)
routines[0] = routine(name: routines[0].name, tasks: tempArray);
}
}
I have tried some (to me) "more correct" ways, like:
routines[0].tasks.append("Salut");
But I always got tons of errors, which I also did not understand.
So my question now: How is it actually done correctly? And why does the second way not work?
Your help and advice is really appreciated!
You can create a function to append the values in the struct (that is what I would do). You can even use it to validade values or anything else you need to do before append, it can also return a boolean to let your code know if the value was successfully appended or not
var routineMgr: routineManager = routineManager();
struct routine{
var name = "Name";
var tasks = [String]();
mutating func addTask(task: String){
tasks.append(task)
}
}
class routineManager: NSObject {
var routines = [routine]();
func addTask(name: String, desc: String){
routines.append(routine(name: name, tasks: ["Hallo","Moin"]));
routines[0].addTask("Salut")
}
}
I hope that helps

Swift - Write an Array to a Text File

I read into myArray (native Swift) from a file containing a few thousand lines of plain text..
myData = String.stringWithContentsOfFile(myPath, encoding: NSUTF8StringEncoding, error: nil)
var myArray = myData.componentsSeparatedByString("\n")
I change some of the text in myArray (no point pasting any of this code).
Now I want to write the updated contents of myArray to a new file.
I've tried this ..
let myArray2 = myArray as NSArray
myArray2.writeToFile(myPath, atomically: false)
but the file content is then in the plist format.
Is there any way to write an array of text strings to a file (or loop through an array and append each array item to a file) in Swift (or bridged Swift)?
As drewag points out in the accepted post, you can build a string from the array and then use the writeToFile method on the string.
However, you can simply use Swift's Array.joinWithSeparator to accomplish the same with less code and likely better performance.
For example:
// swift 2.0
let array = [ "hello", "goodbye" ]
let joined = array.joinWithSeparator("\n")
do {
try joined.writeToFile(saveToPath, atomically: true, encoding: NSUTF8StringEncoding)
} catch {
// handle error
}
// swift 1.x
let array = [ "hello", "goodbye" ]
let joined = "\n".join(array)
joined.writeToFile(...)
With Swift 5 and I guess with Swift 4 you can use code snippet which works fine to me.
let array = ["hello", "world"]
let joinedStrings = array.joined(separator: "\n")
do {
try joinedStrings.write(toFile: outputURL.path, atomically: true, encoding: .utf8)
} catch let error {
// handle error
print("Error on writing strings to file: \(error)")
}
You need to reduce your array back down to a string:
var output = reduce(array, "") { (existing, toAppend) in
if existing.isEmpty {
return toAppend
}
else {
return "\(existing)\n\(toAppend)"
}
}
output.writeToFile(...)
The reduce method takes a collection and merges it all into a single instance. It takes an initial instance and closure to merge all elements of the collection into that original instance.
My example takes an empty string as its initial instance. The closure then checks if the existing output is empty. If it is, it only has to return the text to append, otherwise, it uses String Interpolation to return the existing output and the new element with a newline in between.
Using various syntactic sugar features from Swift, the whole reduction can be reduced to:
var output = reduce(array, "") { $0.isEmpty ? $1 : "\($0)\n\($1)" }
Swift offers numerous ways to loop through an array. You can loop through the strings and print to a text file one by one. Something like so:
for theString in myArray {
theString.writeToFile(myPath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
}

Resources