This question already has answers here:
Split string by components and keep components in place
(2 answers)
Closed last year.
I am new to Swift, I come from Java background. I want to make a struct for parsing input for a calculator. Here is my code
struct Parse {
var runningCalc: String
init(runningCalc: String) {
self.runningCalc = runningCalc
}
mutating func toArray(String: runningCalc){
runningCalc = runningCalc.split()
}
}
I want to have input of a string, and have it return a string Array with delimiters of
{ +-/*()%}.
In java you could do a string tokenizer to keep the delimiters in the returned string array.
How can I use the split or components method to separate my string input to a string array?
I personally wouldn't use a struct. It's more common to extend an existing type with additional functionality. So, you want the components of a String that includes delimiters? Sounds like you want to extend String. Since String conforms to StringProtocol I'd extend StringProtocol so that any other types that conform to StringProtocol get this new functionality for free:
StringProtocol already has a method called components(separatedBy:) which takes a CharacterSet (a set of characters) to delimit the receiver.
Let's implement a method similar to the existing StringProtocol method.
extension StringProtocol {
func components(separatedByIncluding delims: CharacterSet) -> [String] {
var components: [String] = []
var component = ""
for character in self {
if String(character).rangeOfCharacter(from: delims) != nil {
if !component.isEmpty {
components.append(component)
}
components.append(String(character))
component = ""
} else {
component += [character]
}
}
if !component.isEmpty {
components.append(component)
}
return components
}
}
The method loops over each Character and checks if it's a delimiter. If so, it adds a new String to the array consisting of the delimiter Character. If not, it constructs a substring consisting of non-delimiter Character values. The resulting array of delimiters and substrings are then returned.
Usage would be:
func testExample() throws {
let delims = CharacterSet(charactersIn: "{ +-/*()%}")
let text = "{abc def+ghi-jkl/mno"
let components = text.components(separatedByIncluding: delims)
print("================")
print(components)
print("================")
}
The result is:
================
["{", "abc", " ", "def", "+", "ghi", "-", "jkl", "/", "mno"]
================
AFAIK there is no native Swift split method that allows you to keep the delimiters. You will need to implement your own split method extending Collection. Special thanks for #MartinR and #Rob for the code review:
extension Collection {
func splitAndKeep(
maxSplits: Int = .max,
omittingEmptySubsequences: Bool = true,
whereSeparator isSeparator: (Element) throws -> Bool
) rethrows -> [SubSequence] {
precondition(maxSplits >= 0, "maxSplits can not be negative")
if isEmpty { return [] }
var subsequences: [SubSequence] = []
var lowerBound = startIndex
func appendAndAdvance(with upperBound: Index) {
let range = lowerBound..<upperBound
if !omittingEmptySubsequences || !range.isEmpty {
subsequences.append(self[range])
lowerBound = upperBound
}
}
while
var upperBound = try self[lowerBound...].firstIndex(where: isSeparator),
subsequences.count < maxSplits {
appendAndAdvance(with: upperBound)
if subsequences.count == maxSplits {
break
}
formIndex(after: &upperBound)
appendAndAdvance(with: upperBound)
}
appendAndAdvance(with: endIndex)
return subsequences
}
}
let string = "*(22+13)/"
let delimiters = Set<Character>("{+-/*()%}.")
let ss = string.splitAndKeep(whereSeparator: delimiters.contains) // ["*", "(", "22", "+", "13", ")", "/"]
let ss1 = string.splitAndKeep(maxSplits: 0, whereSeparator: delimiters.contains) // ["*(22+13)/"]
let ss2 = string.splitAndKeep(maxSplits: 1, whereSeparator: delimiters.contains) // ["*", "(22+13)/"]
let ss3 = string.splitAndKeep(maxSplits: 2, whereSeparator: delimiters.contains) // ["*", "(", "22+13)/"]
let ss4 = string.splitAndKeep(omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*", "", "(", "22", "+", "13", ")", "", "/", ""]["", "*", "", "(", "22", "+", "13", ")", "", "/", ""]
let ss5 = string.splitAndKeep(maxSplits: 0, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["*(22+13)/"]
let ss6 = string.splitAndKeep(maxSplits: 1, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*(22+13)/"]
let ss7 = string.splitAndKeep(maxSplits: 2, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*", "(22+13)/"]
I am trying to make a live data monitoring application. But I have performance issues. I've included a screenshot of the design below for ease of explanation.
I want to show data from serial or ethernet in a textbox in formats like "Hexadecimal", "ASCII", "Decimal", "Binary".
I'm trying to do it in a MVVM-friendly way. I have byte[] variable in ViewModel. I add the incoming data to this variable. I show the byte format in the desired format with Converter in Xaml. Functionally it works but is slow.
ViewModel
public class MainWindowViewModel : ViewModelBase
{
private byte[] _data;
public byte[] Data
{
get => _data;
set => SetProperty(ref _data, value);
}
public MainWindowViewModel()
{
Data = new byte[] { };
}
private void DataClearClick(object param)
{
Array.Clear(Data, 0, Data.Length);
}
}
Xaml
<syncfusion:TabItemExt Header="ASCII">
<syncfusion:SfTextBoxExt Margin="10"
Text="{Binding Data, Converter={StaticResource ByteToASCIIConverter}}"
TextWrapping="Wrap"
AcceptsReturn="True"
MaxLines="70"/>
</syncfusion:TabItemExt>
ASCII Converter
private string StringToAscii(byte[] array)
{
string str = "";
for (int i = 0; i < array.Length; i++)
{
str = str + asciiCharacter[array[i]];
}
return str;
}
string[] asciiCharacter = new string[] { "<NULL>", "<SOH>", "<STX>", "<ETX>", "<EOT>", "<ENQ>", "<ACK>", "<BEL>",
"<BS>", "<HT>", "<LF>", "<VT>", "<FF>", "<CR>", "<SO>", "<SI>",
"<DLE>", "<DC1>", "<DC2>", "<DC3>", "<DC4>", "<NAK>", "<SYN>", "<ETB>",
"<CAN>", "<EM>", "<SUB>", "<ESC>", "<FS>", "<GS>", "<RS>", "<US>",
"<SP>","!", "\"\"", "#", "$", "%", "&", "'",
"(",")", "*", "+", ",", "-", ".", "/",
"0","1", "2", "3", "4", "5", "6", "7",
"8","9", ":", ";", "<", "=", ">", "?",
"#","A", "B", "C", "D", "E", "F", "G",
"H","I", "J", "K", "L", "M", "N", "O",
"P","Q", "R", "S", "T", "U", "V", "W",
"X","Y", "Z", "[", "\"\"", "]", "^", "_",
"`","a", "b", "c", "d", "e", "f", "g",
"h","i", "j", "k", "l", "m", "n", "o",
"p","q", "r", "s", "t", "u", "v", "w",
"x","y", "z", "{", "|", "}", "~", "<DEL>",
"€", " ", "‚", "ƒ", "„", "…", "†", "‡",
"ˆ", "‰", "Š", "‹", "Œ", " ", "Ž", " ",
" ", "‘", "’", "“", "”", "•", "–", "—",
"˜", "™", "š", "›", "œ", " ", "ž", "Ÿ",
" ", "¡", "¢", "£", "¤", "¥", "¦", "§",
"¨", "©", "ª", "«", "¬", " ", "®", "¯",
"°", "±", "²", "³", "´", "µ", "¶", "·",
"¸", "¹", "º", "»", "¼", "½", "¾", "¿",
"À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç",
"È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï",
"Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×",
"Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß",
"à", "á", "â", "ã", "ä", "å", "æ", "ç",
"è", "é", "ê", "ë", "ì", "í", "î", "ï",
"ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷",
"ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ",};
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return StringToAscii((byte[])value);
}
Similar to above, I have binary, decimal and hexadecimal converters. I didn't want to add every single one of them as it would be a lot of code. I know that using a converter is inefficient. I'm not sure how to approach.
In addition, I moved away from MVVM and added the AppendText property with code-behind. This provided a significant performance boost. Still not quite enough. Setting the MaxLine property gave me an increase in performance. I found a few things about UI virtualization. I couldn't figure out how to implement it for textbox.
How can I improve performance with similar approaches? How can I implement AppendText in MVVM?
If I am wrong, please guide me.
Performance level expectations; The fact that it can display the incoming data in 100 ms in 4 separate textboxes.
Thank you.
In general, you should use StringBuilder for performance-sensitive concatenation. Thus your StringToAscii method can be modified as follows.
private string StringToAscii(byte[] array)
{
var buffer = new StringBuilder();
foreach (byte b in array)
{
switch (b)
{
case <= 32 or 34 or 92 or >= 127:
buffer.Append(asciiCharacter[b]);
break;
default:
buffer.Append((char)b);
break;
};
}
return buffer.ToString();
}
I have a scenario where I need to submit parts of a password that is always set to password1.
Where I need to submit it in is called a surephrase and it randomizes which part of your password you need to insert.
Example:
First Login
Second Login
So essentially I would have had to add sw1 on the First Login and sr1 on the second login, and this can change to whichever combinations the system chooses.
So on Vugen I have been able to see that which field is required, and an ID is described to that input field as example below:
Action.c(82): Notify: Saving Parameter "Piece1 = field1". (p)
Action.c(82): Notify: Saving Parameter "Piece2 = field2". (a)
Action.c(82): Notify: Saving Parameter "Piece3 = field3". (s)
Action.c(82): Notify: Saving Parameter "Piece4 = field4". (s)
Action.c(82): Notify: Saving Parameter "Piece5 = a20b7612". (w)
Action.c(82): Notify: Saving Parameter "Piece6 = a8ea1d7e". (o)
Action.c(82): Notify: Saving Parameter "Piece7 = field7". (r)
Action.c(82): Notify: Saving Parameter "Piece8 = field8". (d)
Action.c(82): Notify: Saving Parameter "Piece9 = B3ad5546c".(1)
And when you submit those required fields it looks like below:
"Body=a20b7612=w&a8ea1d7e=o&B3ad5546c=1&landingpage
What I would like to wrap my head around is, is there a way to assign each letter of the password to each piecex value, (the value in the bracket) and then once the piecex does not equal fieldx and one of those random ID's let it submit that value in the bracket in the Body= ?
Any advise would be appreciated at this point.
Thanks
I did this:
List<string> Array2 = Get<List<string>>("c_surephraseIDs_41");
List<string> Array1 = new List<string> { "p", "a", "s", "s", "w", "o", "r", "d", "1" };
List<string> Array3 = new List<string> { "", "", "", "", "", "", "", "", "" };
int j = 0;
for (int i = 0; i < 9; i++) {
if (Array2[i].Length > 6) {
Array3[j] = Array2[i] + "=" + Array1[i];
if (j == 0) {
Set<string>("PPleft1", Array2[i]);
Set<string>("PPright1", Array1[i]);
}
if (j == 1) {
Set<string>("PPleft2", Array2[i]);
Set<string>("PPright2", Array1[i]);
}
if (j == 2) {
Set<string>("PPleft3", Array2[i]);
Set<string>("PPright3", Array1[i]);
}
//WriteMessage(Array3[j]);
j++;
}
}
This question already has answers here:
Adding the word "and" before the final item of an array in Swift
(6 answers)
Closed 4 years ago.
I have an array of string like this
let arrayOfString = ["Nancy", "Peter", "Kevin"]
and I want to join all array elements together with a comma and a space separating in between.
let joinResult = arrayOfString.joined(separator: ", ")
which gives me
Nancy, Peter, Kevin
my question is if it is possible to add the word "and" before the last element if array.count > 2. In this case, it will be
Nancy, Peter, and Kevin
//if arrayOfString = ["Nancy"], only shows Nancy with no comma and space
Thanks!
Here is a solution
let arrayOfString = ["Nancy", "Peter", "Kevin"]
let joinedString = arrayOfString.dropLast().joined(separator: ", ")
+ (arrayOfString.count > 1 ? " and " : "")
+ (arrayOfString.last ?? "")
print(joinedString) //Nancy, Peter and Kevin
You could also define the above as a function
func joinedNames(from array: [String]) -> String {
return array.dropLast().joined(separator: ", ")
+ (array.count > 1 ? " and " : "")
+ (array.last ?? "")
}
and test it like so:
joinedNames(from: ["Nancy", "Peter", "Kevin"]) //"Nancy, Peter and Kevin"
joinedNames(from: ["Nancy", "Peter"]) //"Nancy and Peter"
joinedNames(from: ["Nancy"]) //"Nancy"
joinedNames(from: []) //""
If you need the Oxford comma here is the solution:
func joinedNamesWithOxfordComma(from array: [String]) -> String {
return array
.enumerated()
.map {(index, element) in
if index == 0 {
return element
} else if index <= array.count - 2 {
return ", " + element
} else if array.count > 2 {
return ", and " + element
} else {
return " and " + element
}
}.joined()
}
joinedNamesWithOxfordComma(from: ["Nancy", "Peter", "Kevin"]) //"Nancy, Peter, and Kevin"
joinedNamesWithOxfordComma(from: ["Nancy", "Peter"]) //"Nancy and Peter"
joinedNamesWithOxfordComma(from: ["Nancy"]) //"Nancy"
joinedNamesWithOxfordComma(from: []) //""
Here's a safe approach
let names = ["Nancy", "Peter", "Kevin"]
func join(names: [String], separator: String, lastSeparator: String) -> String? {
guard !names.isEmpty else { return nil }
guard names.count > 1 else { return names.first }
return names
.dropLast()
.joined(separator: separator) + " " + lastSeparator + " " + names.last!
}
join(names: names, separator: ",", lastSeparator: "and")
Result
"Nancy,Peter and Kevin"