Part of my project involves comparing 4 people's scores, finding the lowest, and just outputting the name of that player. But now I need it to support multiple names, which I think I've successfully done but it outputs the entire Struct of those players. For example:
["AppName.Player(name: "Bob", score: 2), AppName.Player(name: "John", score: 2)]
while I just want ["Bob", "John"] or preferably just "Bob and John".
Heres the code, where winnerText controls what's displayed:
let players = [Player(name: "Bob", score: bobTotal), Player(name: "Ted", score: tedTotal), Player(name: "John", score: johnTotal), Player(name: "Rick", score: rickTotal)]
let minValue = players.min(by: { $0.score < $1.score })?.score ?? 0
let PlayersWithMinScore = players.filter { $0.score == minValue }
print(PlayersWithMinScore)
let winningPlayerIndex = players.indices.filter { players[$0].score == minValue }
print(winningPlayerIndex)
self.winnerText = "" + " won with " + "\(minValue)"
struct Player {
let name: String
let score: Int
}
PlayersWithMinScore.map { $0.name }
The above will give you an array of just the names.
Borrowing a trick from another answer (Join string array with separator ", " and add ", and " to join the last element in Swift):
extension BidirectionalCollection where Element: StringProtocol {
var sentence: String {
count <= 2 ?
joined(separator: " and ") :
dropLast().joined(separator: ", ") + ", and " + last!
}
}
You could then do:
PlayersWithMinScore.map { $0.name }.sentence
Giving you the names comma-separated and then last one with "and" before it (e.g. "Bob and John" or "Bob, Tom, and "John").
A side note: in Swift, variable names usually start with a lowercase name -- playersWithMinScore would be more idiomatic than PlayersWithMinScore
Related
Example code:
var reStr = `"(?:\\"|[^"])*"`
var reStrSum = regexp.MustCompile(`(?m)(` + reStr + `)\s*\+\s*(` + reStr + `)\s*\+\s*(` + reStr + `)`)
var str = `"This\nis\ta\\string" +
"Another\"string" +
"Third string"
`
for i, match := range reStrSum.FindAllStringSubmatch(str, -1) {
fmt.Println(match, "found at index", i)
for i, str := range match {
fmt.Println(i, str)
}
}
Output:
["This\nis\ta\\string" +
"Another\"string" +
"Third string" "This\nis\ta\\string" "Another\"string" "Third string"] found at index 0
0 "This\nis\ta\\string" +
"Another\"string" +
"Third string"
1 "This\nis\ta\\string"
2 "Another\"string"
3 "Third string"
E.g. it matches the "sum of strings" and it captures all three strings correctly.
My problem is that I do not want to match the sum of exactly three strings. I want to match all "sum of strings" where the sum can consist of one or more string literals. I have tried to express this with {0,}
var reStr = `"(?:\\"|[^"])*"`
var reStrSum = regexp.MustCompile(`(?m)(` + reStr + `)` + `(?:\s*\+\s*(` + reStr + `)){0,}`)
var str = `
test1("This\nis\ta\\string" +
"Another\"string" +
"Third string summed");
test2("Second string " + "sum");
`
for i, match := range reStrSum.FindAllStringSubmatch(str, -1) {
fmt.Println(match, "found at index", i)
for i, str := range match {
fmt.Println(i, str)
}
}
`)){0,}`)
then I get this result:
["This\nis\ta\\string" +
"Another\"string" +
"Third string summed" "This\nis\ta\\string" "Third string summed"] found at index 0
0 "This\nis\ta\\string" +
"Another\"string" +
"Third string summed"
1 "This\nis\ta\\string"
2 "Third string summed"
["Second string " + "sum" "Second string " "sum"] found at index 1
0 "Second string " + "sum"
1 "Second string "
2 "sum"
Group 0 of the first match contains all three strings (the regexp matches correctly), but there are only two capturing groups in the expression, and the second group only contains the last iteration of the repetition. E.g. "Another\"string" is lost in the process, it cannot be accessed.
Would it be possible to get all iterations of (all repetitions) inside group 2 somehow?
I would also accept any workaround that uses nested loops. But please be aware that I cannot simply replace the {0,} repetition with an outer FindAllStringSubmatch call, because the FindAllStringSubmatch call is already used for iterating over "sums of strings". In other words, I must find the first string sum and also the "Second string sum".
I just found a workaround that will work. I can do two passes. In the first pass, I just match all string literals, and replace them with unique placeholders in the original text. Then the transformed text won't contain any strings, and it becomes much easier to do further processing on it in a second pass.
Something like this:
type javaString struct {
value string
lineno int
}
// First we find all string literals
var placeholder = "JSTR"
var reJavaStringLiteral = regexp.MustCompile(`(?m)("(?:\\"|[^"])*")`)
javaStringLiterals := make([]javaString, 0)
for pos, strMatch := range reJavaStringLiteral.FindAllStringSubmatch(strContent, -1) {
pos = strings.Index(strContent, strMatch[0])
head := strContent[0:pos]
lineno := strings.Count(head, "\n") + 1
javaStringLiterals = append(javaStringLiterals, javaString{value: strMatch[1], lineno: lineno})
}
// Next, we replace all string literals with placeholders.
for i, jstr := range javaStringLiterals {
strContent = strings.Replace(strContent, jstr.value, fmt.Sprintf("%v(%v)", placeholder, i), 1)
}
// Now the transformed text does not contain any string literals.
After the first pass, the original text becomes:
test1(JSTR(1) +
JSTR(2) +
JSTR(3));
test2(JSTR(3) + JSTR(4));
After this step, I can easily look for "JSTR(\d+) + JSTR(\d+) + JSTR(\d+)..." expressions. Now they are easy to find, because the text does not contain any strings (that could otherwise contain practically anything and interfere with regular expressions). These "sum of string" matches can then be re-matched with another FindAllStringSubmatch (in an inner loop) and then I'll get all information that I needed.
This is not a real solution, because it requires writting a lot of code, it is specific to my concrete use case, and does not really answer the original question: allow access to all iterations inside a repeated capturing group.
But the general idea of the workaround might be benefical for somebody who is facing a similar problem.
I have a string "323 ECO Economics Course 451 ENG English Course 789 Mathematical Topography" I want to split this string using the regex expression [0-9][0-9][0-9][A-Z][A-Z][A-Z] so that the function returns the array:
Array =
["323 ECO Economics Course ", "451 ENG English Course", "789 Mathematical Topography"]
How would I go about doing this using swift?
Edit
My question is different than the one linked to. I realize that you can split a string in swift using myString.components(separatedBy: "splitting string") The issue is that that question doesn't address how to make the splitting string a regex expression. I tried using mystring.components(separatedBy: "[0-9][0-9][0-9][A-Z][A-Z][A-Z]", options: .regularExpression) but that didn't work.
How can I make the separatedBy: portion a regular expression?
You can use regex "\\b[0-9]{1,}[a-zA-Z ]{1,}" and this extension from this answer to get all ranges of a string using literal, caseInsensitive or regularExpression search:
extension StringProtocol {
func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] {
var result: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...].range(of: string, options: options) {
result.append(range)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
}
let inputString = "323 ECO Economics Course 451 ENG English Course 789 Mathematical Topography"
let courses = inputString.ranges(of: "\\b[0-9]{1,}[a-zA-Z ]{1,}", options: .regularExpression).map { inputString[$0].trimmingCharacters(in: .whitespaces) }
print(courses) // ["323 ECO Economics Course", "451 ENG English Course", "789 Mathematical Topography"]
Swift doesn't have native regular expressions as of yet. But Foundation provides NSRegularExpression.
import Foundation
let toSearch = "323 ECO Economics Course 451 ENG English Course 789 MAT Mathematical Topography"
let pattern = "[0-9]{3} [A-Z]{3}"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
// NSRegularExpression works with objective-c NSString, which are utf16 encoded
let matches = regex.matches(in: toSearch, range: NSMakeRange(0, toSearch.utf16.count))
// the combination of zip, dropFirst and map to optional here is a trick
// to be able to map on [(result1, result2), (result2, result3), (result3, nil)]
let results = zip(matches, matches.dropFirst().map { Optional.some($0) } + [nil]).map { current, next -> String in
let range = current.rangeAt(0)
let start = String.UTF16Index(range.location)
// if there's a next, use it's starting location as the ending of our match
// otherwise, go to the end of the searched string
let end = next.map { $0.rangeAt(0) }.map { String.UTF16Index($0.location) } ?? String.UTF16Index(toSearch.utf16.count)
return String(toSearch.utf16[start..<end])!
}
dump(results)
Running this will output
▿ 3 elements
- "323 ECO Economics Course "
- "451 ENG English Course "
- "789 MAT Mathematical Topography"
I needed something like this and should work more like JS String.prototype.split(pat: RegExp) or Rust's String.splitn(pat: Pattern<'a>) but with Regex. I ended up with this
extension NSRegularExpression {
convenience init(_ pattern: String) {...}
/// An array of substring of the given string, separated by this regular expression, restricted to returning at most n items.
/// If n substrings are returned, the last substring (the nth substring) will contain the remainder of the string.
/// - Parameter str: String to be matched
/// - Parameter n: If `n` is specified and n != -1, it will be split into n elements else split into all occurences of this pattern
func splitn(_ str: String, _ n: Int = -1) -> [String] {
let range = NSRange(location: 0, length: str.utf8.count)
let matches = self.matches(in: str, range: range);
var result = [String]()
if (n != -1 && n < 2) || matches.isEmpty { return [str] }
if let first = matches.first?.range {
if first.location == 0 { result.append("") }
if first.location != 0 {
let _range = NSRange(location: 0, length: first.location)
result.append(String(str[Range(_range, in: str)!]))
}
}
for (cur, next) in zip(matches, matches[1...]) {
let loc = cur.range.location + cur.range.length
if n != -1 && result.count + 1 == n {
let _range = NSRange(location: loc, length: str.utf8.count - loc)
result.append(String(str[Range(_range, in: str)!]))
return result
}
let len = next.range.location - loc
let _range = NSRange(location: loc, length: len)
result.append(String(str[Range(_range, in: str)!]))
}
if let last = matches.last?.range, !(n != -1 && result.count >= n) {
let lastIndex = last.length + last.location
if lastIndex == str.utf8.count { result.append("") }
if lastIndex < str.utf8.count {
let _range = NSRange(location: lastIndex, length: str.utf8.count - lastIndex)
result.append(String(str[Range(_range, in: str)!]))
}
}
return result;
}
}
Passes the following tests
func testRegexSplit() {
XCTAssertEqual(NSRegularExpression("\\s*[.]\\s+").splitn("My . Love"), ["My", "Love"])
XCTAssertEqual(NSRegularExpression("\\s*[.]\\s+").splitn("My . Love . "), ["My", "Love", ""])
XCTAssertEqual(NSRegularExpression("\\s*[.]\\s+").splitn(" . My . Love"), ["", "My", "Love"])
XCTAssertEqual(NSRegularExpression("\\s*[.]\\s+").splitn(" . My . Love . "), ["", "My", "Love", ""])
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX"), ["", "My", "", "Love", ""])
}
func testRegexSplitWithN() {
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX", 1), ["xXMyxXxXLovexX"])
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX", -1), ["", "My", "", "Love", ""])
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX", 2), ["", "MyxXxXLovexX"])
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX", 3), ["", "My", "xXLovexX"])
XCTAssertEqual(NSRegularExpression("xX").splitn("xXMyxXxXLovexX", 4), ["", "My", "", "LovexX"])
}
func testNoMatches() {
XCTAssertEqual(NSRegularExpression("xX").splitn("MyLove", 1), ["MyLove"])
XCTAssertEqual(NSRegularExpression("xX").splitn("MyLove"), ["MyLove"])
XCTAssertEqual(NSRegularExpression("xX").splitn("MyLove", 3), ["MyLove"])
}
Update to #tomahh answer for latest Swift (5).
import Foundation
let toSearch = "323 ECO Economics Course 451 ENG English Course 789 MAT Mathematical Topography"
let pattern = "[0-9]{3} [A-Z]{3}"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: toSearch, range: NSRange(toSearch.startIndex..<toSearch.endIndex, in: toSearch))
// the combination of zip, dropFirst and map to optional here is a trick
// to be able to map on [(result1, result2), (result2, result3), (result3, nil)]
let results = zip(matches, matches.dropFirst().map { Optional.some($0) } + [nil]).map { current, next -> String in
let start = toSearch.index(toSearch.startIndex, offsetBy: current.range.lowerBound)
let end = next.map(\.range).map { toSearch.index(toSearch.startIndex, offsetBy: $0.lowerBound) } ?? toSearch.endIndex
return String(toSearch[start..<end])
}
dump(results)
▿ 3 elements
- "323 ECO Economics Course "
- "451 ENG English Course "
- "789 MAT Mathematical Topography"
I am looking for regular expression by which I can ignore strings which is only combination of All special charters.
Example
List<string> liststr = new List<string>() { "a b", "c%d", " ", "% % % %" ,"''","&","''","'"}; etc...
I need result of this one
{ "a b", "c%d"}
You can use this, too, to match string without any Unicode letter:
var liststr = new List<string>() { "a b", "c%d", " ", "% % % %", "''", "&", "''", "'" };
var rx2 = #"^\P{L}+$";
var res2 = liststr.Where(p => !Regex.IsMatch(p, rx2)).ToList();
Output:
I also suggest creating the regex object as a private static readonly field, with Compiled option, so that performance is not impacted.
private static readonly Regex rx2 = new Regex(#"^\P{L}+", RegexOptions.Compiled);
... (and inside the caller)
var res2 = liststr.Where(p => !rx2.IsMatch(p)).ToList();
Use this one :
.*[A-Za-z0-9].*
It matches at least one alphanumeric character. Doing this, it will take any string that is not only symbols/special chars. It does the output you want, see here : demo
You can use a very simple regex like
Regex regex = new Regex(#"^[% &']+$");
Where
[% &'] Is the list of special characters that you wish to include
Example
List<string> liststr = new List<string>() { "a b", "c%d", " ", "% % % %" ,"''","&","''","'"};
List<string> final = new List<string>();
Regex regex = new Regex(#"^[% &']+$");
foreach ( string str in liststr)
{
if (! regex.IsMatch(str))
final.Add(str);
}
Will give an output as
final = {"a b", "c%d"}
Using JavaScript & regex I want to split a string on every %20 that is not within quotes, example:
Here%20is%20"a%20statement%20"%20for%20Testing%20"%20The%20Values%20"
//easy to read version: Here is "a statement " for Testing " The Values "
______________ ______________
would return
{"Here","is","a statement ","for","Testing"," The Values "}
but it seems my regex are no longer strong enough to build the expression. Thanks for any help!
A way using the replace method, but without using the replacement result. The idea is to use a closure to fill the result variable at each occurence:
var txt = 'Here%20is%20"a%20statement%20"%20for%20Testing%20"%20The%20Values%20"';
var result = Array();
txt.replace(/%20/g, ' ').replace(/"([^"]+)"|\S+/g, function (m,g1) {
result.push( (g1==undefined)? m : g1); });
console.log(result);
Just try with:
var input = 'Here%20is%20"a%20statement%20"%20for%20Testing%20"%20The%20Values%20"',
tmp = input.replace(/%20/g, ' ').split('"'),
output = []
;
for (var i = 0; i < tmp.length; i++) {
var part = tmp[i].trim();
if (!part) continue;
if (i % 2 == 0) {
output = output.concat(part.split(' '));
} else {
output.push(part);
}
}
Output:
["Here", "is", "a statement", "for", "Testing", "The Values"]
I try to find a specific String in a Text File.
The file looks like this:
2, 1, 'Ausbau der techn. Anlagen'
2, 2, 'Extension des installations techniques'
2, 3, 'Estensione delle istallazioni tecniche'
I try to find the text between the '' signs.
//Will be set automaticly after implementation.
int project = 2
int languageInteger = 1
String findings = new File(usedPath)?.eachLine {
it.substring((project+ ", " + languageInteger + ", "))
}
This doesn't work. I've also tried with FindAll Closure or find. But I make some mistakes.
What should I do to find the text?
I found a Solution.
It will not be the best, but it works.
new File(usedPath)?.eachLine {
if(it?.toString()?.startsWith(project+ ", " + languageInteger + ", ")){
findings = it?.toString()?.split(project+ ", " + languageInteger + ", ")?.getAt(1)
}
}