I am having trouble assigning a type to this numsToLtrsMap[usr_query[idx]]. The numsToLtrsMap is an object of keys that are strings and the values that are strings. I am new to Typescript and have found no solution for it
Playground Link: Provided
const convertToLtrCombos = (usr_query: string) => {
const result: string[] = [];
const numsToLtrsMap ={
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz",
};
//recursive fn
const scan = (currStr: string, idx: number) => {
//if idx is in last digit
if (idx > usr_query.length - 1) {
//we push string to result array
result.push(currStr);
return;
}
{
/*each letter a usr press gives ref to the key of the value of usr_query based on the idx value of usr_query*/
}
const letters = numsToLtrsMap[usr_query[idx]];
for (const ltr of letters) {
//concatenate new ltr to string, then move to next idx
scan(currStr + ltr, idx + 1);
}
};
// call fn starting with an empty string at first idx
scan("", 0);
return result;
};
I have tried to assign usr_query as a string and change the numsToLtrsMap as an array of objects. That clear one error but inside the nested indexes gives me an error.
Related
This question already has answers here:
Split string by components and keep components in place
(2 answers)
Closed last year.
I am new to Swift, I come from Java background. I want to make a struct for parsing input for a calculator. Here is my code
struct Parse {
var runningCalc: String
init(runningCalc: String) {
self.runningCalc = runningCalc
}
mutating func toArray(String: runningCalc){
runningCalc = runningCalc.split()
}
}
I want to have input of a string, and have it return a string Array with delimiters of
{ +-/*()%}.
In java you could do a string tokenizer to keep the delimiters in the returned string array.
How can I use the split or components method to separate my string input to a string array?
I personally wouldn't use a struct. It's more common to extend an existing type with additional functionality. So, you want the components of a String that includes delimiters? Sounds like you want to extend String. Since String conforms to StringProtocol I'd extend StringProtocol so that any other types that conform to StringProtocol get this new functionality for free:
StringProtocol already has a method called components(separatedBy:) which takes a CharacterSet (a set of characters) to delimit the receiver.
Let's implement a method similar to the existing StringProtocol method.
extension StringProtocol {
func components(separatedByIncluding delims: CharacterSet) -> [String] {
var components: [String] = []
var component = ""
for character in self {
if String(character).rangeOfCharacter(from: delims) != nil {
if !component.isEmpty {
components.append(component)
}
components.append(String(character))
component = ""
} else {
component += [character]
}
}
if !component.isEmpty {
components.append(component)
}
return components
}
}
The method loops over each Character and checks if it's a delimiter. If so, it adds a new String to the array consisting of the delimiter Character. If not, it constructs a substring consisting of non-delimiter Character values. The resulting array of delimiters and substrings are then returned.
Usage would be:
func testExample() throws {
let delims = CharacterSet(charactersIn: "{ +-/*()%}")
let text = "{abc def+ghi-jkl/mno"
let components = text.components(separatedByIncluding: delims)
print("================")
print(components)
print("================")
}
The result is:
================
["{", "abc", " ", "def", "+", "ghi", "-", "jkl", "/", "mno"]
================
AFAIK there is no native Swift split method that allows you to keep the delimiters. You will need to implement your own split method extending Collection. Special thanks for #MartinR and #Rob for the code review:
extension Collection {
func splitAndKeep(
maxSplits: Int = .max,
omittingEmptySubsequences: Bool = true,
whereSeparator isSeparator: (Element) throws -> Bool
) rethrows -> [SubSequence] {
precondition(maxSplits >= 0, "maxSplits can not be negative")
if isEmpty { return [] }
var subsequences: [SubSequence] = []
var lowerBound = startIndex
func appendAndAdvance(with upperBound: Index) {
let range = lowerBound..<upperBound
if !omittingEmptySubsequences || !range.isEmpty {
subsequences.append(self[range])
lowerBound = upperBound
}
}
while
var upperBound = try self[lowerBound...].firstIndex(where: isSeparator),
subsequences.count < maxSplits {
appendAndAdvance(with: upperBound)
if subsequences.count == maxSplits {
break
}
formIndex(after: &upperBound)
appendAndAdvance(with: upperBound)
}
appendAndAdvance(with: endIndex)
return subsequences
}
}
let string = "*(22+13)/"
let delimiters = Set<Character>("{+-/*()%}.")
let ss = string.splitAndKeep(whereSeparator: delimiters.contains) // ["*", "(", "22", "+", "13", ")", "/"]
let ss1 = string.splitAndKeep(maxSplits: 0, whereSeparator: delimiters.contains) // ["*(22+13)/"]
let ss2 = string.splitAndKeep(maxSplits: 1, whereSeparator: delimiters.contains) // ["*", "(22+13)/"]
let ss3 = string.splitAndKeep(maxSplits: 2, whereSeparator: delimiters.contains) // ["*", "(", "22+13)/"]
let ss4 = string.splitAndKeep(omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*", "", "(", "22", "+", "13", ")", "", "/", ""]["", "*", "", "(", "22", "+", "13", ")", "", "/", ""]
let ss5 = string.splitAndKeep(maxSplits: 0, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["*(22+13)/"]
let ss6 = string.splitAndKeep(maxSplits: 1, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*(22+13)/"]
let ss7 = string.splitAndKeep(maxSplits: 2, omittingEmptySubsequences: false, whereSeparator: delimiters.contains) // ["", "*", "(22+13)/"]
I have a project in Typescript in which I am trying to create a function that converts an array into an object using the first values as keys of the object.
This is my array:
let testArr = ["id", "ser", "add", "1", "asd", "82.255", "2", "ert", "82.119", "3", "pol", "82.250"];
This is what I need:
let newArr = [
{
"id": "1",
"ser": "asd",
"add": "82.255"
},
{
"id": "2",
"ser": "ert",
"add": "82.119"
},
{
"id": "3",
"ser": "pol",
"add": "82.250"
}
]
In result I store the data of the array that I want to use as keys of the object:
let chunk = 3;
let result = testArr.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index/chunk)
if(!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [] // start a new chunk
}
resultArray[chunkIndex].push(item)
return resultArray
}, [])
My problem is that I don't know how to use that data as keys of the object and I would like to know how to convert the result variable into a generic function for any array and any number.
You can iterate over the testArr array using forEach and check the index. if it's a multiple of 3 you can assign an object with the values and push that object to the new array.
For your case, i've modified the answer to a generic case where you can pass the starting index of the values in the original array
let testArr = ["id", "ser", "add", "1", "asd", "82.255", "2", "ert", "82.119", "3", "pol", "82.250"];
const arr = [];
function genericArrayFormatter(checkIdx){
testArr.forEach((val, idx) => {
if (idx > checkIdx - 1 && idx % checkIdx === 0){
const obj = {};
let i = 0;
let index = idx;
while (checkIdx > i){
obj[testArr[i++]] = testArr[index++];
}
arr.push(obj);
}
});
}
genericArrayFormatter(3);
console.log(arr);
Sorry if this is a dumb question but everything I try seems to be wrong! (I am new to Swift).
I have 2 arrays of Strings which I need to compare for matches and then return an array with the index position of those matches .... IE:
let array1 = ["abc", "def", "ghi", "jkl", "xyz", "uhr"]
let array2 = ["ghi", "xyz", "uhr"]
// Search array1 for instances of array2
// Result I want is: [2, 4, 5]
Is there a simple function I am missing?! Thanks in advance for your help.
For an efficient solution you can create an index first. The index maps each element in the first array to its position in the array. Both arrays are traversed only once:
let array1 = ["abc", "def", "ghi", "jkl", "xyz", "uhr"]
let array2 = ["ghi", "xyz", "uhr"]
let index = Dictionary(uniqueKeysWithValues: array1.enumerated().map {
($0.element, $0.offset)
})
let result = array2.compactMap { index[$0] }
print(result) // [2, 4, 5]
If the elements in array1 are not known to be unique then the index must be computed slightly differently:
let index = Dictionary(array1.enumerated().map { ($0.element, $0.offset) },
uniquingKeysWith: { (first, _) in first })
The second parameter is a closure which controls which value to put into the dictionary in the case of duplicate keys. Here we choose the position of the first occurrence of an element in the array.
var indexArr: [Int] = []
for element in array2 {
if let elementIndex = array1.firstIndex(of: element) {
indexArr.append(elementIndex)
}
}
print(indexArr)
var results: [Int] = []
for i in 0..<array1.count {
for j in 0..<array2.count {
if array1[i] == array2[j] {
results.append(i)
}
}
}
print(results)
I've got an array of fonts which each have a familyName and a fontName.
I would like to transform them into an array of tuples in the form (familyName: String, fontNames: [String]).
I feel like there should be an easy functional way to do this, but can't work it out. The closest I've got is two calls to reduce: First into a dictionary and then into an array.
let dictionary = fonts.reduce(into [String : [String]]() ) { result, font in
let array = result[font.fontFamily] ?? []
result[fontFamily] = array + [font.fontName]
}
let array = dictionary(into: [(String, [String])]() ) { result, element in
result.append( (element.key, element.value.sorted()) )
}.sorted { $0.0 < $1.0 }
I'm also sorting the array of tuples and the array of fontNames in the array of tuples.
Is there a way I can avoid the intermediary dictionary?
Many thanks.
Update
I created a playground to show sanjaykmwt the results of their suggestions:
struct Font {
let family: String
let name: String
}
let fonts = [
Font(family: "ABC", name: "abc"),
Font(family: "ABC", name: "def"),
Font(family: "ABC", name: "ghi"),
Font(family: "XYZ", name: "xyz"),
Font(family: "XYZ", name: "uvw")
]
let sortedFamily = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
})
let dict = sortedFamily.map({["family":$0.family,
"fonts":$0.name]})
print("dict: \(dict)")
Output:
dict: [["family": "ABC", "fonts": "abc"], ["family": "ABC", "fonts": "def"], ["family": "ABC", "fonts": "ghi"], ["family": "XYZ", "fonts": "xyz"], ["family": "XYZ", "fonts": "uvw"]]
if You have an array of Fonts with fontFamily, fontName
you can make grouping then map
// Array Of Fonts Example
let array = [Font.init(fontFamily: "Cago", fontName: "AA"),
Font.init(fontFamily: "Cago", fontName: "CCCC"),
Font.init(fontFamily: "Mango", fontName: "AAsss"),
Font.init(fontFamily: "Mango", fontName: "mngoo")]
// Grouping
let groupedByFamilayName = Dictionary.init(grouping: array) {$0.fontFamily}
// Map
let arrayOfTuple = groupedByFamilayName.map { (key,array) -> (String,[String]) in
return (key,array.map({$0.fontName}))
}
print(arrayOfTuple)
Expanding (or contracting!) on Abdelahad Darwish's answer…
let tuples = Dictionary(grouping: fonts) { $0.family }
.map { (familyName: $0.key, fontNames: $0.value.map { $0.name }) }
print(tuples)
[(familyName: "XYZ", fontNames: ["xyz", "uvw"]), (familyName: "ABC", fontNames: ["abc", "def", "ghi"])]
let sortedFamily = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
})
let dict = sortedFamily.map({["family":$0.family,"fonts":$0.fonts.sorted()]})
try and print the dict you will get everything sorted
if you want even shorter it can be:
let dict = fonts.sorted(by: { (lhs, rhs) -> Bool in
return lhs.family < rhs.family
}).map({["family":$0.family,"fonts":$0.fonts.sorted()]})
I need to find the most common dictionary in an array of swift dictionaries. I tried using the following :
func frequencies
<S: SequenceType where S.Generator.Element: Hashable>
(source: S) -> [(S.Generator.Element,Int)] {
var frequency: [S.Generator.Element:Int] = [:]
for x in source {
frequency[x] = (frequency[x] ?? 0) + 1
}
return sorted(frequency) { $0.1 > $1.1 }
}
But I can't invoke 'frequencies' with an argument list of type [[String:String]](). How can I edit the above function to take an array of dictionaries, or use another method entirely?
As the comments have mentioned, the problem is that the type [String:String] is not Hashable.
A (much) less efficient solution when your types are not hashable is to fall back to Comparable (could sort and generate running totals) or Equatable or, worst case, require the caller to supply a isEquivalent closure. Then you go hunting through your running frequencies searching for an equivalent item (and if you don't find one, insert it with a frequency of 1).
Here is an implementation that does that in Swift 2.0:
extension SequenceType {
func frequencies(#noescape isEquivalent: (Generator.Element,Generator.Element) -> Bool) -> [(Generator.Element,Int)] {
var frequency: [(Generator.Element,Int)] = []
for x in self {
// find the index of the equivalent entry
if let idx = frequency.indexOf({ isEquivalent($0.0, x)}) {
// and bump the frequency
frequency[idx].1 += 1
}
else {
// add a new entry
frequency.append(x,1)
}
}
return frequency.sort { $0.1 > $1.1 }
}
}
Since there is an implementation of == that compares two dictionaries, so long as those dictionaries contain values that are equatable, you can call it like this:
let dicts = [
["name": "David", "number": "1"],
["name": "John", "number": "2"],
["name": "David", "number": "1"],
]
// you can use `==` in two dictionaries that contain an equatable value,
// such as String here:
dicts[0] == dicts[1] // false
dicts[0] == dicts[2] // true
// so you can call frequencies like so:
dicts.frequencies(==)
which returns:
[(["number": "1", "name": "David"], 2),
(["number": "2", "name": "John"], 1)]
edit: here is a Swift 1.2 version, unfortunately complicated by the absence of a version of find in 1.2 (renamed indexOf in 2.0) that takes a predicate. This ought to work, but I don't have a working copy of 1.2 in this machine so you may need to fix any syntax errors:
extension Array {
// add missing indexOf to Array as 1.2 doesn't have an equivalent
func indexOf(#noescape predicate: T->Bool) -> Int? {
for idx in indices(self) {
if predicate(self[idx]) { return idx }
}
return nil
}
}
func frequencies<S: SequenceType>
(source: S, #noescape isEquivalent: (S.Generator.Element,S.Generator.Element) -> Bool) -> [(S.Generator.Element,Int)] {
var frequency: [(S.Generator.Element,Int)] = []
for x in source {
// find the index of the equivalent entry
if let idx = frequency.indexOf({ isEquivalent($0.0, x)}) {
// and bump the frequency
frequency[idx].1 += 1
}
else {
// add a new entry
frequency.append(x,1)
}
}
return sorted(frequency) { $0.1 > $1.1 }
}
frequencies(dicts, ==)
You could use a NSCountedSet which provides this kind of functionality:
let arr = [
[
"foo": "bar"
],
[
"foo": "bar"
],
[
"foo": "baz"
],
]
let countedSet = NSCountedSet(array: arr)
for dict in countedSet {
println(countedSet.countForObject(dict))
println(dict)
}
prints
2
["foo": "bar"]
1
["foo": "baz"]
If you're having problems with the initializer not woking properly, use:
let countedSet = NSCountedSet()
countedSet.addObjectsFromArray(arr)
instead of
let countedSet = NSCountedSet(array: arr)
I haven't tested with Swift 2, but the usage should be more or less the same.
(I don't know if this works with Swift 1.2)
You can also use a global function:
func frequencies<K, V where K : Hashable, V: Equatable>(dicts: [[K : V]]) -> [(dict: [K : V], count: Int)] {
var counts = [(dict: [K : V], count: Int)]()
for dict in dicts {
if let index = counts.indexOf({ $0.dict == dict }) {
counts[index].1++
} else {
counts.append(dict: dict, count: 1)
}
}
return counts.sort { $0.1 > $1.1 }
}
Use like this
let dicts = [
["name": "David", "number": "1"],
["name": "John" , "number": "2"],
["name": "David", "number": "1"],
]
print(frequencies(dicts))
outputs
[(["number": "1", "name": "David"], 2),
(["number": "2", "name": "John" ], 1)]