Swift, the for loop index value does not change as expectation - arrays

Here is my code, inside of my for loop, there is a while loop changes
the variable i's value. I printed out the value of i before while loop
and and after loop, the new "before index value" is expected to start
at "after index value +1". However, the "new before index value" just
increase as normal, which increase one each time. Appreciate your time.
class Solution
{
func summaryRange(nums: [Int]) -> [String]
{
var result = [String]()
if nums.count == 1
{
result.append(String(nums[0])+"")
return result
}
for var i in (0...nums.count-1)
{
let a = nums[i]
print("before: \(i)")
while i+1<nums.count && (nums[i+1] - nums[i] == 1)
{
i += 1
}
if a != nums[i]
{
result.append(String(a) + "->" + String(nums[i]))
}
else
{
result.append(String(a)+"")
}
print("after: \(i)")
}
return result
}
}
var test = Solution()
var input = [0,1,2,4,5,7]
var result = test.summaryRange(input)
print(result)
This code tries to get result like ["0->2","4->5","7"]

This is my own solution. I figure out the problem. Basically, I switch
the "for loop" to a "while loop". Then, it works.
class Solution
{
func summaryRanges(nums: [Int]) -> [String]
{
var result = [String]()
var i = 0
while(i<nums.count)
{
let a = nums[i]
while i+1<nums.count && (nums[i+1] - nums[i] == 1)
{
i += 1
}
if a != nums[i]
{
result.append(String(a) + "->" + String(nums[i]))
}
else
{
result.append(String(a)+"")
}
i += 1
}
return result
}
}

In swift, for in loop is a wrapper around the Sequence Type. In this particular case, 0...numCount-1 is a Sequence of Ints. When you call for in this is what happens internally.
let a = 0..<5
var generator = a.generate()
while var i = generator.next() {
print(i + 1)
i+= 1
}
So even if you increment the value of i inside the loop, it wont affect the loop itself. This is because loop is controller only by calling genertator.next().
This helps understand the basics.

This was an interesting one. I think this does the trick (with some code cleanup as well :-]
class Solution
{
func summaryRange(nums: [Int]) -> [String] {
var result = [String]()
var currentRange = [Int]()
for num in nums {
if currentRange.isEmpty { //if range is empty, start a new range
currentRange.append(num)
continue
}
if num - 1 != currentRange.last! { //if not next number in sequence, close range, start new
result.append(closeRange(currentRange))
currentRange = []
currentRange.append(num)
} else if num - 1 == currentRange.last {
currentRange.append(num) //if next number, add to range
}
}
if !currentRange.isEmpty {
result.append(closeRange(currentRange)) //check for unclosed ranges
}
return result
}
func closeRange(range: [Int]) -> String {
guard range.count > 0 else { return "" }
if range.count == 1 { return String(range.first!) }
return "\(range.first!)->\(range.last!)"
}
}
var test = Solution()
var input = [0,1,2,4,5,7]
var result = test.summaryRange(input)
print(result)
Let me know how this works for you!

Related

Replacing text won't go correctly with encoding and decoding

I am currently using 2 arrays:
let letters:[Character] =
[" ","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","!","#","#","$","%","^","&","*","(",")","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","E","S","T","U","V","W","X","Y","Z"]
let cypher:[Character] = ["o","p","q","r","a","b","c","d","e","f","g","h","i","u","v","w","x","y","z","j","k","l","m","n","s","t","$","#","#","!","&","%","^","*","("," ",")","5","7","2","9","8","0","1","3","2","4","Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M"]
Both 73 characters.
I am using this line of code to encode the inserted text:
var encode:[Character:Character] = [:]
for (index, letter) in letters.enumerated() { encode[letter] = cypher[index] }
let encodeStep1 = String(insertedText.characters.map({ encode[$0] ?? $0 }))
randomNumber = Int(arc4random_uniform(9) + 1)
var encodeStep2 = cypher.rotate(shift: randomNumber)
for (index, letter) in letters.enumerated() { encode[letter] = encodeStep2[index] }
let final = String(encodeStep1.characters.map({ encode[$0] ?? $0 }))
Decode:
var decode:[Character:Character] = [:]
let step1 = cypher.rotate(shift: (randomNumber))
for (index, letter) in step1.enumerated() { decode[letter] = letters[index] }
let step1Decoded = String(insertedEncryptedText.characters.map({ decode[$0] ?? $0 }))
for (index, letter) in cypher.enumerated() { decode[letter] = letters[index] }
let step2Decoded = String(step1Decoded.characters.map({ decode[$0] ?? $0 }))
Rotate function:
extension Array {
func rotate(shift:Int) -> Array {
var array = Array()
if (self.count > 0) {
array = self
if (shift > 0) {
for i in 1...shift {
array.append(array.remove(at: 0))
}
}
else if (shift < 0) {
for i in 1...abs(shift) {
array.insert(array.remove(at: array.count-1),at:0)
}
}
}
return array
}
}
For some very odd reason, number "3" is often shown as a number "9" when decoded. From what I have seen, the problem occurs at step2Decoded. I just can not figure out what I am doing wrong. This is however a part of the code, if I need to post more I can do that.
It's because you have a typo. Your cypher array has two "2"s in it, one at cypher[39] and another 6 indices further at cypher[45]. When you're decoding in the final step you're expecting decoded["2"] to map to the value "3" in the letters array, which it does, but it's getting overwritten when it hits the "2" again setting it to the value 6 indices further in the letters array which has a value of "9".
I assume you want to change that second "2" value in the letters array to "6" instead (since I noticed there was no "6" in there). That would solve your problem.

How to mutate an array of integers in-place in swift through filtering

One can filter an array like this in swift:
var numbers = Array(1...1000000)
numbers = numbers.filter( { return $0 % 2 == 0 } )
Is it possible to filter and avoid the copy operation, that occurs when the filtering is done, e.g mutating the original array.
In a similar way to this pseudocode:
numbers.MutablefilterOperation({ return $0 % 2 == 0})
In C++ the equvivalent to what is going on in Swift above would be:
std::vector<int> originalNumbers(1000000);
std::vector<int> newNumbers;
std::copy_if (originalNumbers.begin(), originalNumbers.end(), std::back_inserter(newNumbers), [](int i) { return i % 2 == 0 } );
What I would like to achieve for performance reasons:
std::vector<int> originalNumbers(1000000);
auto pos = std::remove_if(originalNumbers.begin(), originalNumbers.end(), [](int x) { return x % 2 == 0; });
originalNumbers.erase(pos, originalNumbers.end());
This implementation should do the filtering without having to make a temporary copy of the entire array in the process (unless a copy of it is referenced by another variable, see "Copy on Write")
extension Array {
mutating func filterInPlace(isIncluded: (Element) throws -> Bool) rethrows {
var writeIndex = self.startIndex
for readIndex in self.indices {
let element = self[readIndex]
let include = try isIncluded(element)
if include {
if writeIndex != readIndex {
self[writeIndex] = element
}
writeIndex = self.index(after: writeIndex)
}
}
self.removeLast(self.distance(from: writeIndex, to: self.endIndex))
}
}
// example:
var arr = [6,2,6,5,2,5,6,2,2,1,6,7,3]
arr.filterInPlace { $0 % 2 == 1 }
print(arr) // [5, 5, 1, 7, 3]

Swift: How to sort an Array<String> [duplicate]

This question already has an answer here:
Swift: sorting of array is not done correctly
(1 answer)
Closed 7 years ago.
I have an Array containing values like 7-4.json, 87-1.json and 102-4.json and want to sort it (ascending). I used the following code:
var fileNames = ["7-4.json", "87-1.json", "102-4.json"]
fileNames = fileNames.sort{ $0 < $1 }
print(fileNames)
which results in:
["102-4.json", "7-4.json", "87-1.json"]
So it didn't worked as I aspected. How can I sort it like 7-4, 87-1, 102-4?
Here you go:
var fileNames = ["7-4.json", "87-1.json", "102-4.json"]
func sortWithCustomFormat(first: String, second: String) -> Bool{
func extract(value: String) -> (Int, Int){
return (Int(value.componentsSeparatedByString("-").first!)!, Int(value.componentsSeparatedByString("-").last!.componentsSeparatedByString(".").first!)!)
}
let firstNumber = extract(first)
let secondNumber = extract(second)
if firstNumber.0 != secondNumber.0 { return firstNumber.0 < secondNumber.0 }
return firstNumber.1 < secondNumber.1
}
fileNames.sort(sortWithCustomFormat)
The function sortWithCustomFormat has a function extract that takes the inputted string and extracts the first and second numbers from it. Then, you compare the first numbers. If they are equal, then you compare the second numbers.
var fileNames = ["87-1.json", "7-4.json", "87-3.json", "102-4.json"]
fileNames = fileNames.sort({ (s1, s2) -> Bool in
let f1 = s1.stringByReplacingOccurrencesOfString(".json", withString: "")
let f2 = s2.stringByReplacingOccurrencesOfString(".json", withString: "")
let arr1 = f1.componentsSeparatedByString("-")
let arr2 = f2.componentsSeparatedByString("-")
var int1 = Int(arr1[0])
var int2 = Int(arr2[0])
if int1 < int2 {
return true
}
else if int1 > int2 {
return false
}
else {
int1 = Int(arr1[1])
int2 = Int(arr2[1])
if int1 < int2 {
return true
}
else if int1 > int2 {
return false
}
else {
return true
}
}
});
print(fileNames)
Try this...
var fileNames = ["87-1.json", "7-4.json", "102-4.json"]
// Modded OP's order to actually test sort
var sorted = fileNames.sort{ $0 < $1 }
print(sorted) // ["102-4.json", "7-4.json", "87-1.json"]
// Not sorted as OP "required", as they are string sorted, not number sorted
// Very simplistic solution
sorted = fileNames.sort { ($0 as NSString).integerValue < ($1 as NSString).integerValue}
print(sorted) // As OP requires, but...
// It won't sort on his count field - add a failing case...
fileNames = ["7-4.json", "87-1.json", "102-4.json", "102-1.json"]
sorted = fileNames.sort { ($0 as NSString).integerValue < ($1 as NSString).integerValue}
print(sorted) // ["7-4.json", "87-1.json", "102-4.json", "102-1.son"]
// WRONG!
// Define a simple function that parses his strings into tuples.
// This assumes that the Strings are valid, and fails safe if not.
// If you want more validation, add it yourself!
func myParse(s: String) -> (Int, Int) {
let c = s.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "-."))
switch c.count {
case 0:
print("Careful Will Robinson!")
return (0, 0)
case 1:
print("Careful Will Robinson!")
return ((c[0] as NSString).integerValue, 0)
default:
return ((c[0] as NSString).integerValue, (c[1] as NSString).integerValue)
}
}
let test = fileNames.map { myParse($0) }
print("\(test)") // Test execution of function
sorted = fileNames.sort { (s1: String, s2: String) -> Bool in
let t1 = myParse(s1)
let t2 = myParse(s2)
if t1.0 == t2.0 {
return t1.1 < t2.1
} else {
return t1.0 < t2.0
}
}
print(sorted) // As required ["7-4.json", "87-1.json", "102-1.json", "102-4.json"]

Optional Int to Arrays in Swift - Average Function

How can I write an average function in swift in where input in an array of optional Ints? Here's what I wrote so far:
func ave(array: [Int?] -> Double?) {
var mysum = 0
for num in array {
mysum += num
}
return Double(mysum)/Double(array.count)
}
I read a lot of Optional ints but I don't know how to implement that when the input in an array of optional ints... Any help?
Here it is in Swift 2, since it is only a few days away now:
func ave(array: [Int?]) -> Double? {
guard array.filter({ $0 == nil }).isEmpty else {
print("One of the Ints was nil")
return nil
}
return Double(array.reduce(0, { $0 + $1! })) / Double(array.count)
}
The opening guard statement checks array for any nil members, and returns nil after printing a message if it finds any.
If no nil is found, we use a simple reduce statement to calculate the sum of the array members, then divide by the count.
Here are some examples of the results:
ave([1,2,3]) // 2
ave([1,2,nil]) // nil (and the message will print)
ave([]) // Double.NaN (because you're dividing by zero)
If you want it in Swift 1.2, the implementation is not all that different:
func ave(array: [Int?]) -> Double? {
if array.filter({ $0 == nil }).isEmpty {
return Double(array.reduce(0, combine: { $0 + $1! })) / Double(array.count)
} else {
print("One of the Ints was nil")
return nil
}
}
You just need to make an if let check in your for loop.
func ave(array: [Int?])-> Double {
var arraySize = array.count
var mysum = 0
for num in array {
if let num = num{
mysum += num
}else{
arraySize--
println("Is nil")
}
}
return Double(mysum)/Double(arraySize)
}
As you maybe see, I've added a variable called arraySize because you need to check what's the size of your real array. Without the nils. Otherwise your final calculation doesn't work as wanted. Also I've changed your func-header a little bit. There was a mistake in your code before:
func ave(array: [Int?] -> Double?) {
^^^^^^^^^^
The return-value has to be outside of the ():
func ave(array: [Int?])-> Double {
In Swift you can do average func in two lines (you can do this in one line, of course, but then you get duplicate code - array.filter { $0 != nil }):
func average(array: [Int?]) -> Double {
let arrayWhithoutNils = array.filter { $0 != nil }
return arrayWhithoutNils.count > 0 ? (arrayWhithoutNils.map { Double($0!) }.reduce(0, combine: +) / Double(arrayWhithoutNils.count)) : 0
}
print(average([1, 2, 3])) // 2
print(average([nil, 4, 5, nil])) // 4.5
When you have an array with optional elements, it usually helps to use flatmap to first give you an array with no optionals...
func ave ( nums:[Int?] ) -> Double?
{
var answer : Double? = .None
let realInts = nums.flatMap { $0 }
if ( realInts.count > 0 ) {
var accum : Int = 0
realInts.map { accum += $0 }
answer = Double(accum) / Double(realInts.count)
}
return answer
}
This should do the job
func average(numbers: [Int?]) -> Double {
let sum = numbers.reduce(0) { $0 + ($1 ?? 0) }
let numValidElms = numbers.filter { $0 != nil }.count
let delta = numbers.count - numValidElms
if delta > 0 {
println("Found \(delta) nil values")
}
if numValidElms > 0 {
return Double(sum) / Double(numValidElms)
} else {
return 0
}
}
Examples
average([nil]) // 0
average([1,2,3]) // 2
average([1, nil, 2]) // 1.5
Hope this helps.
One more option:
func ave(array: [Int?])-> Double {
var mysum = 0
var c = 0
for num in array {
if let n = num {
mysum += n
c++
}
return Double(mysum)/Double(c)
}

outside array not affected from function appending within

I am working with arrays and I created a function that appends an array from within. However when I print the array, it still appears empty. What gives?
var queriesFinal : [String] = []
func queryValidator(search : String)
{
var letterSet = NSCharacterSet(charactersInString: "abcdefgjhijklmnopqrstuvwxyz ")
var numberSet = NSCharacterSet(charactersInString: "1234567890".uppercaseString)
var queriesTwo : [String] = search.lowercaseString.componentsSeparatedByCharactersInSet(letterSet)
for(var x = 0; x < queriesTwo.count; x++)
{
for(var y = 0; y < 10; y++)
{
var str = String(y)
if(queriesTwo[x] == str)
{
var numberStr = String(queriesTwo[x]) + "th"
queriesFinal.append(numberStr)
}
}
}
}
println(queriesFinal)
search = "Matt 8th"
queryValidator(search)
This code can run in playground..
I appreciate any help!
As mentioned by Mike S, you've made a small mistake println should be after your queryValidator, I've also added an optional in case your queryValidator search returns nil, also as mentioned by Zaph you don't need numberSet, so I removed it:
func queryValidator(search : String) -> [String]? {
let queriesTwo:[String] = search.lowercaseString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "abcdefgjhijklmnopqrstuvwxyz "))
var queriesResult:[String] = []
for x in 0...queriesTwo.count-1 {
for y in 0...9 {
if(queriesTwo[x] == String(y)) {
queriesResult.append(String(queriesTwo[x]) + "th")
}
}
}
return queriesResult.count > 0 ? queriesResult : nil
}
var search = "Matt 8 less7"
if let queriesFinal = queryValidator(search) {
println(queriesFinal)
} else {
println("no result")
}
An alternative approach with Regular Expressions:
func queryValidator(search: String) -> [String] {
var queriesFinal:[String] = []
var nsSearch: NSString = search
let pattern = "(\\d+)"
var regEx = NSRegularExpression(pattern:pattern, options:nil, error:nil)
regEx?.enumerateMatchesInString(nsSearch, options:nil, range:NSMakeRange(0, nsSearch.length), usingBlock: { (result, flags, stop) in
let found = nsSearch.substringWithRange(result.range)
queriesFinal.append("\(found)th")
})
return queriesFinal
}
var result = queryValidator(search)
println("result: \(result)")
Output:
result: [8th, 7th, 6th]
For information on regular expressions see: Regular Expressions

Resources