5

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.

Dominick
  • 164
  • 2
  • 4
  • 13
  • 1
    For troubleshooting this error, note that if you try to pass `[myArray]` where you intended to pass `myArray`, this is the error that you'll get. Fix that typo & you're in business! – ConfusionTowers Apr 17 '21 at 20:37

3 Answers3

7

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:

  1. 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.
  2. 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"]
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • This is the most intelligent way to do it, although I confess that I find it forgettable because I've never really understood the logic of writing the expression in this way. This is very much a side point but my view is that `[String]("Leopard")` or `[String]("Leopard".characters)` would make more sense and be more consistent with `[Int](1...3)` etc. but I'd be delighted if someone could help me see the light on this. – sketchyTech Apr 22 '18 at 11:19
  • A `String` is once again a collection of `Character`, so calling `map` on a `String` operates on each `Character`. Calling `String.init` with each `Character` converts it into a `String`. – vacawama Apr 22 '18 at 11:24
  • The problem is type conversion. `[String]("Leopard")` doesn't work because this is no initializer for `Array` that takes a `String` or an `Array`. Each `Character` needs to be converted to a `String` which can't be done with casting. You must call a `String` initializer on every `Character`. – vacawama Apr 22 '18 at 11:29
  • Thanks for taking the time to clarify. – sketchyTech Apr 22 '18 at 18:18
  • I've addressed the mysterious error in a separate question. See [Fatal error: Only BidirectionalCollections can be advanced by a negative amount](https://stackoverflow.com/q/52128359/1033581) – Cœur Sep 01 '18 at 13:04
4

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)}
sketchyTech
  • 5,746
  • 1
  • 33
  • 56
1

If, it is optional character or string

usedLetters.append(currentWord.randomElement().map(String.init)!)

Here usedLetters is Array[String] currentWord is Optional string

logeshpalani31
  • 1,416
  • 12
  • 33