Swift - best practice to find the longest string at [String] array - string

I'm trying to find what is the most effective way to get the longest string in a string array. For example :
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
and the outcome will be - "Game Of Thrones is just good"
I've tried using the maxElement func, tho it's give the max string in a alphabetic ideas(maxElement()).
Any suggestions? Thanks!

Instead of sorting which is O(n log(n)) for a good sort, use max(by:) which is O(n) on Array providing it a closure to compare string lengths:
Swift 4:
For Swift 4 you can get the string length with the count property on String:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.max(by: {$1.count > $0.count}) {
print(max)
}
Swift 3:
Use .characters.count on String to get the string lengths:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.max(by: {$1.characters.count > $0.characters.count}) {
print(max)
}
Swift 2:
Use maxElement on Array providing it a closure to compare string lengths:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let max = array.maxElement({$1.characters.count > $0.characters.count}) {
print(max)
}
Note: maxElement is O(n). A good sort is O(n log(n)), so for large arrays, this will be much faster than sorting.

You can use reduce to do this. It will iterate through your array, keeping track of the current longest string, and then return it when finished.
For example:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
if let longestString = array.reduce(Optional<String>.None, combine:{$0?.characters.count > $1.characters.count ? $0:$1}) {
print(longestString) // "Game Of Thrones is just good"
}
(Note that Optional.None is now Optional.none in Swift 3)
This uses an nil starting value to account for the fact that the array could be empty, as pointed out by #JHZ (it will return nil in that case). If you know your array has at least one element, you can simplify it to:
let longestString = array.reduce("") {$0.characters.count > $1.characters.count ? $0:$1}
Because it only iterates through each element once, it will quicker than using sort(). I did a quick benchmark and sort() appears around 20x slower (although no point in premature optimisation, I feel it is worth mentioning).
Edit: I recommend you go with #vacawama's solution as it's even cleaner than reduce!

Here you go:
let array = ["I'm Roi","I'm asking here","Game Of Thrones is just good"]
var sortedArr = array.sort() { $0.characters.count > $1.characters.count }
let longestEelement = sortedArr[0]

You can also practice with the use of Generics by creating this function:
func longestString<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == String{
return (stringsArray.max {$0.count < $1.count}) ?? ""
}
Explanation: Create a function named longestString. Declar that there is a generic type T that implements the Sequence protocol (Sequence is defined here: https://developer.apple.com/documentation/swift/sequence). The function will return a single String (of course, the longest). The where clause explains that the generic type T should be limited to having elements of type String.
Inside the function, call the max function of the stringsArray by comparing the longest string of the elements inside. What will be returned is the longest String (an optional as it can be nil if the array is empty). If the longest string is nil then (use of ??) returns an empty string as the longest string instead.
Now call it:
let longestA = longestString(from:["Shekinah", "Chesedh", "Agape Sophia"])
If you get the hang of using generics, even if the strings are hidden inside objects, you can make use of the pattern of coding above. You can change the element to objects of the same class (Person for example).
Thus:
class Person {
let name: String
init(name: String){
self.name = name
}
}
func longestName<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == Person{
return (stringsArray.max {$0.name.count < $1.name.count})?.name ?? ""
}
Then call the function like these:
let longestB = longestName(from:[Person(name: "Shekinah"), Person(name: "Chesedh"), Person(name: "Agape Sophia")])
You also get to rename your function based on the appropriateness of its use. You can tweak the pattern to return something else, like the object itself, or the length (count) of the String. And finally, becoming familiar with generics may improve your coding ability.
Now, with a little tweak again, you may extend further so that you can compare strings owned by many different types as long as they implement a common protocol.
protocol Nameable {
var name: String {get}
}
This defines a protocol named Nameable that requires those who implement to have a name variable of type String. Next, we define two different things that both implement the protocol.
class Person: Nameable {
let name: String
init(name: String){
self.name = name
}
}
struct Pet: Nameable {
let name: String
}
Then we tweak our generic function so that it requires that the elements must conform to Nameable, vastly different though they are.
func longestName<T:Sequence>(from stringsArray: T) -> String where T.Iterator.Element == Nameable{
return (stringsArray.max {$0.name.count < $1.name.count})?.name ?? ""
}
Let's collect the different objects into an array. Then call our function.
let myFriends: [Nameable] = [Pet(name: "Bailey"), Person(name: "Agape Sophia")]
let longestC = longestName(from: myFriends)
Lastly, after knowing "where" above and "Sequence" above, you may simply extend Sequence:
extension Sequence where Iterator.Element == String {
func topString() -> String {
self.max(by: { $0.count < $1.count }) ?? ""
}
}
Or the protocol type:
extension Sequence where Iterator.Element == Nameable {
func theLongestName() -> Nameable? {
self.max(by: { $0.name.count < $1.name.count })
}
}

Related

Making sure every Alphabet is in a string (Kotlin)

So I have a question where I am checking if a string has every letter of the alphabet in it. I was able to check if there is alphabet in the string, but I'm not sure how to check if there is EVERY alphabet in said string. Here's the code
fun isPangram (pangram: Array<String>) : String {
var panString : String
var outcome = ""
for (i in pangram.indices){
panString = pangram[i]
if (panString.matches(".^*[a-z].*".toRegex())){
outcome = outcome.plus('1')
}
else {outcome = outcome.plus('0')}
}
return outcome
}
Any ideas are welcomed Thanks.
I think it would be easier to check if all members of the alphabet range are in each string than to use Regex:
fun isPangram(pangram: Array<String>): String =
pangram.joinToString("") { inputString ->
when {
('a'..'z').all { it in inputString.lowercase() } -> "1"
else -> "0"
}
}
Hi this is how you can make with regular expression
Kotlin Syntax
fun isStrinfContainsAllAlphabeta( input: String) {
return input.lowercase()
.replace("[^a-z]".toRegex(), "")
.replace("(.)(?=.*\\1)".toRegex(), "")
.length == 26;
}
In java:
public static boolean isStrinfContainsAllAlphabeta(String input) {
return input.toLowerCase()
.replace("[^a-z]", "")
.replace("(.)(?=.*\\1)", "")
.length() == 26;
}
the function takes only one string. The first "replaceAll" removes all the non-alphabet characters, The second one removes the duplicated character, then you check how many characters remained.
Just to bounce off Tenfour04's solution, if you write two functions (one for the pangram check, one for processing the array) I feel like you can make it a little more readable, since they're really two separate tasks. (This is partly an excuse to show you some Kotlin tricks!)
val String.isPangram get() = ('a'..'z').all { this.contains(it, ignoreCase = true) }
fun checkPangrams(strings: Array<String>) =
strings.joinToString("") { if (it.isPangram) "1" else "0" }
You could use an extension function instead of an extension property (so it.isPangram()), or just a plain function with a parameter (isPangram(it)), but you can write stuff that almost reads like English, if you want!

How to make a function that compares strings?

I want to make a function which compares strings.
I don't want to use equal operators (==), I want it worked only with Swift language.
First I made a function which takes 2 strings, and returns bool type.
then I looped these strings with for in syntax.
And want to compare these characters, if strings have equal value, it should return true, if not, then false. Is there any better way?
func isEqual(str1:String, str2:String) -> Bool {
var result = false
for char in str1 {
}
for char2 in str2 {
}
//Compare characters.
return result
}
== works fine with Strings in Swift. For educational purposes
(as I conclude from your comment "because I'm practicing...")
you can implement it as:
func myStringCompare(str1 : String, str2 : String) -> Bool {
if count(str1) != count(str2) {
return false
}
for (c1, c2) in zip(str1, str2) {
if c1 != c2 {
return false
}
}
return true
}
zip(str1, str2) returns a sequence of pairs from the given
sequences, this is a convenient way to enumerate the strings
"in parallel".
Once you have understood how it works, you can shorten it,
for example to:
func myStringCompare(str1 : String, str2 : String) -> Bool {
return count(str1) == count(str2) && !contains(zip(str1, str2), { $0 != $1 })
}
Comparing the string length is necessary because the zip() sequence
terminates as soon as one of the strings is exhausted. Have a look at
#drewag's answer to In Swift I would like to "join" two sequences in to a sequence of tuples
for an alternative Zip2WithNilPadding sequence.
If you don't want to use the built-in zip() function (again for
educational/self-learning purposes!) then you can use the fact
that Strings are sequences, and enumerate them in parallel using
the sequence generator. This would work not only for strings but
for arbitrary sequences, as long as the underlying elements can
be tested for equality, so let's make it a generic function:
func mySequenceCompare<S : SequenceType where S.Generator.Element : Equatable>(lseq : S, rseq : S) -> Bool {
var lgen = lseq.generate()
var rgen = rseq.generate()
// First elements (or `nil`):
var lnext = lgen.next()
var rnext = rgen.next()
while let lelem = lnext, relem = rnext {
if lelem != relem {
return false
}
// Next elements (or `nil`):
lnext = lgen.next()
rnext = rgen.next()
}
// Are both sequences exhausted?
return lnext == nil && rnext == nil
}
Tests:
mySequenceCompare("xa", "xb") // false
mySequenceCompare("xa", "xa") // true
mySequenceCompare("a", "aa") // false
mySequenceCompare("aa", "a") // false
My solution differ a little as I didn't know about the zip operator, I guess is not as efficient as the one post by Martin great use of tuple.
Great question alphonse
func isEqual(str1:String, str2:String) -> Bool {
if count(str1) != count(str2){
return false
}
for var i = 0; i < count(str1); ++i {
let idx1 = advance(str1.startIndex,i)
let idx2 = advance(str2.startIndex,i)
if str1[idx1] != str2[idx2]{
return false
}
}
return true
}
As pointed by Martin each string needs its own index, as explained by him:
"The "trick" is that "🇩🇪" is an "extended grapheme cluster" and consists of two Unicode code points, but counts as one Swift character."
Link for more details about extended grapheme cluster

Convert String.Index to Int or Range<String.Index> to NSRange

So I've found issues relating to the case of converting NSRange to Range<String.Index>, but I've actually run into the opposite problem.
Quite simply, I have a String and a Range<String.Index> and need to convert the latter into an NSRange for use with an older function.
So far my only workaround has been to grab a substring instead like so:
func foo(theString: String, inRange: Range<String.Index>?) -> Bool {
let theSubString = (nil == inRange) ? theString : theString.substringWithRange(inRange!)
return olderFunction(theSubString, NSMakeRange(0, countElements(theSubString)))
}
This works of course, but it isn't very pretty, I'd much rather avoid having to grab a sub-string and just use the range itself somehow, is this possible?
If you look into the definition of String.Index you find:
struct Index : BidirectionalIndexType, Comparable, Reflectable {
/// Returns the next consecutive value after `self`.
///
/// Requires: the next value is representable.
func successor() -> String.Index
/// Returns the previous consecutive value before `self`.
///
/// Requires: the previous value is representable.
func predecessor() -> String.Index
/// Returns a mirror that reflects `self`.
func getMirror() -> MirrorType
}
So actually there is no way to convert it to Int and that for good reason. Depending on the encoding of the string the single characters occupy a different number of bytes. The only way would be to count how many successor operations are needed to reach the desired String.Index.
Edit The definition of String has changed over the various Swift versions but it's basically the same answer. To see the very current definition just CMD-click on a String definition in XCode to get to the root (works for other types as well).
The distanceTo is an extension which goes to a variety of protocols. Just look for it in the String source after the CMD-click.
let index: Int = string.startIndex.distanceTo(range.startIndex)
I don't know which version introduced it, but in Swift 4.2 you can easily convert between the two.
To convert Range<String.Index> to NSRange:
let range = s[s.startIndex..<s.endIndex]
let nsRange = NSRange(range, in: s)
To convert NSRange to Range<String.Index>:
let nsRange = NSMakeRange(0, 4)
let range = Range(nsRange, in: s)
Keep in mind that NSRange is UTF-16 based, while Range<String.Index> is Character based.
Hence you can't just use counts and positions to convert between the two!
In Swift 4, distanceTo() is deprecated. You may have to convert String to NSString to take advantage of its -[NSString rangeOfString:] method, which returns an NSRange.
Swift 4 Complete Solution:
OffsetIndexableCollection (String using Int Index)
https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-
let a = "01234"
print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234
print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2
if let number = a.index(of: "1") {
print(number) // 1
print(a[number...]) // 1234
}
if let number = a.index(where: { $0 > "1" }) {
print(number) // 2
}
You can use this function and call it when ever you need convertion
extension String
{
func CnvIdxTooIntFnc(IdxPsgVal: Index) -> Int
{
return startIndex.distanceTo(IdxPsgVal)
}
}

Swift - Finding a substring between two locations in a string

I have a string that is formatted like this: "XbfdASF;FBACasc|Piida;bfedsSA|XbbnSF;vsdfAs|"
Basiclly its an ID;ID| and then it repeats.
I have the first ID and I need to find it's partner Example: I have 'Piida' and I need to find the String that follows it after the ';' which is 'bfedsSA'
How do I do this?
The problem I am having is that the length of the IDs is dynamic so I need to get the index of '|' after the ID I have which is 'Piida' and then get the string that is between these indexes which in this case should be 'bfedsSA'.
There are many ways to do this, but the easiest is to split the string into an array using a separator.
If you know JavaScript, it's the equivalent of the .split() string method; Swift does have this functionality, but as you see there, it can get a little messy. You can extend String like this to make it a bit simpler. For completeness, I'll include it here:
import Foundation
extension String {
public func split(separator: String) -> [String] {
if separator.isEmpty {
return map(self) { String($0) }
}
if var pre = self.rangeOfString(separator) {
var parts = [self.substringToIndex(pre.startIndex)]
while let rng = self.rangeOfString(separator, range: pre.endIndex..<endIndex) {
parts.append(self.substringWithRange(pre.endIndex..<rng.startIndex))
pre = rng
}
parts.append(self.substringWithRange(pre.endIndex..<endIndex))
return parts
} else {
return [self]
}
}
}
Now, you can call .split() on strings like this:
"test".split("e") // ["t", "st"]
So, what you should do first is split up your ID string into segments by your separator, which will be |, because that's how your IDs are separated:
let ids: [String] = "XbfdASF;FBACasc|Piida;bfedsSA|XbbnSF;vsdfAs|".split("|")
Now, you have a String array of your IDs that would look like this:
["XbfdASF;FBACasc", "Piida;bfedsSA", "XbbnSF;vsdfAs"]
Your IDs are in the format ID;VALUE, so you can split them again like this:
let pair: [String] = ids[anyIndex].split(";") // ["ID", "VALUE"]
You can access the ID at index 0 of that array and the value at index 1.
Example:
let id: String = ids[1].split(";")[0]
let code: String = ids[1].split(";")[1]
println("\(id): \(code)") // Piida: bfedsSA

String interpolation in Swift

A function in swift takes any numeric type in Swift (Int, Double, Float, UInt, etc).
the function converts the number to a string
the function signature is as follows :
func swiftNumbers <T : NumericType> (number : T) -> String {
//body
}
NumericType is a custom protocol that has been added to numeric types in Swift.
inside the body of the function, the number should be converted to a string:
I use the following
var stringFromNumber = "\(number)"
which is not so elegant, PLUS : if the absolute value of the number is strictly inferior to 0.0001 it gives this:
"\(0.000099)" //"9.9e-05"
or if the number is a big number :
"\(999999999999999999.9999)" //"1e+18"
is there a way to work around this string interpolation limitation? (without using Objective-C)
P.S :
NumberFormater doesn't work either
import Foundation
let number : NSNumber = 9_999_999_999_999_997
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 20
formatter.minimumIntegerDigits = 20
formatter.minimumSignificantDigits = 40
formatter.string(from: number) // "9999999999999996.000000000000000000000000"
let stringFromNumber = String(format: "%20.20f", number) // "0.00000000000000000000"
Swift String Interpolation
1) Adding different types to a string
2) Means the string is created from a mix of constants, variables, literals or expressions.
Example:
let length:Float = 3.14
var breadth = 10
var myString = "Area of a rectangle is length*breadth"
myString = "\(myString) i.e. = \(length)*\(breadth)"
Output:
3.14
10
Area of a rectangle is length*breadth
Area of a rectangle is length*breadth i.e. = 3.14*10
Use the Swift String initializer: String(format: <#String#>, arguments: <#[CVarArgType]#>)
For example:
let stringFromNumber = String(format: "%.2f", number)
String and Characters conforms to StringInterpolationProtocol protocol which provide more power to the strings.
StringInterpolationProtocol - "Represents the contents of a string literal with interpolations while it’s being built up."
String interpolation has been around since the earliest days of Swift, but in Swift 5.0 it’s getting a massive overhaul to make it faster and more powerful.
let name = "Ashwinee Dhakde"
print("Hello, I'm \(name)")
Using the new string interpolation system in Swift 5.0 we can extend String.StringInterpolation to add our own custom interpolations, like this:
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Date) {
let formatter = DateFormatter()
formatter.dateStyle = .full
let dateString = formatter.string(from: value)
appendLiteral(dateString)
}
}
Usage: print("Today's date is \(Date()).")
We can even provide user-defined names to use String-Interpolation, let's understand with an example.
extension String.StringInterpolation {
mutating func appendInterpolation(JSON JSONData: Data) {
guard
let JSONObject = try? JSONSerialization.jsonObject(with: JSONData, options: []),
let jsonData = try? JSONSerialization.data(withJSONObject: JSONObject, options: .prettyPrinted) else {
appendInterpolation("Invalid JSON data")
return
}
appendInterpolation("\n\(String(decoding: jsonData, as: UTF8.self))")
}
}
print("The JSON is \(JSON: jsonData)")
Whenever we want to provide "JSON" in the string interpolation statement, it will print the .prettyPrinted
Isn't it cool!!

Resources