SwiftUI - Indexset to index in array - arrays

I am using ForEach within a NavigationView and list combined with a function called when the user deletes a row using .onDelete() as per below.
struct PeriodListView: View {
#ObservedObject var theperiodlist = ThePeriodList()
#EnvironmentObject var theprofile: TheProfile
#State private var showingAddPeriod = false
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
var body: some View {
NavigationView {
List {
ForEach(theperiodlist.periods) {period in
PeriodRow(period: period)
}
.onDelete(perform: removePeriods)
}
.navigationBarTitle("Periods")
.navigationBarItems(trailing:
Button(action: {self.showingAddPeriod = true}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddPeriod) {
AddPeriod(theperiodlist: self.theperiodlist).environmentObject(self.theprofile)
}
}
}
func removePeriods(at offsets: IndexSet) {
AdjustProfileRemove(period: theperiodlist.periods[XXX])
theperiodlist.periods.remove(atOffsets: offsets)
}
I have a separate function (AdjustProfileRemove(period)) which I want to call with the removed period as the variable - e.g. I want to find XXX in AdjustProfileRemove(period: theperiodlist.periods[XXX]). Is there a simple way to do this (I am guessing from IndexSet) or am I missing something fundamental?
Thanks.

.onDelete is declared as
#inlinable public func onDelete(perform action: ((IndexSet) -> Void)?) -> some DynamicViewContent
IndexSet is simply Set of all indexes of the elements in the array to remove. Let try this example
var arr = ["A", "B", "C", "D", "E"]
let idxs = IndexSet([1, 3])
idxs.forEach { (i) in
arr.remove(at: i)
}
print(arr)
so the resulting arr is now
["A", "C", "D"]
The reason, why .onDelete use IndexSet is that more than one row in List could be selected for delete operation.
BE CAREFULL! see the resulting array! Actually removing elements one by one needs some logic ...
Let's try
var arr = ["A", "B", "C", "D", "E"]
let idxs = IndexSet([1, 3])
idxs.sorted(by: > ).forEach { (i) in
arr.remove(at: i)
}
print(arr)
it works now as you expected, is it? the result now is
["A", "C", "E"]
Based on
theperiodlist.periods.remove(atOffsets: offsets)
it seems, that the ThePeriodList already has build-in function with required functionality.
in your case just replace
AdjustProfileRemove(period: theperiodlist.periods[XXX])
with
offsets.sorted(by: > ).forEach { (i) in
AdjustProfileRemove(period: theperiodlist.periods[i])
}

Here is possible approach (taking into account that in general offsets can contain many indexes)
func removePeriods(at offsets: IndexSet) {
theperiodlist.periods =
theperiodlist.periods.enumerated().filter { (i, item) -> Bool in
let removed = offsets.contains(i)
if removed {
AdjustProfileRemove(period: item)
}
return !removed
}.map { $0.1 }
}

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]
}

Swift - How to get indexes of filtered items of array

let items: [String] = ["A", "B", "A", "C", "A", "D"]
items.whatFunction("A") // -> [0, 2, 4]
items.whatFunction("B") // -> [1]
Does Swift 3 support a function like whatFunction(_: Element)?
If not, what is the most efficient logic?
You can filter the indices of the array directly, it avoids the extra mapping.
let items = ["A", "B", "A", "C", "A", "D"]
let filteredIndices = items.indices.filter {items[$0] == "A"}
or as Array extension:
extension Array where Element: Equatable {
func whatFunction(_ value : Element) -> [Int] {
return self.indices.filter {self[$0] == value}
}
}
items.whatFunction("A") // -> [0, 2, 4]
items.whatFunction("B") // -> [1]
or still more generic
extension Collection where Element: Equatable {
func whatFunction(_ value : Element) -> [Index] {
return self.indices.filter {self[$0] == value}
}
}
You can create your own extension for arrays.
extension Array where Element: Equatable {
func indexes(of element: Element) -> [Int] {
return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
}
}
You can simply call it like this
items.indexes(of: "A") // [0, 2, 4]
items.indexes(of: "B") // [1]
You can achieve this by chain of:
enumerated() - add indexes;
filter() out unnecessary items;
map() our indexes.
Example (works in Swift 3 - Swift 4.x):
let items: [String] = ["A", "B", "A", "C", "A", "D"]
print(items.enumerated().filter({ $0.element == "A" }).map({ $0.offset })) // -> [0, 2, 4]
Another way is using flatMap, which allows you to check the element and return index if needed in one closure.
Example (works in Swift 3 - Swift 4.0):
print(items.enumerated().flatMap { $0.element == "A" ? $0.offset : nil }) // -> [0, 2, 4]
But since Swift 4.1 flatMap that can return non-nil objects become deprecated and instead you should use compactMap.
Example (works since Swift 4.1):
print(items.enumerated().compactMap { $0.element == "A" ? $0.offset : nil }) // -> [0, 2, 4]
And the cleanest and the most memory-cheap way is to iterate through array indices and check if element of array at current index equals to required element.
Example (works in Swift 3 - Swift 5.x):
print(items.indices.filter({ items[$0] == "A" })) // -> [0, 2, 4]
In Swift 3 and Swift 4 you can do that:
let items: [String] = ["A", "B", "A", "C", "A", "D"]
extension Array where Element: Equatable {
func indexes(of item: Element) -> [Int] {
return enumerated().compactMap { $0.element == item ? $0.offset : nil }
}
}
items.indexes(of: "A")
I hope my answer was helpful 😊
you can use it like that :
let items: [String] = ["A", "B", "A", "C", "A", "D"]
let indexes = items.enumerated().filter {
$0.element == "A"
}.map{$0.offset}
print(indexes)
just copy and paste
extension Array {
func whatFunction(_ ids : String) -> [Int] {
var mutableArr = [Int]()
for i in 0..<self.count {
if ((self[i] as! String) == ids) {
mutableArr.append(i)
}
}
return mutableArr
}
}
You can use that below code:
var firstArray = ["k","d","r","r","p","k","b","p","k","k"]
var secondArray = ["k","d","r","s","d","r","b","c"]
let filterArray = firstArray.filter { secondArray.contains($0) }
let filterArray1 = firstArray.filter { !secondArray.contains($0) }
let filterIndex = firstArray.enumerated().filter { $0.element == "k" }.map { $0.offset }
print(filterArray) --> // ["k", "d", "r", "r", "k", "b", "k", "k"]
print(filterArray1) --> // ["p", "p"]
print(filterIndex) --> // [0, 5, 8, 9]
this can be a way too
// MARK: - ZIP: Dictionary like
let words = ["One", "Two", "Three", "Four"]
let numbers = 1...words.count
for (word, number) in zip(words, numbers) {
print("\n\(word): \(number)")
}
For example finding the indices of p_last values that are in inds1 array: (swift 4+)
let p_last = [51,42]
let inds1 = [1,3,51,42,4]
let idx1 = Array(inds1.filter{ p_last.contains($0) }.indices)
idx1 = [0,1]

Best way to move an item to the start of a collection

If I have a collection
let initial = [ "a", "b", "c", "d", "e" ]
and I wanted to move an item from that collection to the start (but keep the ordering of the other items intact)
let final = initial.placeFirst { $0 == "b" }
assert(final == [ "b", "a", "c", "d", "e" ])
What would be the best way to implement placeFirst?
My example has the elements as Equatable - that's just to make the question readable, it's sadly not the case in real life, hence a predicate passed into placeFirst which will return true for the item I want at the start.
For my use case there should only be one item which matches the predicate - if more than one matches then putting any (or some, or all) of the matching elements at the start is fine.
I have a few ideas, but it seems like the kind of problem there would be a really neat solution which uses bits of Collection/Sequence I'm not aware of yet.
PS I do realize how much this sounds like a homework question - I promise it's not :)
A possible implementation as a mutating method on RangeReplaceableCollection (Swift 3):
extension RangeReplaceableCollection {
mutating func placeFirst(where predicate: (Iterator.Element) -> Bool) {
if let index = index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
Example:
var array = [ "a", "b", "c", "d", "e" ]
array.placeFirst(where: { $0 == "b" })
print(array) // ["b", "a", "c", "d", "e"]
Similar as in How do I shuffle an array in Swift? you can add a
non-mutating method taking an arbitrary sequence and returning an array:
extension Sequence {
func placingFirst(where predicate: (Iterator.Element) -> Bool) -> [Iterator.Element] {
var result = Array(self)
result.placeFirst(where: predicate)
return result
}
}
Example:
let initial = [ "a", "b", "c", "d", "e" ]
let final = initial.placingFirst { $0 == "b" }
print(final) // ["b", "a", "c", "d", "e"]
A possible implementation as a pair of mutating methods on MutableCollection (doesn't require the resizing of the collection):
extension MutableCollection {
mutating func placeFirst(from index: Index) {
var i = startIndex
while i < index {
swap(&self[i], &self[index]) // in Swift 4: swapAt(i, index)
formIndex(after: &i)
}
}
// in Swift 4, remove Iterator.
mutating func placeFirst(where predicate: (Iterator.Element) throws -> Bool) rethrows {
var i = startIndex
while i < endIndex {
if try predicate(self[i]) {
placeFirst(from: i)
}
formIndex(after: &i)
}
}
}
var initial = ["a", "b", "c", "d", "e", "c", "q"]
initial.placeFirst(where: { $0 == "c" })
print(initial) // ["c", "c", "a", "b", "d", "e", "q"]
In placeFirst(from:), we just take a single index, and swap all the elements from the start index up to the desired index, effectively placing the element at the given index at the start, and "shifting" the remaining elements up.
Then in the predicate version, placeFirst(where:), we iterate through and check the predicate against all the indices of the collection, calling onto placeFirst(from:) if we find a match.
And as Martin says, a non-mutating variant for all sequences can be created easily by first constructing an Array:
extension Sequence {
// in Swift 4, remove Iterator.
func placingFirst(
where predicate: (Iterator.Element) throws -> Bool
) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirst(where: predicate)
return result
}
}
let initial = ["a", "b", "c", "d", "e", "c", "q"]
let final = initial.placingFirst(where: { $0 == "c" })
print(final) // ["c", "c", "a", "b", "d", "e", "q"]
In order to benchmark against Martin's implementation, I changed the implementation of my placeFirst(where:) to only consider the first element that the predicate matches, such that both implementations short-circuit:
extension MutableCollection {
mutating func placeFirstSwap(from index: Index) {
var i = startIndex
while i < index {
swapAt(i, index)
formIndex(after: &i)
}
}
mutating func placeFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
placeFirstSwap(from: index)
}
}
}
extension RangeReplaceableCollection {
mutating func placeFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
extension Sequence {
func placingFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstInsertRemove(where: predicate)
return result
}
func placingFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstSwap(where: predicate)
return result
}
}
Then, with the following setup in a Swift 4 release build:
import Foundation
let a = Array(0 ... 50_000_000)
let i = 33_000_000
print("pivot \(100 * Double(i) / Double(a.count - 1))% through array")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
print("---")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
When i is around 33_000_000, both implementations appear to have similar performance:
pivot 66.0% through array
50000001 Martin's: 0.344986021518707
50000001 Hamish's: 0.358841001987457
---
50000001 Martin's: 0.310263991355896
50000001 Hamish's: 0.313731968402863
With Martin's performing slightly better for values of i over this, e.g with i = 45_000_000:
pivot 90.0% through array
50000001 Martin's: 0.35604602098465
50000001 Hamish's: 0.392504990100861
---
50000001 Martin's: 0.321934998035431
50000001 Hamish's: 0.342424035072327
and mine performing slightly better for values of i less than this, e.g with i = 5_000_000:
pivot 10.0% through array
50000001 Martin's: 0.368523001670837
50000001 Hamish's: 0.271382987499237
---
50000001 Martin's: 0.289749026298523
50000001 Hamish's: 0.261726975440979
In all of these results, the second pair is generally more reliable, as both should benefit from branch prediction done by the first run.

How can I filter an array to make sure it does contain positions of another array

I am filtering my array in the following way:
randomArray = randomArray.filter({m in m.x < firstArray[0].x && ConditionX})
I have a another array secondArray which contains a elements which are in randomArray.
I would like to add another condition (to represent ConditionX in the code above) to the filter to remove all positions in secondArray from randomArray
Despite not understand exactly what you're trying to do here's a swing at basic array filtering.
let array1 = ["a", "b", "c"]
let array2 = ["c", "d", "e"]
var array3 = array1.filter() { return !array2.contains($0) }
That should return:
["a", "b"]
struct MyStruct {
var x: Int = 0
init(_ x: Int) {
self.x = x
}
}
var randomArray = [MyStruct(1), MyStruct(2), MyStruct(3)]
var firstArray = [MyStruct(10)]
var secondArray = [MyStruct(1), MyStruct(2)]
func exist(_ item: MyStruct, in array: [MyStruct]) -> Bool {
for i in array {
if item.x == i.x {
return true
}
}
return false
}
func shouldInclude(_ item: MyStruct, basedOn array: [MyStruct]) -> Bool {
return item.x < array[0].x
}
randomArray = randomArray.filter({ (item) -> Bool in
return shouldInclude(item, basedOn:firstArray) && !exist(item, in:secondArray)
})
print(randomArray) // output: [MyStruct(x: 3)]

Merge Arrays and Remove Duplicates Swift & Firebase

I've got an application that retrieves data from a Firebase Database. My issue is that whenever the data is updated, it retrieves a whole new array of the data (and appends it at the end of the array). For example, if my array is :
["A", "B", "C", "D", "E"]
And I attempt to remove D (in a tableView; when I swipe to delete, it removes the entry from Firebase), the array becomes
["A", "B", "C", "E", "A", "B", "C", "E"]
I have tried using the array removeAll() method before returning the array, but my tableView refreshes before it can finish; so it crashes saying index out of range. Is there anyway that I can create a second array in my database update function, and then call a merge method with my main array, and prevent adding duplicates every time? Thanks!
Code:
self.conditionRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
self.stocks.removeAll()
self.stockObjects.removeAll()
if snapshot.value is NSNull {
print("nothing found")
self.title = "No Results"
}
else {
for i in snapshot.children {
self.stocks.append(i.key)
}
print(self.stocks)
}
for stock in self.stocks {
let stockSnapshot = self.conditionRef.child(stock)
stockSnapshot.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
if snapshot.value is NSNull {
print("nothing found")
} else {
let price = snapshot.childSnapshotForPath("price").value as! String
let volume = snapshot.childSnapshotForPath("volume").value as! String
let stockObj = StockObject(name: stock, price: price, volume: volume)
self.stockObjects.append(stockObj)
print(stockObj.name)
print(stockObj.price)
print(stockObj.volume)
}
self.tableView.reloadData()
})
}
})
UPDATE: I have solved my issue by converting my StockObject class to equatable and adding this function to the class:
func == (lhs: StockObject, rhs: StockObject) -> Bool {
return lhs.name == rhs.name
}
Then, in the code I have above, I have replaced the line where I append to the array with this:
if self.stockObjects.contains(stockObj) == false {
self.stockObjects.append(stockObj)
}
So far, everything works as it should. Any ideas on where this might crash?

Resources