This question already has answers here:
Cocoa method to return a list of all letters in the current locale's alphabet?
(3 answers)
Closed 1 year ago.
I’m translating my app to different languages, how could I get the alphabet based on the localization? Could be Latin, Cyrillic, etc.
Try this:
import UIKit // Or Foundation
if let alphabetCharacterSet = Locale(identifier: "ru").exemplarCharacterSet?.intersection(CharacterSet.lowercaseLetters) {
print(alphabetCharacterSet.characters().sorted(by: {String($0).localizedCompare(String($1)) == .orderedAscending }))
}
// If you don't sort alphabetical order is not guaranteed.
extension CharacterSet {
func characters() -> [Character] {
// A Unicode scalar is any Unicode code point in the range U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive.
return codePoints().compactMap { UnicodeScalar($0) }.map { Character($0) }
}
func codePoints() -> [Int] {
var result: [Int] = []
var plane = 0
// following documentation at https://developer.apple.com/documentation/foundation/nscharacterset/1417719-bitmaprepresentation
for (i, w) in bitmapRepresentation.enumerated() {
let k = i % 0x2001
if k == 0x2000 {
// plane index byte
plane = Int(w) << 13
continue
}
let base = (plane + k) << 3
for j in 0 ..< 8 where w & 1 << j != 0 {
result.append(base + j)
}
}
return result
}
}
You have to provide Localizable.strings base file to you project, which consist of your different text for different origins:
Like
for default language:
"Hello World!" = "Hello World!";
and for like in Latin language:
"Hello World!" = "salve mundi!";
Related
Eg:
HelloWorld - repeated characters are 5 (l is repeating 3 times and o is repeating 2 times)
Smart2000, repeated characters = 3?(0 is repeating 3 times)
Smart#200#12, repeated characters = 6
I tried with iterating over string
Here is my code with string iteration to find out the repeated character in string.
func countRepeatDigitsIn(keyword : String) -> Int
{
// To keep track of processed symbols
var uniqueCharacters = ""
var repeatCharacterCount = 0
for char in keyword.uppercased() {
let alphabet = String(char)
// If this is already counted, skip it
if (uniqueCharacters.contains(alphabet))
{
repeatCharacterCount += 1
}
// Otherwise, add it to processed symbols
uniqueCharacters += alphabet
}
return repeatCharacterCount
}
HelloWorld - repeated characters are 5 (l is repeating 3 times and o is repeating 2 times
The simplest way to get that result is to take a histogram and then add up all the values that are not 1.
Example:
func histogram(_ s:String) -> [Character:Int] {
var d = [Character:Int]()
for c in s {
d[c, default:0] += 1
}
return d
}
let reps = histogram("helloworld").values.filter{$0 > 1}.reduce(0, +) // 5
let reps2 = histogram("smart2000").values.filter{$0 > 1}.reduce(0, +) // 3
let reps3 = histogram("Smart#200#12").values.filter{$0 > 1}.reduce(0, +) // 6
Here's a fun chain of reductions and filters on the characters of the string.
func countRepeatDigitsIn(keyword : String) -> Int {
let total = Array(keyword.uppercased()).reduce(into: [Character : Int]()) { $0[$1, default: 0] += 1 }.filter { $0.value > 1 }.reduce(0) { $0 + $1.value }
return total
}
for text in ["HelloWorld", "Smart2000", "Smart#200#12"] {
print(text, countRepeatDigitsIn(keyword: text))
}
The first reduce builds a dictionary where the keys are characters and the values is the count for the character. Then the filter removes characters only found once. The second reduce adds the remaining counts.
Group same characters in a dictionary using Dictionary(grouping:by:) and add the counts of values where the count is greater than 1
extension String {
func repeatCount() -> Int {
return Dictionary(grouping: lowercased()) { $0 }.values.filter { $0.count > 1 }.reduce(0) { $0 + $1.count }
}
}
print("HelloWorLd".repeatCount())//5
print("Smart2000".repeatCount())//3
print("Smart#200#12".repeatCount())//6
This question already has answers here:
Replace multiple words from a String based on the values in an Array
(4 answers)
Closed 3 years ago.
I'd like to find and replace any word(s) in a string that match any string value in an array. I can find the matching value using .contains(where:
var playersApprovedArray = ["Monica","Zach","Chrissy"]
card.cardText = "Chrissy, do 10 jumping jacks right now!"
if playersApprovedArray.contains(where: card.cardText.contains) {
print("Found matching player in card text")
// Replace the matching word/player here with another word/player
}
But I don't know how to replace the occurrence of the matching word with another string value in the array.
Here is a simple solution that replaces one player with another:
for i in playersApprovedArray.indices {
let player = playersApprovedArray[i]
if let range = cardText.range(of: player) {
let otherIndex = (i + Int.random(in: 1..<playersApprovedArray.count)) % playersApprovedArray.count
let otherPlayer = playersApprovedArray[otherIndex]
cardText.replaceSubrange(range, with: otherPlayer)
break
}
}
You can remove the break if the sentence may contains more players.
Here is a faster solution that would replace all occurrences of any player's name by another:
let playersApprovedArray = ["Monica","Zach","Chrissy"]
let cardText = " Chrissy, do 10 jumping jacks right now! "
var result = ""
var i = cardText.startIndex
while i < cardText.endIndex {
var j = i
while j < cardText.endIndex,
CharacterSet.letters.inverted.isSuperset(of: CharacterSet(charactersIn: String(cardText[j])))
{
j = cardText.index(after: j)
}
var tempo1 = ""
if i != j { tempo1 += cardText[i..<j] }
if j < cardText.endIndex { i = j } else {
result += tempo1
break
}
while j < cardText.endIndex,
CharacterSet.letters.isSuperset(of: CharacterSet(charactersIn: String(cardText[j])))
{
j = cardText.index(after: j)
}
let tempo2 = String(cardText[i..<j])
if let index = playersApprovedArray.firstIndex(of: tempo2) {
let otherIndex = (index + Int.random(in: 1..<playersApprovedArray.count)) % playersApprovedArray.count
let otherPlayer = playersApprovedArray[otherIndex]
result += tempo1 + otherPlayer
//See comment below for possible early exit
} else {
result += tempo1 + tempo2
}
i = j
}
print(result) // Monica, do 10 jumping jacks right now!
If you're sure that there is only one player in the string, then you can exit early this way:
...
result += tempo1 + otherPlayer + cardText[j...]
break
...
This question already has answers here:
Refactored Solution In Swift
(2 answers)
Closed 6 years ago.
I'm attempting to solve HackerRank's Hash Table Ransom Note challenge. There are 19 test cases and I'm passing all but two of time due to timeout on larger data sets (10,000-30,000 entries).
I'm given:
1) an array of words contained in a magazine and
2) an array of words for a ransom note. My objective is to determine if the words in the magazine can be used to construct a ransom note.
I need to have enough unique elements in the magazineWords to satisfy the quantity needed by noteWords.
I'm using this code to make that determination...and it takes FOREVER...
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
What is a faster way to accomplish this task?
Below is my complete code for the challenge:
import Foundation
var magazineWords = // Array of 1 to 30,000 strings
var noteWords = // Array of 1 to 30,000 strings
enum RegexString: String {
// Letters a to z, A to Z, 1 to 5 characters long
case wordCanBeUsed = "([a-zA-Z]{1,5})"
}
func matches(for regexString: String, in text: String) -> [String] {
// Hat tip MartinR for this
do {
let regex = try NSRegularExpression(pattern: regexString)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
func canCreateRansomNote(from magazineWords: [String], for noteWords: [String]) -> String {
// figure out what's unique
let magazineWordsSet = Set(magazineWords)
let noteWordsSet = Set(noteWords)
let intersectingValuesSet = magazineWordsSet.intersection(noteWordsSet)
// constraints specified in challenge
guard magazineWords.count >= 1, noteWords.count >= 1 else { return "No" }
guard magazineWords.count <= 30000, noteWords.count <= 30000 else { return "No" }
// make sure there are enough individual words to work with
guard magazineWordsSet.count >= noteWordsSet.count else { return "No" }
guard intersectingValuesSet.count == noteWordsSet.count else { return "No" }
// check if all the words can be used. assume the regex method works perfectly
guard noteWords.count == matches(for: RegexString.wordCanBeUsed.rawValue, in: noteWords.joined(separator: " ")).count else { return "No" }
// FIXME: this is a processor hog. I'm timing out when I get to this point
// need to make sure there are enough magazine words to write the note
// compare quantity of word in magazine with quantity of word in note
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
return "Yes"
}
print(canCreateRansomNote(from: magazineWords, for: noteWords))
I don't know how to read from the test case on the contest website or what frameworks you are allowed. If Foundation is allowed, you can use NSCountedSet
import Foundation
let fileContent = try! String(contentsOf: URL(fileURLWithPath: "/path/to/file.txt"))
let scanner = Scanner(string: fileContent)
var m = 0
var n = 0
scanner.scanInt(&m)
scanner.scanInt(&n)
var magazineWords = NSCountedSet(capacity: m)
var ransomWords = NSCountedSet(capacity: n)
for i in 0..<(m+n) {
var word: NSString? = nil
scanner.scanUpToCharacters(from: .whitespacesAndNewlines, into: &word)
if i < m {
magazineWords.add(word!)
} else {
ransomWords.add(word!)
}
}
var canCreate = true
for w in ransomWords {
if ransomWords.count(for: w) > magazineWords.count(for: w) {
canCreate = false
break
}
}
print(canCreate ? "Yes" : "No")
It works by going through the input file one word at a time, counting how many times that word appears in the magazine and in the ransom note. Then if any word appear more frequently in the ransom note than in the magazine, it fails the test immediately. Run the 30,000 words test case in less than 1 second on my iMac 2012.
I have a string called source. This string contains tags, marked with number signs (#) on left and right side.
What is the most efficient way to get tag names from the source string.
Source string:
let source = "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##."
Expected result:
["TAG_1", "TAG_2"]
Not a very short solution, but here you go:
let tags = source.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: " ,."))
.filter { (str) -> Bool in
return str.hasSuffix("##") && str.hasPrefix("##")
}
.map { (str) -> String in
return str.stringByReplacingOccurrencesOfString("##", withString: "")
}
Split the string at all occurences of ##:
let components = source.components(separatedBy: "##")
// Result: ["Here is tag 1: ", "TAG_1", ", tag 2: ", "TAG_2", "."]
Check that there's an odd number of components, otherwise there's an odd amount of ##s:
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
Get every second element:
components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
In a single function:
import Foundation
func getTags(source: String, delimiter: String = "##") -> [String] {
let components = source.components(separatedBy: delimiter)
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
return components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
}
getTags(source: "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##.") // ["TAG_1", "TAG_2"]
You can read this post and adapt the answer for your needs: Swift: Split a String into an array
If not you can also create your own method, remember a string is an array of characters, so you can use a loop to iterate through and check for a '#'
let strLength = source.characters.count;
var strEmpty = "";
for( var i=0; i < strLength; i++ )
{
if( source[ i ] == '#' )
{
var j=(i+2);
for( j; source[ (i+j) ] != '#'; j++ )
strEmpty += source[ (i+j) ]; // concatenate the characters to another variable using the += operator
i = j+2;
// do what you need to with the tag
}
}
I am more of a C++ programmer than a Swift programmer, so this is how I would approach it if I didn't want to use standard methods. There may be a better way of doing it, but I don't have any Swift knowledge.
Keep in mind if this does not compile then you may have to adapt the code slightly as I do not have a development environment I can test this in before posting.
I'm looking for an existing swift2 function to split string input on whitespace while at the same time preserving whitespace within quoted strings.
I have read stack overflow question 25678373. My question does not appear to be a duplicate.
I searched for similar functionality in cocoapods. I did not find it.
If this shlex.split function does not exist in swift2, what is an effective alternate way to accomplish something similar? What is an alternate way to split a string while preserving whitespace within internal quoted strings?
Here's an example of what I mean in python:
$ python
Python 2.7.6 (default, Jun 22 2015, 18:00:18)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import shlex
>>> input=""" alpha 2 'chicken with teeth' 4 'cat with wings' 6 turkey"""
>>> results = shlex.split(input)
>>> type(results)
<type 'list'>
>>> results[0]
'alpha'
>>> results[2]
'chicken with teeth'
>>> for term in results:
... print(term)
...
alpha
2
chicken with teeth
4
cat with wings
6
turkey
>>>
As #EricD writes in his comment to you, there exists no such native Swift function. You can, however, quite readily write your own such split function, e.g.
extension String {
func shlexSplit() -> [String] {
/* separate words by spaces */
var bar = self.componentsSeparatedByString(" ")
/* identify array idx ranges of quoted (') sets of words */
var accumulating = false
var from = 0
var joinSegments : [(Int, Int)] = []
for (i,str) in bar.enumerate() {
if str.containsString("'") {
if accumulating { joinSegments.append((from, i)) }
else { from = i }
accumulating = !accumulating
}
}
/* join matching word ranges with " " */
for (from, through) in joinSegments.reverse() {
bar.replaceRange(from...through,
with: [bar[from...through].joinWithSeparator(" ")])
}
return bar
}
}
Usage example
/* exampe usage */
let foo = "alpha 2 'chicken with teeth' 4 'cat with wings' 6 turkey"
let bar = foo.shlexSplit()
bar.forEach{ print($0) }
/* alpha
2
'chicken with teeth'
4
'cat with wings'
6
turkey */
Note that the above assumes the input string have matching sets of quote delimiters '.
'pure' swift (no Foundation) example
extension String {
// split by first incidence of character
func split(c: Character)->(String,String) {
var head: String = "", tail: String = ""
if let i = characters.indexOf(c) {
let j = startIndex.distanceTo(i)
head = String(characters.prefix(j))
tail = String(characters.dropFirst(j + 1))
} else {
head = self
}
return (head, tail)
}
}
// what you are looking for
func split(str: String)->[String] {
// internal state
var state:((String,String), [String], Bool) = (str.split("'"), [], false)
repeat {
if !state.2 {
// you can define more whitespace characters
state.1
.appendContentsOf(state.0.0.characters.split{" \t\n\r".characters.contains($0)}
.map(String.init))
state.2 = true
} else {
state.1.append(state.0.0)
state.2 = false
}
state.0 = state.0.1.split("'")
} while !state.0.0.isEmpty
return state.1
}
Usage
let str = "a 2 'b c' d ''"
dump(split(str))
/*
▿ 4 elements
- [0]: a
- [1]: 2
- [2]: b c
- [3]: d
*/