Swift - Find Specific Differences in Arrays of Structs - arrays

If I have a structure, say:
struct Subject {
var subjectID: String?
var name: String?
var note: String?
}
And I have two arrays of this structure: Array1 and Array2.
For example:
Array1 = [(subjectID = "T", name = "H", note = "DF"), (subjectID = "F", name = "H", note = "SD")]
Array2 = [(subjectID = "T", name "G", note = "DF"), (subjectID = "R", name = "F", note = "SDF")]
I want to return a new array, which consists of a subset of elements from Array2 that match the subjectID field of Array1 but have different name and/or note elements.
In the example above, the returned array would be:
[(subjectID = "T", name "G", note = "DF")]
As it contains the same subjectID (in this case T) as in Array1 but the name field is different. Note that the fields for this new returned array should be original values from Array2 (ex: you don't need to correct them to match Array1)
Is there an easy way (ex: one-two lines of code) to do this without brute forcing it?
Thanks!

There are good answers here, I prefer to keep the test simple.
First the setup
struct Subject {
var subjectID: String?
var name: String?
var note: String?
}
let array1 = [Subject(subjectID: "T", name: "H", note: "DF"), Subject(subjectID: "F", name: "H", note: "SD")]
let array2 = [Subject(subjectID: "T", name: "G", note: "DF"), Subject(subjectID: "R", name: "F", note: "SDF")]
Now lets look at the actual algorithm. array2.filter returns an array of Subjects in the array2 in which the block returns true. array1.contains returns true if any of the Subjects in array1 returns true. The test itself is exactly what you described. Are the subject id equal and does either the name or the note differ.
let result = array2.filter { s2 in
array1.contains { s1 in
s1.subjectID == s2.subjectID && (s1.name != s2.name || s1.note != s2.note)
}
}

You can do it like this:
let subjectsByID = Dictionary(grouping: array1, by: { $0.subjectID })
let diff = array2.filter { subject in
if let other = subjectsByID[subject.subjectID]?.first {
return subject.name != other.name || subject.note != other.note
} else {
return false
}
}
It groups the subjects in the first array by ID and then filters the second array based on whether or not there is an entry for that ID with a different name or note. You didn't specify what to do if there are multiple entries in the first array with the same ID so it just looks at the first one.

I used a forEach and filter in combination to find the requested elements
var result = [Subject]()
arr1.forEach( { subject in
result.append(contentsOf: arr2.filter( { $0.subjectID == subject.subjectID &&
($0.name != subject.name ||
$0.note != subject.note) }))
})
To get a little cleaner code the check could be made into a function in the struct
struct Subject {
...
func isModifiedComparedTo(_ subject: Subject) -> Bool {
return self.subjectID == subject.subjectID && (self.name != subject.name || self.note != subject.note)
}
}
var result = [Subject]()
arr1.forEach( { subject in
result.append(contentsOf: arr2.filter({$0.isModifiedComparedTo(subject)}))
})

You could filter the second array elements based on the first array elements, as:
let Array1 = [Subject(subjectID: "T", name: "H", note: "DF"), Subject(subjectID: "F", name: "H", note: "SD")]
let Array2 = [Subject(subjectID: "T", name: "G", note: "DF"), Subject(subjectID: "R", name: "F", note: "SDF")]
let result = Array2.filter { subject -> Bool in
for s in Array1 {
if subject.subjectID == s.subjectID && subject.name != s.name && subject.note != s.subjectID { return true }
}
return false
}
result should contains what are you asking for. Keep in mind that it has the complexity of nested iteration (O(n²)).

Related

Divide array into subarrays

I have the following array, I have to make sure to divide it in this way into subarray, taking into consideration the first part of the name followed by / as a criterion for subdivision, for example "name/other".
Can you give me a hand?
var a = ["origin/a", "origin/b", "origin/c", "remo/a", "remo/d", "remo/c", "next/g"]
var b = {
origin: ["a", "b", "c"],
remo: ["a", "d", "c"],
next: ["g"]
}
You could used reduce(into:_:) to do so:
let reduced = a.reduce(into: [String: [String]]()) { partialResult, currentTerm in
let components = currentTerm.components(separatedBy: "/")
guard components.count == 2 else { return }
partialResult[components[0]] = partialResult[components[0], default: [String]()] + [components[1]]
}
print(reduced)
Output:
$>["remo": ["a", "d", "c"], "next": ["g"], "origin": ["a", "b", "c"]]
One idea is like this:
First we need to separate the keys for the dictionary and all the values that need to be gathered together:
let keysValues = a
.map { $0.components(separatedBy: "/") }
.compactMap { components -> (String, String)? in
guard components.count == 2 else { return nil }
return (components.first!, components.last!)
}
Now we need to reduce that into a dictionary of [String: [String]] by grouping together the values for each key:
var dict: [String: [String]] = [:]
let answer = keysValues.reduce(into: dict) { (d, kv) in
let (k, v) = kv
d[k, default: []] += [v]
}

How can I remove duplicate strings from an array and count them in Swift?

want to print out duplicate characters first and the next number of characters.
like from ["a","a","b","c","c"] to ["a", "2","c","2"].
However, I properly didn't solve this. want you to give me feedback on my code or let me know your wisdom how can you solve this.
import Foundation
var m : [String] = ["a","a","b","c","c"]
var count : Int = 0
var result : [String] = []
for i in 0..<m.count{
for word in m {
if m[i] == word{
count += 1
result.append(word)
}else{
count = 0
}
if count > 1{
result.append(String(count))
}
}
}
print("\(count)")
print("\(result)")
//["a", "a", "2", "a", "a", "2", "b", "c", "c", "2", "c", "c", "2"]
print(["a", "2","c","2"])// want to print it on the console like this.
There is a type for it, NSCountedSet
let m = ["a","a","b","c","c"]
let countedSet = NSCountedSet(array: m)
var result = [String]()
countedSet.forEach {
let count = countedSet.count(for: $0)
if count > 1 {
result.append(contentsOf: [$0 as! String, String(count)])
}
}
print(result)
You can get directly get duplicate value by below method
let fiteredValue = arrayList.filterDuplicates(includeElement: {$0 == $1})

Get items with the same position from multidimensional array in Swift 5

I can't find the best way to do this.
I have an array with 3 arrays in there(this never change)
var ancho = [String]()
var largo = [String]()
var cantidad = [String]()
var arrayDeCortes = [ancho,largo,cantidad]
arrayDeCortes = [[a,b,c,d,..],[e,f,g,h,..],[i,j,k,l,..]]
I need to get this:
[a,e,i]
[b,f,j]
[c,g,k]
[d,h,l]
My problem is that I don't know how many items there is in each array(ancho,largo,cantidad)
and how access to all of them.
I hope you understand me
You can use reduce(into:_:) function of Array like this:
let arrayDeCortes = [["a","b","c","d"],["e","f","g","h"],["i","j","k","l"]]
let arrays = arrayDeCortes.reduce(into: [[String]]()) { (result, array) in
array.enumerated().forEach {
if $0.offset < result.count {
result[$0.offset].append($0.element)
} else {
result.append([$0.element])
}
}
}
print(arrays)
// [["a", "e", "i"], ["b", "f", "j"], ["c", "g", "k"], ["d", "h", "l"]]
Edit: As #Alexander mentioned in the comments, there is a simpler way of achieving this by using zip(_:_:) function twice.
The following will return an array of tuples:
var widths = ["a","b","c","d"]
var heights = ["e","f","g","h"]
var quantities = ["i","j","k","l"]
let result = zip(widths, zip(heights, quantities)).map { width, pair in
(width, pair.0, pair.1)
}
print(result)
// [("a", "e", "i"), ("b", "f", "j"), ("c", "g", "k"), ("d", "h", "l")]

Fetch data from an array with elements of second array

I have one array "users" with all user data and and second array "userIds" having user's id. I have to fetch User from "users" array using "userIds" array
struct User {
let name: String
let id: Int
}
let users: [User] = [User(name: "a", id: 1),
User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 4),
User(name: "d", id: 5)]
let userIds = [2,3,2,5]
result array that I want is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "b", id: 2),
User(name: "d", id: 5)]
so it can have duplicate data according to the data in "userIds".
Now I tried using Higher order function filter:
let result = users.filter { (user) -> Bool in
return userIds.contains(user.id)
}
but this removes the duplicate data and the output is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 5)]
One approach that I tried is using for loop :
var result = [User]()
for i in userIds {
result.append(users.filter({ $0.id == i }).first!)
}
which gives the desired output but if there is a better approach please suggest.
You can solve this using first(where:) to search through users:
let result = userIds.compactMap { desiredDataValue in
users.first(where: { $0.id == desiredDataValue })
}
But if you're doing this a lot, it would probably speed things up if you built a datastructure that allows for fast lookup by the "id" value. You should compare the performance for yourself, and see if you do this enough/frequently enough for it to be worthwhile:
let dictsByData = Dictionary(uniqueKeysWithValues:
users
.lazy
.map { dict in
(key: dict.id, value: dict)
}
)
let result = userIds.compactMap { desiredDataValue in dictsByData[desiredDataValue]! }
result.forEach { print($0) }
Well after digging few more and with the help of this blog:
https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
I tried doing like this:
let results = userIds.compactMap { (int) -> User? in
var matchedUser: User?
if users.contains(where: { (user) -> Bool in
if user.id == int {
matchedUser = user
}
return user.id == int
}) {
return matchedUser
}
return nil
}
and in playground I checked the count the code was executed :
and it seems like the count is less comparing to "for" loop.

Swift: How to replace uppercase symbols in array

This is the code that replaces cyrillic symbols to latin:
let cyr = ["а", "б", "в", "г", "д", "Б"]
let latin = ["a", "b", "v", "g", "d", "B"]
var original = self.input.text
for i in 0..<cyr.count {
let target = cyr[i]
let destination = latin[i]
original = original?.replacingOccurrences(of: target, with: destination, options: String.CompareOptions(rawValue: NSString.CompareOptions.caseInsensitive.rawValue | NSString.CompareOptions.literal.rawValue), range: nil)
self.output.text = original;
But it doesn't replace uppercase symbols to upper if i add them in array. It either replaces all lowercases to upper, either upper to lower. How to make it differentiate if it's uppercase symbol or lower and replace it respectively?
You should not use the caseInsensitive option when you want a case sensitive search and replacement. Your code can also be simplified to
let cyr = ["а", "б", "в", "г", "д", "Б"]
let latin = ["a", "b", "v", "g", "d", "B"]
var text = "абвгдБ"
for (src, dest) in zip(cyr, latin) {
text = text.replacingOccurrences(of: src, with: dest)
}
print(text) // abvgdB
Having said that, what you really might want is a transliteration to latin:
let original = "абвгдБ"
let trans = original.applyingTransform(.toLatin, reverse: false) ?? original
print(trans) // abvgdB
Remove the caseInsensitive option and add the uppercase letters to the arrays.
let cyr = ["а", "б", "в", "г", "д", "Б", "А", ...]
let latin = ["a", "b", "v", "g", "d", "B", "A", ...]
var original = self.input.text
for i in 0..<cyr.count {
let target = cyr[i]
let destination = latin[i]
original = original?.replacingOccurrences(of: target, with: destination, options: [.literal], range: nil)
}
self.output.text = original

Resources