I have an array of custom objects and want to know how to identify which objects are duplicates. I know how to remove the duplicates but that is not functionality I'm after. Im using swift 2.
Example:
var movies: [Movie] = ["Batman v Superman: Dawn of Justice", "Batman v Superman: Dawn of Justice", "Deadpool"," "Deadpool", "Hardcore Henry", "Jason Bourne", "Jurassic World"]
So i want to show a table view with the list of movies above but with "Batman" and "Deadpool" highlighted.
For more clarity on what I'm trying to achieve look at the screenshot. I have a list of movies that two users select in a previous view controller. I want to display the selected movies in a table view. I want to specifically show if there were movies that both people selected.
Based on your comment I have made a simple example with string arrays, which can easily be converted to your movie type:
let movies = ["Batman","Batman","Flash","Avengers"]
var movieCounts:[String:Int] = [:]
for movie in movies {
movieCounts[movie] = (movieCounts[movie] ?? 0) + 1
}
And you can test it like so:
for (key, value) in movieCounts {
print("\(key) has been selected \(value) time/s")
}
I generally try to avoid posting answers with code entirely written by someone else (MIT License), but the reference below is such a neat fit for this question that I believe it's worth including as an answer.
The solution use the same technique as the accepted answer, but on a more general form (made compact with a neat subscript extension to Dictionary): the freq() dictionary extension from GitHub user oisdk's excellent SwiftSequence framework (MIT License):
/* ---------------------------------------------------------------------------
source: GitHub user oisdk:
https://github.com/oisdk/SwiftSequence/blob/master/Sources/Categorise.swift */
private extension Dictionary {
subscript(key: Key, or or: Value) -> Value {
get { return self[key] ?? or }
set { self[key] = newValue }
}
}
public extension SequenceType where Generator.Element : Hashable {
// MARK: Frequencies
/**
Returns a dictionary where the keys are the elements of self, and
the values are their respective frequencies
```swift
[0, 3, 0, 1, 1, 3, 2, 3, 1, 0].freqs()
// [2: 1, 0: 3, 3: 3, 1: 3]
```
*/
#warn_unused_result
func freqs() -> [Generator.Element:Int] {
var freqs: [Generator.Element:Int] = [:]
for el in self { freqs[el, or: 0] += 1 }
return freqs
}
}
/* --------------------------------------------------------------------------- */
/* example usage */
let movies = ["Batman","Batman","Flash","Avengers"]
print(movies.freqs()) // ["Avengers": 1, "Flash": 1, "Batman": 2]
Have a look at the framework for lots of other sequence goodies:
https://github.com/oisdk/SwiftSequence
Why not adding an id in the Movie object
and compare the two arrays searching the same object.
public class Movie:Equatable{
var id=NSUUID().UUIDString
}
public func ==(lhs: Movie, rhs: Movie) -> Bool{
return lhs.id == rhs.id
}
Comparing the arrays:
var moviesA=[Movie]()
var moviesB=[Movie]()
var sharedMovies=[Movie]()
for movie in moviesA{
if moviesB.contains(movie){
sharedMovies.append(movie)
}
}
Not sure what functionalities you want to make.
If just for list of items, you can use swift dictionary to count duplicates, by using movie name as key and count as value starting by 0.
If you want to highlight, you can use different style when you populate the table in the delegate method by checking if the item has duplicates.
Related
I apologize in advance, this is hard to explain. I will provide more detail if needed.
This is the Constants struct that I use to reference UIButtons in a collection array and use as keys for dictionaries.
struct Constants {
static let scoreA = "score_a"
static let scoreB = "score_b"
static let scoreC = "score_c"
static let scoreD = "score_d"
static let constantsArray = [kScoreA, kScoreB, kScoreC, kScoreD]
enum Scores: Int, CaseIterable { case scoreA = 1, ScoreB, ScoreC, ScoreD}
}
My initial view controller has a lot of UIButtons. All the score UIButtons are tagged from 1 and up. The UIButtons are hooked to an IBOutlet UIButton array. This way I can avoid having too many IBOutlets
#IBOutlet var collectionOfScoreButtons: Array<UIButton>!
I reference the UIButtons using code like this throughout my App.
if let scoreAButton = collectionOfScoreButtons[Constants.Scores.scoreA.rawValue - 1]
The UIButtons order is the same as the enum's order e.g. scoreA is the first item in the enum and scoreA button is the first button in the array.
And I can retrieve a dictionary key like this, so I can update its value
// after pushing a score button
func handleScoreValue(tag: Int) {
let scoreKey = Constants.constantScoreArray[tag - 1]
dictionary[scoreKey, default: 0] += 1
}
I am not sure if there is a better way to handle this situation. The code works well, but I feel like there is a better way.
I can't see any advantage of using Scores enum to get reference for certain button, you have to specify index anyway
if let scoreAButton = collectionOfScoreButtons[0]
also you can make your Constants enum and implement CaseIterable protocol which allows you to make array of all enum's cases using Enum.allCases
enum Score: String, CaseIterable {
case A = "score_a"
case B = "score_b"
case C = "score_c"
case D = "score_d"
}
then I believe you have IBAction for your button so you can get index of sender in your array of buttons. Then you don't have to set tag of UIButton
#IBAction func buttonPressed(_ sender: UIButton) {
if let index = collectionOfScoreButtons.index(of: sender) {
handleScoreValue(index: index)
}
}
Finally you can get scoreKey as rawValue of case for certain index in allCases array
func handleScoreValue(index: Int) {
let scoreKey = Score.allCases[index].rawValue
dictionary[scoreKey, default: 0] += 1
}
Why not just use an enum directly ?
enum Constants: String, CaseIterable {
case scoreA = "score_a"
case scoreB = "score_b"
case scoreC = "score_c"
case scoreD = "score_d"
}
So you can loop through your enum cases like
Constants.allCases[anyIndex].rawValue
I am looking to (per this example) add more items to a specific section of a structured array after creating the initial entry.
struct Zoo {
let section: String
let items: [String]
}
var objects = [Zoo]()
let animals = Zoo(section: "Animals", items: ["Cat","Dog","Mouse"])
let birds = Zoo(section: "Birds", items: ["Crow","Pidgeon","Hawk"])
let reptiles = ["Snake","Lizard"]
objects.append(animals)
objects.append(birds)
// ... varous logic and proccessing where I determine I need
// to add two more items to the animals section...
// trying to extend animals with two more entries.
// this is where I am getting hung up:
objects[0].items.append(reptiles)
Remove the following code
objects[0].items.append(reptiles)
Use this code:
objects[0].items += reptiles
Update for Swift 5:
In Swift 5, this solution will not work and you will get an error like
"Left side of mutating operator isn't mutable: 'items' is a 'let'
constant"
The solution is to change the structure :
struct Zoo {
let section: String
var items: [String]
}
My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.
I have this structure: [String: [String: Double]]()
Specifically, something like that: var dictionaries = ["GF": ["ET": 4.62, "EO": 21.0],"FD": ["EE": 80.95, "DE": 0.4]]
How can I easily access and modify nested dictionaries?
EXAMPLE UPDATED: I want to append "TT": 6 at FD and later I want to append another dictionary inside the array. At the end I'll print the results.
for (key,value) in dictionaries {
// if array contains FD, add the record to FD
if key.contains("FD") {
dictionaries["FD"]!["TT"] = 6
}
else {
// if array doesn't contain FD, add FD and add the record to it
dictionaries = dictionaries+["FD"]["TT"] = 6 // <-- I know that it's wrong but I want to achieve this result in this case.
}
}
Result of print will be:
GF -> ET - 4.62, EO - 21.0
FD -> EE - 80.95, DE - 0.4, TT - 6
MISSION: I need to append new dictionary records like in the example above, update existing ones in a simple and straightforward way, loop easily through records to read the values and print them out.
Can anyone help me? Never had the chance to manage dictionaries in Swift since now.
It is not clear what exactly you need but the exercise amused me, so I came up with this solution: we extend Dictionary so that it provides convenience methods if it is a nested dictionary.
First, because of Swift idiosyncrasies, we have to create a dummy protocol to "mark" Dictionary¹:
protocol DictionaryProtocol {
associatedtype Key: Hashable
associatedtype Value
subscript(key: Key) -> Value? { get set }
var keys: LazyMapCollection<[Key : Value], Key> { get }
}
extension Dictionary: DictionaryProtocol {}
Basically, just copy-paste² all declarations you need later from Dictionary to DictionaryProtocol.
Then, you can happily extend away. For instance, add a two-parameter subscript:
extension Dictionary where Value: DictionaryProtocol {
typealias K1 = Key
typealias K2 = Value.Key
typealias V = Value.Value
subscript(k1: K1, k2: K2) -> V? {
get {
return self[k1]?[k2]
}
set {
if self[k1] == nil {
self.updateValue([K2: V]() as! Value, forKey: k1)
}
self[k1]![k2] = newValue
}
}
}
Or an alternative pretty-print³:
extension Dictionary where Value: DictionaryProtocol {
func pretty() -> String {
return self.keys.map { k1 in
let row = self[k1]!.keys.map { k2 in
return "\(k2) - \(self[k1]![k2]!)"
}.joined(separator: ", ")
return "\(k1) -> \(row)"
}.joined(separator: "\n")
}
}
You can also create a type alias for this special dictionary:
typealias D2Dictionary<K: Hashable, V> = Dictionary<K, Dictionary<K, V>>
Going back to the example in your question:
var dictionary = D2Dictionary<String, Double>()
dictionary["GF", "ET"] = 4.62
dictionary["GF", "EO"] = 21.0
dictionary["FD", "EE"] = 80.95
dictionary["FD", "DE"] = 0.4
dictionary["FD", "TT"] = 6
print(dictionary.pretty())
// > GF -> ET - 4.62, EO - 21.0
// > FD -> EE - 80.95, DE - 0.4, TT - 6.0
Background: Only protocols can be used in type bounds on extension conditions.
Make sure to get the types right. If we write var keys: [Key] { get }, for instance, the compiler dies with a seg fault.
Unfortunately, extension Dictionary: CustomStringConvertible where Value: DictionaryProtocol { ... } is not allowed, for whatever reason.
It'll be much simpler if you have the key "FD" you can use
dictionaries["FD"]!["TT"] = 6
to add a new value ["TT":6] or modify existing value of TT to 6. Note that the ! between ["FD"]!["TT"] assumes that ["FD"] exists regardless. You need to check if ["FD"] exists otherwise. Like:
if dictionaries["FD"] != nil {
dictionaries["FD"]!["TT"] = 6
} else {
dictionaries["FD"] = ["TT" : 6]
}
If you need to look for the key you will have to run the entire dictionary like you already tried, but dictionaries support fast enumeration for key and values like
for (key,value) in dictionaries {
if key.contains("FD") { //or any other checks you need to identify your key
value["TT"] = 6
}
}
I haven't found anything on that in Swift. Have found how to find unique values on an array, but not this. Sorry if it sounds quite basic...
But I have the following array
var selectedValues = [String]()
And the following value that comes from a Parse query
var objectToAppend = object.objectForKey("description")! as! String
this is how I'am doing it at the moment.
self.selectedHobbies.append(objectToAppend)
But because the query happens repeated times, it ends up appending repeated values. It works, but I just want to not waste memory and only keep unique values.
Any ideas on how to solve that in swift?
You can use a Set which guarantees unique values.
var selectedValues = Set<String>()
// ...
selectedValues.insert(newString) // will do nothing if value exists
Of course, the elements of a Set are not ordered.
If you want to keep the order, just continue with the Array but check before you insert.
if !selectedValues.contains("Bar") { selectedValues.append("Bar") }
I guess that your problem was resolved but I add my answer for next developers who's facing same problem :)
My solution is to write an extension of Array to add elements from an array with a distinct way:
here the code :
extension Array{
public mutating func appendDistinct<S>(contentsOf newElements: S, where condition:#escaping (Element, Element) -> Bool) where S : Sequence, Element == S.Element {
newElements.forEach { (item) in
if !(self.contains(where: { (selfItem) -> Bool in
return !condition(selfItem, item)
})) {
self.append(item)
}
}
}
}
example:
var accounts: [Account]
let arrayToAppend: [Account]
accounts.appendDistinct(contentsOf: arrayToAppend, where: { (account1, account2) -> Bool in
return account1.number != account2.number
})