How to alter part of an array element in Swift? - arrays

Say I have the following array:
var questions = ["Is he great?", "Is he nice?", "Is he wonderful?"]
How can I alter it so that if I call any element from this array, the 'he' is substituted for 'she'? I've tried map, but this only seems to work for individual elements, and not parts of an element. Slicing is also only inter-element, and replaceOccurrence creates a new string, but doesn't alter the original. Ideally I'd like to write:
print(questions[0])
and get: "Is she great?" for any element in the array containing 'he' without having to create a whole new array. TIA.

I am not sure why you want to avoid creating a new array, but if you're determined to avoid making new arrays, consider an array of functions that return the thing you want, like:
var sheOrHe = "he"
func f(_ s: String) -> () -> String {
return { "Is \(sheOrHe) \(s)?" }
}
let qFuncs = [f("great"), f("nice"), f("wonderful")]
qFuncs[0]() // "Is he great?"
sheOrHe = "she"
qFuncs[0]() // "Is she great?"
Or without closing over the sheOrHe var:
func f(_ s: String) -> (String) -> String {
{ sheOrHe in "Is \(sheOrHe) \(s)?" }
}
let qFuncs = [f("great"), f("nice"), f("wonderful")]
qFuncs[0]("he") // "Is he great?"
qFuncs[0]("she") // "Is she great?"
Or without storing the functions:
func areThey(_ s: String) -> (String) -> String {
{ sheOrHe in "Is \(sheOrHe) \(s)?" }
}
let adjectives = ["great", "nice", "wonderful"]
areThey(adjectives[0])("he") // "Is he great?"
areThey(adjectives[0])("she") // "Is she great?"

Related

Remove duplicate Items in multiple arraylist swift

var brachNames = ["AP","AP","AP","AS","AS","AS","BR","BR","BR"]
var overAllTaget = ["84","84","84","84","84","84","84","84","84"]
var overAllSold = ["135","135","135","135","135","135","135","135","135"]
extension Array where Element : Hashable {
func removeDups() -> [Element] {
var uniquedElements = Set<Element>()
return filter { uniquedElements.insert($0).inserted }
}
}
I want this type of output - [AP,84,135,AS,84,135,BR,84,135]
Since you've 3 different Arrays, you need to first combine these to get an Array of Arrays using zip(_:_:) and map(_:), i.e.
var arr = zip(brachNames, zip(overAllTaget, overAllSold)).map { [$0.0, $0.1.0, $0.1.1] }
Now, use Set to filter out the duplicates. Then use flatMap(_:) to get a single result Array, i.e.
let result = Array(Set(arr)).flatMap{ $0 } //["AP", "84", "135", "AS", "84", "135", "BR", "84", "135"]
Note: Set is unordered. So, the sequence of the result might change.
Another approach would be to create a struct with the required fields (brachName, overallTarget, overallSold), comply to Hashable and apply something like this:
https://www.hackingwithswift.com/example-code/language/how-to-remove-duplicate-items-from-an-array
This way you could keep the order, if that's important.
It would be much better to work with an array of a custom type instead of 3 different arrays of data to make the code clearer and to avoid simple mistakes when accessing the data. Below is an example of such solution using a struct to hold the data
struct BranchData: Hashable {
let branchName: String
let overallTarget: Int
let overallSold: Int
}
var set = Set<BranchData>()
for (index, branch) in brachNames.enumerated() {
guard index < overAllSold.count, index < overAllTaget.count else {
break
}
set.insert(BranchData(branchName: branch, overallTarget: Int(overAllTaget[index]) ?? 0, overallSold: Int(overAllSold[index]) ?? 0))
}
To support the specific output with all values in an array we can add a computed property
extension BranchData {
var propertyArray: [String] {
[branchName, String(overallTarget), String(overallSold)]
}
}
let output = set.flatMap { $0.propertyArray }
or a more direct approach
let output = set.flatMap { [$0.branchName, $0.overallTarget, $0.overallSold] }

Refer to array based on string output

I'm learning Swift as I go here, so apologies if this is a silly question.
I'm looking to use the output of one function (String) to determine an input into a different function (Array).
The first function output (String) is then combined with another String to form the name of an already defined Array, which i'd like to use as input to second function. However, despite having the same name, the String is not seen as an array.
I've skipped some of the code, but relevant section below.
// Defined array
let rushProb = [0,11,19,64,78,89,96,98,99,100]
// Define probability and outcome function - PlayType
func findPlay(prob: [Int], outcome: [String]) -> String {
if let index = prob.firstIndex(where: { $0 > Int.random(in: 1...100) }) {
return outcome[index]
}
else {
return "na"
}
}
// This is successfully output as "rush"
let playSel = findPlay(prob: scen1Prob, outcome: scenPlay)
// This then creates "rushProb"
let playSelProb = playSel+"Prob"
// I want this to ultimately be findYards(prob: rushProb)
findYards(prob: playSelProb)
Well, you could use a dictionary where the key replaces your array name, and the value is the array. Then, you'd use the name you create to look up the array value in the dictionary:
let arrays = ["rushProb": [0,11,19,64,78,89,96,98,99,100],
"fooProb" : [0,15,29,44,68,78,86,92,94,100]]
// This is successfully output as "rush"
let playSel = findPlay(prob: scen1Prob, outcome: scenPlay)
// This then creates "rushProb"
let playSelProb = playSel+"Prob"
// look up the array that corresponds to "rushProb"
if let array = arrays[playSelProb] {
findYards(prob: array)
}

Swift Error Cannot convert value of type '[String.Element]' (aka 'Array<Character>') to expected argument type '[String]'

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

How do I pass a closure that returns an a value that conforms to Equatable into a function

I'm trying to create an extension of Array that returns a new array of unique items based on the items with the closure applied.
For example: If I had an array of Apple where apple has properties name and origin, to get one Apple of each origin I would call apple.uniqued(on: { $0.origin })
Here's the code I have so far:
extension Array where Element: Equatable {
func uniqued(on extract: (Element) -> Equatable) { // A
let elementsAndValues = map { (item: $0, extracted: extract($0)) } // 1
var uniqueValues: [Element] = []
var uniqueExtracts: [Equatable] = [] // A
elementsAndValues.forEach { (item, extracted) in
if !uniqueExtracts.contains(extracted) { // 3, B
uniqueValues += [item]
uniqueExtracts += [extracted]
}
}
return uniqueValues
}
}
This should work as follows:
Create an array of tuples with both the original elements (item) and the elements with the closure applied (extracted)
If uniqueExtracts doesn't contain the item, add it and add the item to the uniqueItems array.
The errors I'm getting are:
A) "Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements" (twice)
B) "Missing argument label 'where:' in call"
I'm using the latest version of Xcode. Any advice would be a lot of help. Many thanks.
You have multiple problems that mix together to create the errors you’re seeing. What you should do is use a generic.
extension Array
{
func uniqued<T:Equatable>(on extract:(Array.Element) -> T) -> [Array.Element]
{
let elementsAndValues = self.map{ (item: $0, extracted: extract($0)) }
var uniqueValues:[Element] = []
var uniqueExtracts:[T] = []
for (item, extracted) in elementsAndValues
{
if !uniqueExtracts.contains(extracted)
{
uniqueValues.append(item)
uniqueExtracts.append(extracted)
}
}
return uniqueValues
}
}
The <T:Equatable> declares a generic type parameter T that conforms to Equatable. Then the function signature can expect a closure that returns some generic type T that we know conforms to Equatable from the type constraint in the angle brackets. You also have to change every occurrence of Equatable to the generic parameter T, since Equatable isn’t a real type; see my answer here. If you do that the code should compile.
You have a few other things you should probably change:
Instead of using elementsAndValues.forEach(:), use a for <pattern> in list {} loop.
Although this is contentious, you should probably use the Array().append(:) method instead of += concatenation when adding one element to an array. In the case of +=, as opposed to +, this is purely to convey intent.
You did not declare a return type for your function, so the compiler assumes it returns Void, and so the return uniqueValues statement will cause a compiler error. Add a -> [Array.Element] to the function to fix this.
where Element:Equatable as a constraint on Array itself is superfluous. You are using a key function to determine equability, so whether the elements themselves are equatable is irrelevant.
You may want to use a Set or some other hashed data structure instead of a uniqueExtracts array. Testing for membership in an array is an O(n) operation.
I would do this with a group(by:) function, which would group each element in the sequence by a given key (e.g. the origin), yielding a Dictionary mapping keys to groups (arrays of elements in the group). From there, I would just map over the dictionary and just get the first element in each group.
public extension Sequence {
public typealias Element = Iterator.Element
public typealias Group = [Element]
public func group<Key: Hashable>(by deriveKey: (Element) -> Key) -> [Key: Group] {
var groups = [Key: Group]()
for element in self {
let key = deriveKey(element)
if var existingArray = groups[key] { // Group already exists for this key
groups[key] = nil //performance optimisation to prevent CoW
existingArray.append(element)
groups[key] = existingArray
}
else {
groups[key] = [element] // Create new group
}
}
return groups
}
}
struct Apple {
let name: String
let origin: String
}
let apples = [
Apple(name: "Foo", origin: "Origin 1"),
Apple(name: "Bar", origin: "Origin 1"),
Apple(name: "Baz", origin: "Origin 2")
]
let firstAppleInEachOrigin = apples.group(by: {$0.origin}).flatMap{ _, group in group.first }
firstAppleInEachOrigin.forEach{ print($0) }

Swift array loop once, write many

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"]

Resources