Comparing String against array of String and counting the matches in Swift - arrays

I'm trying to compare a string (userInput) against an array of strings (groupA) to check how many of the items in groupA are present in the userInput.
var groupA = ["game of thrones", "star wars", "star trek" ]
var userInput = "My name is oliver i love game of thrones, star wars and star trek."
var count = 0
func checking() -> Int {
for item in groupA {
// alternative: not case sensitive
if userInput.lowercased().range(of:item) != nil {
count + 1
}
}
return count
}
func Printer() {
print(count)
}

Your code is not very well designed, since you're using a lot of globals, but it will work with a few minor changes:
var groupA = ["game of thrones", "star wars", "star trek" ]
var userInput = "My name is oliver i love game of thrones, star wars and star trek."
var count = 0
func checking() -> Int {
for item in groupA {
// alternative: not case sensitive
if userInput.lowercased().range(of:item) != nil {
count += 1 //Make this `count += 1`
}
}
return count
}
func printer() {
print(count)
}
//Remember to call `checking()` and `printer()`
checking()
printer()
Also note that you should name all functions beginning with a lower-case letter, so Printer() should be printer().
Consider this code instead:
import UIKit
var groupA = ["game of thrones", "star wars", "star trek" ]
var userInput = "My name is oliver i love game of thrones, star wars and star trek."
//The `checking` function has been rewritten as `countOccurerences(ofStringArray:inString)`,
//and now takes parameters and returns a value.
func countOccurrences(ofStringArray stringArray: [String], inString string: String) -> Int {
var result = 0
for item in stringArray {
// alternative: not case sensitive
if string.lowercased().range(of:item) != nil {
result += 1
}
}
return result
}
//And the `printer()` function now takes parameter as well.
func printer(_ count: Int) {
print("count = \(count)")
}
//Here is the code to use those 2 functions after refactoring
let count = countOccurrences(ofStringArray: groupA, inString: userInput)
printer(count)

To make the above code work, you just need to change count + 1 in line 18 to count += 1 I've posted the complete code below.
import Cocoa
import Foundation
var groupA = ["game of thrones", "star wars", "star trek" ]
var userInput = "My name is oliver i love game of thrones, star wars and star trek."
var count = 0
func checking() -> Int {
for item in groupA {
// alternative: not case sensitive
if userInput.lowercased().range(of:item) != nil {
count += 1
}
}
return count
}
func Printer() {
print(count)
}
checking()
Printer()

Related

Searching and Editing Values in Swift Array or Dictionary

I have a method which is supposed to return a Set of Strings. Here is a method description:
Returns: 10 product names containing the specified string.
If there are several products with the same name, producer's name is added to product's name in the format "<producer> - <product>",
otherwise returns simply "<product>".
Can't figure out how to check if there are duplicate names in the array and then edit them as required
What I've got so far:
struct Product {
let id: String; // unique identifier
let name: String;
let producer: String;
}
protocol Shop {
func addNewProduct(product: Product) -> Bool
func deleteProduct(id: String) -> Bool
func listProductsByName(searchString: String) -> Set<String>
func listProductsByProducer(searchString: String) -> [String]
}
class ShopImpl: Shop {
private var goodsInTheShopDictionary: [String: Product] = [:]
func addNewProduct(product: Product) -> Bool {
let result = goodsInTheShopDictionary[product.id] == nil
if result {
goodsInTheShopDictionary[product.id] = product
}
return result
}
func deleteProduct(id: String) -> Bool {
let result = goodsInTheShopDictionary[id] != nil
if result {
goodsInTheShopDictionary.removeValue(forKey: id)
}
return result
}
func listProductsByName(searchString: String) -> Set<String> {
var result = Set<String>()
let searchedItems = goodsInTheShopDictionary.filter{ $0.value.name.contains(searchString) }
let resultArray = searchedItems.map{ $0.value }
result = Set(searchedItems.map{ $0.value.name })
if result.count > 10 {
result.removeFirst()
}
return result
}
}
If you want to achieve this you would need to iterate over you resultArray and save producer and product into another array. On each iteration you would need to check if the array allready contains either the product name itself or an allready modified version.
A possible implementation would look like this:
var result = [(producer: String, product: String)]()
// iterate over the first 10 results
for item in resultArray.prefix(10){
if let index = result.firstIndex(where: { _ , product in
product == item.name
}){
// the result array allready contains the exact product name
// so we need to convert the name allready in the list
let oldProduct = (producer: result[index].producer, product: "\(result[index].producer) \(result[index].product)")
result[index] = oldProduct
// add the new one
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
}
else if !result.filter({ $0.product.components(separatedBy: " ").contains(item.name)}).isEmpty {
// if the result array allready contains a modified version of the name
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
} else{
// if the result array does not contain the product yet
result.append((producer: item.producer, product: "\(item.name)"))
}
}
let productNames = result.map{ $0.product}
Please be aware: As you are using a [String: Product], which is a unsorted dictionary, to hold your values this will yield different results (if the resultArray collection is larger than 10) each time you search.
Tested with searchString = name1:
var goodsInTheShopDictionary: [String: Product] = Dictionary(uniqueKeysWithValues: (0...20).map { index in
("\(index)",Product(id: "", name: "name\(index)", producer: "producer\(index)"))
})
goodsInTheShopDictionary["100"] = Product(id: "11", name: "name1", producer: "producer11")
goodsInTheShopDictionary["101"] = Product(id: "12", name: "name1", producer: "producer12")
Result:
["name13", "producer12 name1", "name10", "name19", "producer11 name1",
"name17", "name14", "name18", "producer1 name1", "name16"]

SwiftUI: Change string of a duplicates in array

I am trying to change every duplicate in an array to the same string but with a number of this element in a row at the end. For example change ["text", "word", "text", "text"] into ["text1", "word", "text2", "text3"].
Here are my ideas but they still don't work.
struct Test: View {
#State var array = ["text", "słowo", "text", "text", "siema", "elo", "siema"]
#State var test = [String : [Int]]()
var body: some View {
VStack{
ForEach(array, id:\.self) { i in
Text(i)
}
}.onAppear {
for (index,dateString) in array.enumerated() {
if(test[dateString] == nil){
test[dateString] = [index]
}else{
test[dateString]?.append(index)
}
}
test = test.filter { key, value in
value.count > 1
}
print(test)
var num = 1
for i in array.indices {
if test.keys.contains(array[i]) {
array[i] = array[i] + "\(num)"
num += 1
}
}
print(array)
}
}
}
Please help!!!
This is a straight Swift problem, not SwiftUI. Don't confuse the issue by trying to do that in your View code. This is pretty simple Swift to write:
let array = ["one", "even", "three", "even", "five", "even"]
func replaceInstances(of aString: String, with newString: String, inStringArray stringArray: [String]) -> [String] {
var output = [String]()
var itemCount = 0
for item in stringArray {
if item == aString {
itemCount += 1
output.append("\(newString)\(itemCount)")
} else {
output.append(item)
}
}
return output
}
print(replaceInstances(of: "even", with: "newString", inStringArray: array))
That outputs
["one", "newString1", "three", "newString2", "five", "newString3"]
Or
outputs:
["text1", "słowo", "text2", "text3", "siema", "elo", "siema"]
Let's use one of Swift's awesome superpowers, extensions to the rescue!
Here we teach Swift's array of String a trick to de-duplicate its contents whilst maintaining the order of the elements.
extension Array where Element == String {
var deduplicated : Self {
var occurrences = [String : Int]()
return self.map { item in
guard let count = occurrences[item]
else {
occurrences[item] = 1 // first encounter
return item // unmodified
}
occurrences[item] = count + 1
return "\( item )\( count )"
}
}
}
A quick test...
var array = ["text", "słowo", "text", "text", "siema", "elo", "siema"]
print(array.deduplicated)
// prints: ["text", "słowo", "text1", "text2", "siema", "elo", "siema1"]
The above extension can be hidden away in a sub-folder in your project called "Exntesions" as usual in a file called "Array.swift" as usual.
Now in your view you just need to make this one small change
ForEach(array.deduplicated, id:\.self) { i in
Text(i)
}

How to subtract one array by another array in Swift?

I want to subtract array1 by array2
Example:
var array1 = ["the", "people", "prefer", "to", "go", "to", "the","sun","beach"]
var array2 = ["the", "people", "prefer", "go", "to", "the", "moon","beach"]
I want Output:
["to","sun"]
What I am trying so far:
let reuslt = array1.filter { ! array2.contains($0) }
Output:
["sun"]
it's checking to contain a matching item removing all items if it matches but I want to remove one for one.
Just do it on the computer the way you would do it in your brain. Loop through array2 (not array1). For each element of array2, if that element has a firstIndex in array1, remove the element at that index from array1.
for word in array2 {
if let index = array1.firstIndex(of: word) {
array1.remove(at: index)
}
}
What you effectively want to do is this for loop
for item2 in array2 {
for i in 0..<array1.count {
if item2 == array1[i] {
array1.remove(at: i)
break
}
}
}
Filter works in exactly this way except it doesn't break on the first item but continues to remove all items.
You can also put this into a one liner like this with map instead of filter: array2.map({guard let i = array1.firstIndex(of: $0) else {return}; array1.remove(at: i)})
Like above answers, it is ok for a loop contains findIndex and remove from array.
But in other world, I think if the array is too large, the complexity of firstIndex(of:) and remove(at:) cause time and CPU too much for this task - Heat of device can raise a lots too. You can minimize it by using dictionary.
This is an another approach:
func findTheDiff(_ compareArr: [String], _ desArr: [String]) -> [String] {
var resultArr : [String] = []
var dict : [String: Int] = [:]
for word in compareArr {
if dict[word] == nil {
dict[word] = 1
} else {
dict[word] = dict[word]! + 1
}
}
for checkWord in desArr {
if dict[checkWord] != nil && dict[checkWord]! > 0 {
dict[checkWord] = dict[checkWord]! - 1
continue
}
resultArr.append(checkWord)
}
return resultArr
}
Usage:
var array1 = ["the", "people", "prefer", "to", "go", "to", "the","sun","beach"]
var array2 = ["the", "people", "prefer", "go", "to", "the", "moon","beach"]
var result = self.findTheDiff(array2, array1)
print(result) // ["to", "sun"]
You can find the complexity of firstIndex, remove(at:) below:
https://developer.apple.com/documentation/swift/array/firstindex(of:)
https://developer.apple.com/documentation/swift/array/remove(at:)-1p2pj
#Thang Phi's answer has the right idea. This is not different, but it works with a level of abstraction that incorporates the "counted set" idea for which Swift doesn't yet provide a built-in type:
import OrderedCollections
public extension Sequence where Element: Hashable {
/// A version of this sequence without the earliest occurrences of all `elementsToRemove`.
///
/// If `elementsToRemove` contains multiple equivalent values,
/// that many of the earliest occurrences will be filtered out.
func filteringOutEarliestOccurrences(from elementsToRemove: some Sequence<Element>) -> some Sequence<Element> {
var elementCounts = Dictionary(bucketing: elementsToRemove)
return lazy.filter {
do {
try elementCounts.remove(countedSetElement: $0)
return false
} catch {
return true
}
}
}
}
public extension Dictionary where Value == Int {
/// Create "buckets" from a sequence of keys,
/// such as might be used for a histogram.
/// - Note: This can be used for a "counted set".
#inlinable init(bucketing unbucketedKeys: some Sequence<Key>) {
self.init(zip(unbucketedKeys, 1), uniquingKeysWith: +)
}
/// Treating this dictionary as a "counted set", reduce the element's value by 1.
/// - Throws: If `countedSetElement` is not a key.
#inlinable mutating func remove(countedSetElement: Key) throws {
guard let count = self[countedSetElement] else { throw AnyError() }
self[countedSetElement] = count == 1 ? nil : count - 1
}
}
/// `zip` a sequence with a single value, instead of another sequence.
#inlinable public func zip<Sequence: Swift.Sequence, Constant>(
_ sequence: Sequence, _ constant: Constant
) -> some Swift.Sequence<(Sequence.Element, Constant)> {
zip(sequence, **ModuleName**.sequence(constant))
}
/// An infinite sequence of a single value.
#inlinable public func sequence<Element>(_ element: Element) -> some Sequence<Element> {
let getSelf: (Element) -> Element = \.self
return sequence(first: element, next: getSelf)
}
/// A nondescript error.
public struct AnyError: Error & Equatable {
public init() { }
}
Your example seems a little confusing / incomplete on the output. But sounds like you could do something like this:
extension Array where Element: Hashable {
func difference(from other: [Element]) -> [Element] {
let thisSet = Set(self)
let otherSet = Set(other)
return Array(thisSet.symmetricDifference(otherSet))
}
}
let names1 = ["the", "people", "prefer", "to", "go", "to", "the","sun","beach"]
let names2 = ["the", "people", "prefer", "go", "to", "the", "moon","beach"]
let difference = names1.difference(from: names2)
print(Array(difference)) // ["sun", "moon"]
Using an extension will of course make this code available to all of your arrays in your project. Since we convert the arrays into Sets, duplicates are removed, which may be a issue in your use case.
This Array extension was taken form: https://www.hackingwithswift.com/example-code/language/how-to-find-the-difference-between-two-arrays A vital resoruce for all things swift, especially SwiftUI.
Simple subtraction method that works for Equatable types including String.
extension Array where Element: Equatable {
func subtracting(_ array: Array<Element>) -> Array<Element> {
var result: Array<Element> = []
var toSub = array
for i in self {
if let index = toSub.firstIndex(of: i) {
toSub.remove(at: index)
continue
}
else {
result.append(i)
}
}
return result
}
}
let first = [1, 1, 2, 3, 3, 5, 6, 7, 7]
let second = [2, 2, 3, 4, 4, 5, 5, 6]
let result = first.subtracting(second)
//print result
//[1, 1, 3, 7, 7]

Hash Tables: Ransom Note - Hacker Rank in Swift Timed Out

My code is alright but keeping getting time out on some test cases, any tips to improve this? My guess is the indexOf function taking too long.
func checkMagazine(magazine: [String], note: [String]) -> Void {
var mutableMag = magazine
if note.count > mutableMag.count {
print("No")
return
}
for word in note {
if let index = mutableMag.index(of: word) {
mutableMag.remove(at: index)
} else {
print("No")
return
}
}
print("Yes") }
Please find the challenge in this link: https://www.hackerrank.com/challenges/ctci-ransom-note/problem
One possible solution that passes all tests is using NSCountedSet for storing the words in the note and magazine and comparing the count of each word in note to the count of that word in magazine and if any of them is lower in magazine, making an early return and printing No.
I'd also suggest changing the function signature to return a Bool value even though the function prototype generated by hacker rank returns Void. It's better to make checkMagazine a pure function and not doing any I/O operations in it.
func checkMagazine(magazine: [String], note: [String]) -> Bool {
let magazineWords = NSCountedSet(array: magazine)
let noteWords = NSCountedSet(array: note)
for noteWord in noteWords {
if magazineWords.count(for: noteWord) < noteWords.count(for: noteWord) {
return false
}
}
return true
}
Then you just need to change the end of the generated code to the following:
let magazineWorks = checkMagazine(magazine: magazine, note: note)
if magazineWorks {
print("Yes")
} else {
print("No")
}
func checkMagazine(magazine: [String], note: [String]) -> Void {
var notesDictionary : [String : Int] = [:]
var magazineDictionary : [String : Int] = [:]
var canMakeRansom = true
for n in note {
var count = notesDictionary[n] ?? 0
count += 1
notesDictionary[n] = count
}
for n in magazine {
var count = magazineDictionary[n] ?? 0
count += 1
magazineDictionary[n] = count
}
for note in notesDictionary {
if note.value > magazineDictionary[note.key] ?? 0 {
canMakeRansom = false
}
}
print(canMakeRansom ? "Yes" : "No")
}
This is another way to solve this.
I think this does what NSCountedSet does by itself somehow.

Swift - Convert Array to Dictionary

I just want convert an array of Player Names into a dictionary Scoreboard, giving everyone an initial score of 0.
Meaning...
var playerNames = ["Harry", "Ron", "Hermione"]
becomes...
var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]
This is my first time asking a question, but I’m totally completely stuck on what feels so simple and all how-to's/questions I've found are off in some way. I have tried using reduce in a variety of ways, but always end up short. Thanks in advance!
Here's a quick one liner that I like to use:
let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }
reduce is definitely one of the more difficult builtin functions to use correctly, but it is what you want here.
let names = ["Harry", "Ron", "Hermione"]
let scoreboard: [String: Int] = names.reduce(into: [:], { result, next in
result[next] = 0
})
It takes 2 parameters: the initial value (in our case, an empty dictionary [:]), and a closure that updates the result with each element in the array. This closure has 2 parameters, result and next. We want to update result based on the next element. Our closure does this by setting result[next] to 0.
If the player names are known to be all different then you can do
let playerNames = ["Harry", "Ron", "Hermione", "Ron"]
var scoreBoard = Dictionary(uniqueKeysWithValues: zip(playerNames,
repeatElement(0, count: playerNames.count)))
print(scoreBoard) // ["Harry": 0, "Ron": 0, "Hermione": 0]
Here zip is used to create a sequence of player/score pairs, from which the dictionary is created.
Remark: Originally I had used AnySequence { 0 } to generate the zeros. Using repeatElement() instead was suggested by Alexander and has the advantage that the correct required capacity is passed to the dictionary intializer.
You can use reduce(into:) as you suspected. You simply need to declare the initial value as [String:Int]() to be an empty Dictionary of the required type, then simply set the value of all keys in playerNames to 0.
var playerNames = ["Harry", "Ron", "Hermione"]
var scoreBoard = playerNames.reduce(into: [String:Int](), { currentScores,player in
currentScores[player] = 0
})
Using reduce(into:_:):
var playerNames = ["Harry", "Ron", "Hermione"]
let playerScore = playerNames.reduce(into: [:]) { counts, playerNames in
counts[playerNames, default: 0] += 0
}
print(playerScore)
To keep a count of the players names (eg. duplicate names):
counts[myArray, default: 0] += 1
So for example if Ron had two entries before the game started (score > 0) then you would know.
Without using reduce(into:_:) method and as an extension:
var playerNames = ["Harry", "Ron", "Hermione"]
extension Sequence where Self.Iterator.Element: Hashable {
func freq() -> [Self.Iterator.Element: Int] {
return reduce([:]) {
( iter: [Self.Iterator.Element: Int], element) in
var dict = iter
dict[element] = 0
return dict
}
}
}
print(playerNames.freq())
// ["Harry": 0, "Hermione": 0, "Ron": 0]
keep a count (eg. duplicate names):
dict[element, default: -1 ] += 1
Here is how you can do that:
var playerNames = ["Harry", "Ron", "Hermione"]
var dictionary = [String: Int]()
for player in playerNames {
dictionary[player] = 0
}
Here is another way to do it:
// Implementation
extension Dictionary {
static func from(_ array: [Value], key: KeyPath<Value, Key>) -> Dictionary<Key, Value> {
var dict: Dictionary<Key, Value> = [:]
array.forEach { dict[$0[keyPath: key]] = $0}
return dict
}
}
/// Usage
let array: [String] = ["Banana", "Apple"]
Dictionary.from(array, key: \.self)
// or if you have a more complex object
struct Foo {
let id: Int
}
let array2: [Foo] = [Foo(id: 1), Foo(id: 2)]
Dictionary.from(array2, key: \.id)
Based on jmad8 answer
Details
Swift 5.3
Xcode 12.0.1 (12A7300)
Solution
extension Sequence {
func toDictionary<Key: Hashable, Value>(where closure: (Element) -> (Key, Value)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
let components = closure(element)
result[components.0] = components.1
}
}
func toCompactDictionary<Key: Hashable, Value>(where closure: (Element) -> ((Key, Value)?)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
guard let components = closure(element) else { return }
result[components.0] = components.1
}
}
}
Usage
// Sample 1
print(languages.toDictionary { (string) -> (Character, String) in
return (string.first!, string)
})
print(languages.toCompactDictionary { (string) -> (Character, String)? in
guard let character = string.first, character != Character("J") else { return nil }
return (character, string)
})
// Sample 2
print(languages.enumerated().toDictionary { (data) -> (Int, String) in
return (data.offset, data.element)
})
// Shorter version of sample 2
print(languages.enumerated().toDictionary { ($0.offset, $0.element) })
// Sample 3
struct Order {
let id: Int
let desctiption: String
}
let orders = [
Order(id: 0, desctiption: "Apple"),
Order(id: 1, desctiption: "Banana"),
Order(id: 2, desctiption: "watermelon")
]
print(orders.toDictionary { ($0.id, $0) })

Resources