I'm trying to make sure that the middle value Lists is the first view that is seen when building my application. Xcode offers if let firstView = viewList.first and if let firstView = viewList.last but I can't workout how to select the middle value.
lazy var viewList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let lists = sb.instantiateViewController(withIdentifier: "Lists")
let reminders = sb.instantiateViewController(withIdentifier: "Reminders")
let todo = sb.instantiateViewController(withIdentifier: "To Do")
return [reminders, lists, todo]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstView = viewList.first {
self.setViewControllers([firstView], direction: .forward, animated: true, completion: nil)
}
}
Similar to first and last, you can extend Array with a computed middle property that returns an optional Element.
extension Array {
var middle: Element? {
guard count != 0 else { return nil }
let middleIndex = (count > 1 ? count - 1 : count) / 2
return self[middleIndex]
}
}
Usage Example:
if let middleView = viewList.middle {
//... Do something
}
I want you to be aware that first and last can return the same element if the array has only 1 element.
Similarly, though this extension will work for any array length, it can return the same element for:
first, middle & last if your array has only 1 element
middle & last if your array has only 2 elements
Can add an extension to Array to accomplish this:
extension Array {
var middleIndex: Int {
return (self.isEmpty ? self.startIndex : self.count - 1) / 2
}
}
let myArray: [String] = ["Hello", "World", "!"]
print("myArray.middleIndex: \(myArray.middleIndex)") // prints 1
Since viewList declared as [UIViewController] (not as optionals - [UIViewController?]), you don't have to "optional binding" (checking the whether the element is nil or not) because it has to be exist. what you should do instead is to check if the index in the range (making sure that the index is in the range).
Logically speaking (obviously), if you are pretty sure that viewList always has 3 elements, there is no need to do any check, just:
let middleViewController = viewList[1]
In case of the number of elements in viewList is undetermined and you are aiming to get the middle element, you simply get it as:
let middleViewController = viewList[(viewList.count - 1) / 2]
Remember, first and last are optionals, in your case there is no need to work with optionals...
Your viewList is an array of type UIViewController. And first and last only denotes their 0th and last index. like:
viewList.first is same as viewList[0]
viewList.last is same as viewList[viewList.count - 1]
Only difference in using these are if you will use viewList.first it will return nil if your array is empty, but if you will use viewList[0] on empty array, you app will be crash with error index out of bound...
So, you can easily access your middle value with index as:
if viewList.count > 1 {
let middleView = viewList[1]
self.setViewControllers([middleView], direction: .forward, animated: true, completion: nil)
}
If you are not sure about the viewList.count will be 3 or more, Then:
let middleIndex = (viewList.count - 1) / 2
let middleView = viewList[middleIndex]
Related
I am trying to create an array of letters from a given word by using the following Swift code (I have an array of words for allWords, but for simplicity I've just added an example word there for now):
let allWords = ["Leopards"]
var arrayOfLetters = Array(allWords[0])
let everyPossibleArrangementOfLetters = permute(list: arrayOfLetters)
func permute(list: [String], minStringLen: Int = 3) -> Set<String> {
func permute(fromList: [String], toList: [String], minStringLen: Int, set: inout Set<String>) {
if toList.count >= minStringLen {
set.insert(toList.joined(separator: ""))
}
if !fromList.isEmpty {
for (index, item) in fromList.enumerated() {
var newFrom = fromList
newFrom.remove(at: index)
permute(fromList: newFrom, toList: toList + [item], minStringLen: minStringLen, set: &set)
}
}
}
var set = Set<String>()
permute(fromList: list, toList:[], minStringLen: minStringLen, set: &set)
return set
}
I obtained this code from: Calculate all permutations of a string in Swift
But the following error is presented:
Cannot convert value of type '[String.Element]' (aka 'Array') to expected argument type '[String]'
I attempted the following, which works, but it takes over 10 seconds per word (depending on number of repeat letters) and I was hoping to find a better solution.
var arrayOfLetters: [String] = []
for letter in allWords[0] {
arrayOfLetters.append(String(letter))
}
let everyPossibleArrangementOfLetters = permute(list: arrayOfLetters)
I wasn't able to get the following solution to work, although I think is has promise I couldn't get past the productID name of items in the array whereas my array items aren't named...
Migration from swift 3 to swift 4 - Cannot convert String to expected String.Element
I'm also creating another array and checking each of those words to ensure their validity, and I run into the same error which I correct in the same way with array.append which is adding a lot more time in that location as well.
var everyPossibleArrangementOfLettersPartDeux: [String] = []
for word in everyPossibleArrangementOfLetters {
everyPossibleArrangementOfLettersPartDeux.append(word)
}
numberOfRealWords = possibleAnagrams(wordArray: everyPossibleArrangementOfLettersPartDeux)
func possibleAnagrams(wordArray: [String]) -> Int {
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSMakeRange(0, word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
var count = 0
for word in wordArray {
if isReal(word: word) {
count += 1
//print(word)
}
}
return count
}
I'm hoping the same replacement for array.append will work in both spots.
The problem is that Array(allWords[0]) produces [Character] and not the [String] that you need.
You can call map on a String (which is a collection of Characters and use String.init on each character to convert it to a String). The result of the map will be [String]:
var arrayOfLetters = allWords[0].map(String.init)
Notes:
When I tried this in a Playground, I was getting the mysterious message Fatal error: Only BidirectionalCollections can be advanced by a negative amount. This seems to be a Playground issue, because it works correctly in an app.
Just the word "Leopards" produces 109,536 permutations.
Another Approach
Another approach to the problem is to realize that permute doesn't have to work on [String]. It could use [Character] instead. Also, since you are always starting with a String, why not pass that string to the outer permute and let it create the [Character] for you.
Finally, since it is logical to think that you might just want anagrams of the original word, make minStringLen an optional with a value of nil and just use word.count if the value is not specified.
func permute(word: String, minStringLen: Int? = nil) -> Set<String> {
func permute(fromList: [Character], toList: [Character], minStringLen: Int, set: inout Set<String>) {
if toList.count >= minStringLen {
set.insert(String(toList))
}
if !fromList.isEmpty {
for (index, item) in fromList.enumerated() {
var newFrom = fromList
newFrom.remove(at: index)
permute(fromList: newFrom, toList: toList + [item], minStringLen: minStringLen, set: &set)
}
}
}
var set = Set<String>()
permute(fromList: Array(word), toList:[], minStringLen: minStringLen ?? word.count, set: &set)
return set
}
Examples:
print(permute(word: "foo", minStringLen: 1))
["of", "foo", "f", "fo", "o", "oof", "oo", "ofo"]
print(permute(word: "foo"))
["foo", "oof", "ofo"]
This line is returning a Character array, not a String one:
var arrayOfLetters = Array(allWords[0])
You can convert this to a String array like so:
var arrayOfLetters = Array(allWords[0]).map{String($0)}
You could alternatively write:
var arrayOfLetters = allWords[0].characters.map{String($0)}
If, it is optional character or string
usedLetters.append(currentWord.randomElement().map(String.init)!)
Here usedLetters is Array[String]
currentWord is Optional string
I have index out of range error when i delete object from my array. There is my code. It is an Elevator function which takes object of Floor class and works with array of objects of Passenger class on this floor. I create a temporary copy of object of current floor and then I go along array of this copy and if object is suitable for the conditions we push this object to Elevator array of passengers and delete it by index from original array of current floor object. If this matters, I use the Equatable protocol and created a function to compare.
Thanks for any answers.
class Passenger: Equatable{...}
func ==(l: Passenger, r: Passenger) -> Bool {
return l === r
}
func checkFloor(f: Floor){
var tempFloor = f
var pass = passengers
for i in 0..<passengers.count {
if(passengers.isEmpty){
break
}
if(pass[i].getFloor()==f.getIdFloor()){
f.peoples.append(pass[i])
f.peoples[f.peoples.count - 1].setDirection(who: "nothing")
//if var index = passengers.index(of: pass[i]) {
if let index = searchInArray(who: passengers, who: pass[i]) {
passengers.remove(at: index)
}
}
}
// in this part I have a problem
for i in 0..<tempFloor.countOf() {
if(f.peoples.isEmpty || passengers.count >= capacity){
break
}
if(tempFloor.peoples[i].getDirection()==tempFloor.peoplesDirection()
){
passengers.append(tempFloor.peoples[i])
if let index = f.peoples.index(of: tempFloor.peoples[i]) {
if (index >= 0 && index < f.peoples.count) {
//print(index)
f.peoples.remove(at: index) // index out of range error
}
}
}
}
}
You are removing items whilst enumerating a range, so the range is changing (potentially often) but this wont update for i in 0..<tempFloor.countOf()
When you remove an item from an array, each item after that index changes its index and the count is reduced. so if you plan to do this, it's usually best to enumerate the array backwards, so the removal of the current item will not affect what your doing next.
To demonstrate, try this code in a playground
var arr = [1,2,3,4,5,6,7,8,9,10]
for (index, item) in arr.enumerated().reversed() {
if item % 2 == 0 {
arr.remove(at: index)
}
}
print(arr)
It will iterate over the items in the array backwards and remove any that are even, and will output:
"[1, 3, 5, 7, 9]\n"
I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true
Consider the following silly, simple example:
let arr = ["hey", "ho"]
let doubled = arr.map {$0 + $0}
let capitalized = arr.map {$0.capitalizedString}
As you can see, I'm processing the same initial array in multiple ways in order to end up with multiple processed arrays.
Now imagine that arr is very long and that I have many such processes generating many final arrays. I don't like the above code because we are looping multiple times, once for each map call. I'd prefer to loop just once.
Now, obviously we could handle this by brute force, i.e. by starting with multiple mutable arrays and writing into all of them on each iteration:
let arr = ["hey", "ho"]
var doubled = [String]()
var capitalized = [String]()
for s in arr {
doubled.append(s + s)
capitalized.append(s.capitalizedString)
}
Fine. But now we don't get the joy of using map. So my question is: is there a better, Swiftier way? In a hazy way I imagine myself using map, or something like map, to generate something like a tuple and magically splitting that tuple out into all resulting arrays as we iterate, as if I could say something like this (pseudocode, don't try this at home):
let arr = ["hey", "ho"]
let (doubled, capitalized) = arr.map { /* ???? */ }
If I were designing my own language, I might even permit a kind of splatting by assignment into a pseudo-array of lvalues:
let arr = ["hey", "ho"]
let [doubled, capitalized] = arr.map { /* ???? */ }
No big deal if it can't be done, but it would be fun to be able to talk this way.
How about a function, multimap, that takes a collection of transformations, and applies each one, returning them as an array of arrays:
// yay protocol extensions
extension SequenceType {
// looks like T->U works OK as a constraint
func multimap
<U, C: CollectionType
where C.Generator.Element == Generator.Element->U>
(transformations: C) -> [[U]] {
return transformations.map {
self.map($0)
}
}
}
Then use it like this:
let arr = ["hey", "ho"]
let double: String->String = { $0 + $0 }
let uppercase: String->String = { $0.uppercaseString }
arr.multimap([double, uppercase])
// returns [["heyhey", "hoho"], ["HEY", "HO"]]
Or it might be quite nice in variadic form:
extension SequenceType {
func multimap<U>(transformations: (Generator.Element->U)...) -> [[U]] {
return self.multimap(transformations)
}
}
arr.multimap({ $0 + $0 }, { $0.uppercaseString })
Edit: if you want separate variables, I think the best you can do is a destructure function (which you have to declare n times for each n-tuple unfortunately):
// I don't think this can't be expressed as a protocol extension quite yet
func destructure<C: CollectionType>(source: C) -> (C.Generator.Element,C.Generator.Element) {
precondition(source.count == 2)
return (source[source.startIndex],source[source.startIndex.successor()])
}
// and, since it's a function, let's declare pipe forward
// to make it easier to call
infix operator |> { }
func |> <T,U>(lhs: T, rhs: T->U) -> U {
return rhs(lhs)
}
And then you can declare the variables like this:
let (doubled,uppercased)
= arr.multimap({ $0 + $0 }, { $0.uppercaseString }) |> destructure
Yes this is a teensy bit inefficient because you have to build the array then rip it apart – but that’s really not going to be material, since the arrays are copy-on-write and we’re talking about a small number of them in the outer array.
edit: an excuse to use the new guard statement:
func destructure<C: Sliceable where C.SubSlice.Generator.Element == C.Generator.Element>(source: C) -> (C.Generator.Element,C.Generator.Element) {
guard let one = source.first else { fatalError("empty source") }
guard let two = dropFirst(source).first else { fatalError("insufficient elements") }
return (one,two)
}
What is wrong with your suggestion of tuple?
let arr = ["hey", "ho"]
let mapped = arr.map {e in
return (e + e, e.capitalizedString)
}
How about this, we process 'capitalized' array while we map the 'doubled' array:
let arr = ["hey", "ho"]
var capitalized = [String]()
let doubled = arr.map {(var myString) -> String in
capitalized.append(myString.capitalizedString)
return myString + myString
}
//doubled ["heyhey", "hoho"]
//capitalized: ["Hey", "Ho"]
I have a UISegmentedControl with 4 indexes that contains quiz answers that are gathered from a string array. My goal is to fill all 4 indexes with wrong answers, and then randomly replace one of those indexes with the correct answer. When I run the simulator, the app appears to enter an infinite loop and the view never loads. When I comment-out the entire second for-loop (which is supposed to fill the indexes with incorrect guesses), the view loads and the segmented control appears with just the correct answer displayed in one segment. Why is the infinite loop being entered (if that is the case) and how can I modify this code to display the segmented controls as desired?
func nextQuestion()
{
questionNumberLabel.text = String(format: "Question %1$d of %2$d",
(correctGuesses + 1), numberOfQuestions)
answerLabel.text = ""
correctAnswer = allAnimals.removeAtIndex(0)
animalImageView.image = UIImage(named: correctAnswer) // next animal
// re-enable UISegmentedControls and delete prior segments
for segmentedControl in segmentedControls
{
segmentedControl.enabled = true
segmentedControl.removeAllSegments()
}
// place guesses on displayed UISegmentedControls
allAnimals.shuffle() // shuffle array
var i = 0
for segmentedControl in segmentedControls
{
if !segmentedControl.hidden
{
var segmentIndex = 0
while segmentIndex < 4
{
if i < allAnimals.count && correctAnswer != allAnimals[i]
{
segmentedControl.insertSegmentWithTitle(
getStringFromFile(allAnimals[i]),
atIndex: segmentIndex, animated: false)
++segmentIndex
}
++i
}
}
}
// pick random segment and replace with correct answer
let randomIndexInRow = Int(arc4random_uniform(UInt32(4)))
segmentedControls[0].removeSegmentAtIndex(
randomIndexInRow, animated: false)
segmentedControls[0].insertSegmentWithTitle(
getStringFromFile(correctAnswer),
atIndex: randomIndexInRow, animated: false)
}
The solution is:
selectedSegmentIndex = 2