Query items between x and y with Firebase - ios

So, I am using the official Hacker News API and it is hosted on Firebase.
The problem I am having is that I want to get a subset of a list basically.
Something along the way of.
let topNewsRef = firebase.childByAppendingPath("topstories").queryLimitedToFirst(UInt(batchSize + offset)).queryLimitedToLast(UInt(batchSize))
[I know this does not work but I would like this effect]. Basically I want a subset of a set, specified by a range; from item 2 to item 15, for example].
Say I want 50 items from the 75th item, but the above is not working. So the question is; how do I achieve the same effect?
For example; given a list of 100 items in Firebase. I want all item from the 50th and 75th. There is no property that gives away the order of the items.
Here is my current solution;
let topNewsRef = firebase.childByAppendingPath("topstories").queryLimitedToFirst(UInt(batchSize + offset))
var handle: UInt?
handle = topNewsRef.observeEventType(.Value) { (snapshot: FDataSnapshot!) -> Void in
if let itemIDs = snapshot.value as? [Int] {
itemIDs.dropFirst(offset) // This drops all items id I already fetched ...
for itemID in itemIDs {
let itemRef = self.firebase.childByAppendingPath("item/\(itemID)")
var itemHandle: UInt?
itemHandle = itemRef.observeEventType(.Value, withBlock: { (snapshot: FDataSnapshot!) -> Void in
if let itemHandle = itemHandle {
itemRef.removeObserverWithHandle(itemHandle)
}
if let json = snapshot.value as? [String:AnyObject],
// Handle JSON ...
}
})
}
}
if let handle = handle {
topNewsRef.removeObserverWithHandle(handle)
}
} // offset += batchSize
... which is gettting all items from the start (offset) to the end (batchSize + offset), then I drop the first end of the list by the size of offset. Thus leaving on the list with a size of batchSize left.

Following our comments to your question,
You can use .queryOrderedByKey in combination with queryStartingAtValue:, queryEndingAtValue:
The data in /topstories is stored as an array. when ordering by key, the indices of the objects in the array are the keys. Then, all you need to do is cast the offset and batchSize as strings and pass them to queryStartingAtValue: & queryEndingAtValue: like so:
ref.queryOrderedByKey().queryStartingAtValue(String(offset)).queryEndingAtValue(String(offs‌​et+batchSize-1));
Don't forget to subtract 1 for the endingValue if you are incrementing offset by batchsize.
For the nth query in the series,
startingAtIndex = initialOffset + (batchSize * (n - 1))
endingAtIndex = initialOffset + (batchSize * n) - 1
For example, let batchSize=100 and the initial offset=0.
The first 100 items are keys 0 through 99. The key (index) of the first item in the array is 0.
If you query for endingAt(offset+batchSize), you will be ending at index 100, which is the 101st item in the array.
Thus, you will be getting 101 items -- one more than your batch size.
the second query will then be for items with indices 100-199 (starting at 0+batchsize and ending at 0+batchsize+batchsize-1), which is 100 items.
Here is an example PLNKR of the same concept implemented in JavaScript.
:)

Okay, so the problem was solved with the following query.
firebase.childByAppendingPath("topstories").queryOrderedByKey().queryStartingAtValue(String(offset)).queryEndingAtValue(String(offset + batchSize - 1))
The thing is that the Firebase documentation does not specify that the "AnyObject!" parameters can be used as indices on the query. They also forget to mention that the indices should be of type String.

Related

How Can I Construct an Efficient CoreData Search, Including Allowing For Preceding and Trailing Characters Here?

Based on straight SQL searches in a previous app, I am adding CoreData searching to a new app. These searches are in a custom dictionary db that the app contains; this function does the work:
public func wordMatcher (pad: Int, word: Array<String>, substitutes : Set<String> ) {
let context = CoreDataManager.shared.persistentContainer.viewContext
var query: Array<String>
var foundPositions : Set<Int> = []
var searchTerms : Array<String> = []
if word.count >= 4 {
for i in 0..<word.count {
for letter in substitutes {
query = word
query[i] = letter
searchTerms.append(query.joined())
let rq: NSFetchRequest<Word> = Word.fetchRequest()
rq.predicate = NSPredicate(format: "name LIKE %#", query.joined())
rq.fetchLimit = 1
do {
if try context.fetch(rq).count != 0 {
foundPositions.insert(i)
break
}
} catch {
}
}
// do aggregated searchTerms search here instead of individual searches?
}
}
}
The NSFetchRequest focuses on one permutation at a time. But I'm accumulating the search string fragments in the array searchTerms because I don't know if it would be more efficient to construct a single query connected with ORs, and I also don't know how to do that in CoreData.
The focus is on the positions in the original term word: I need to indicate if any given location has at least one of the substitutes as a valid fit. So to implement the aggregate searchTerms approach, a FetchRequest would have to happen for each location in the base term.
A second complication is the one referred to in the title of the question. I am using LIKE because the search term in the FetchRequest could be a substring in a longer word. However, the maximum number of letters is 11, and pad is the starting point of the original term in that field of 11 spaces.
So if pad is 3, then I would need to allow for 0..<pad preceding characters. And because there may be trailing characters, I would also want results with 0..<(11 - (pad + word.count)) alphabetic characters after the last letter in the search term.
Regex seems like one way to do this, but I haven't found a clear example of how to do this in this case, and especially with the multiple search terms (if that's the way to go). The limits of SQLite in the previous version forced constructing multiple queries with increasing numbers of "_" underscores to indicate the padding characters; that tended to really explode the number of queries.
BTW, substitutes is limited to an absolute maximum of 9 values, and in practice is usually below 5, so things are a little more manageable.
I would like to get a grip on this, and so if anyone can provide direction or examples that can make this a reasonably efficient function, the help is appreciated greatly.
EDIT:
I've realized that I need a result for each position in the target string, with cases where the leading and trailing spaces also may need to contain a substitute as well.
So I'm moving to this:
public func wordMatcher (pad: Int, word: Array<String>, substitutes : Set<String> ) {
let context = CoreDataManager.shared.persistentContainer.viewContext
var pad_ = pad
var query: Array<String>
var foundPositions : Set<Int> = []
let rq: NSFetchRequest<Word> = Word.fetchRequest()
rq.fetchLimit = 1
let subs = "[\(substitutes.joined())]"
// if word.count >= 4 { // because those locations will be blocked off anyway otherwise
let start = pad > 0 ? -1 : 0
let finish = 11 - (pad + word.count) > 0 ? word.count + 1 : word.count
for i in start..<finish {
query = word
var _pad = 11 - (pad + word.count)
if i == -1 {
query = Array(arrayLiteral: subs) + query
pad_ -= 1
} else if i > word.count {
query.append(subs)
_pad -= 1
} else {
pad_ = pad
query[i] = subs
}
let endPad = _pad > 0 ? "{0,\(_pad)}" : ""
let predMatch = ".\(query.joined())\(endPad)"
print(predMatch)
rq.predicate = NSPredicate(format:"position <= %# AND word MATCHES %#", pad_, predMatch)
do {
if try context.fetch(rq).count != 0 {
foundPositions.insert(i)
}
} catch {
}
// }
}
lFreq = foundPositions
}
This relies on a regex substitution, inserted into the original target string. What I'll have to find out is if this is fast enough at the edge cases, but it may not be critical even in the worst case.
predMatch will end up looking something like "ab[xyx]d{0,3}", and I think I can get rid of the position section by changing it to be "{0,2}ab[xyx]d{0,3}". But I guess I'm going to have to try to find out.

Swift Filter and Map over array of structs

I may have this really wrong. Noob. I've recreated what I'm doing in a playground type environment here. Basically the sender is a slider within a UITableView of other sliders. The myData is the underlying data. I want to perform a calculation to all the items of the underlying data EXCEPT the one which corresponds to the sender item. I have no idea if my closure syntax is correct. This is the first time I'm creating one.
// sender comes over as a struct
struct myStruct {
var tag: Int = 0
var value: Float = 0
}
let sender = myStruct(tag: 1, value: 199)
// some vars for the calculation
let globalTotal: Float = 597
let globalAnotherTotal: Float = 0
// an array of data structs
struct myDataStruct {
var name: String = ""
var value: Float = 0
}
var myData: [myDataStruct] = []
myData.append(myDataStruct(name: "Tom", value: 45.0))
myData.append(myDataStruct(name: "Dick", value: 16.4))
myData.append(myDataStruct(name: "Harry", value: 12.3))
// a closure to do the calculation
var calcOtherVals: (Float, Float) -> (Float) = { (startVal, senderStartVal) in
let remainingStartVals = globalTotal - senderStartVal
let remainingNewVal = globalTotal - sender.value - globalAnotherTotal
let endVal = ((startVal * (100 / remainingStartVals)) / 100) * remainingNewVal
return endVal
}
// now need to perform calcOtherVals on all the .value floats in myData EXCEPT the element at position sender.tag hopefully using filter and map
So basically I'm trying to use filter and map and the calcOtherVals closure to edit the array of structs in place. I can do this with conditionals and loops and calcOtherVals as a function no problem. Just hoping to do it more elegantly.
QUESTION: As in the code comment, I need to perform calcOtherVals on all the .value floats in myData EXCEPT the element at position sender.tag. How?
myData.enumerated().flatMap { (index, element) in return index != sender.tag ? calcOtherVals (element.value) : nil }
Few bits of swift magic here. Firstly enumerate() returns an array of tuples containing the element, and the index of said element.
Next flatMap(). This is essentially map but it ignores any transform that resolves to nil. Great for converting from an optional array to a flat array, and also great if you wish to do a map+filter operation such as this.
-- Updated --
If you're comfortable with implicit arguments, you can reduce it further:
myData.enumerated().flatMap { $0.offset != sender.tag ? calcOtherVals ($0.element.value) : nil }
So as I understood you need to filter your array
something like
let filteredData = myData.filter({$0.tag != sender.tag})
then you use reduce to calculate
let sumAll = filterdData.reduce(0, {$0.value + $1.value})
This Question is Already have an answer,
for better understanding you can review this very good tutorial about map, filter and reduce
Link:- https://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/

Using arc4random to generate ten random numbers

I am using arc4random to generate 10 random numbers so I can then query firebase to get the questions that contain the randomly generated numbers. The problem is that I don't want any number to appear more than once so then there are not duplicate questions. Current code below...
import UIKit
import Firebase
class QuestionViewController: UIViewController {
var amountOfQuestions: UInt32 = 40
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Use a for loop to get 10 questions
for _ in 1...10{
// generate a random number between 1 and the amount of questions you have
let randomNumber = Int(arc4random_uniform(amountOfQuestions - 1)) + 1
print(randomNumber)
// The reference to your questions in firebase (this is an example from firebase itself)
let ref = Firebase(url: "https://test.firebaseio.com/questions")
// Order the questions on their value and get the one that has the random value
ref.queryOrderedByChild("value").queryEqualToValue(randomNumber)
.observeEventType(.ChildAdded, withBlock: {
snapshot in
// Do something with the question
print(snapshot.key)
})
}
}
#IBAction func truepressed(sender: AnyObject) {
}
#IBAction func falsePressed(sender: AnyObject) {
}
}
You can have an array to store the value you want to random with, in your case, [1,2,3....10], and then use arc4random to get the random index of any value inside (0..9), get the value and remove it from array. Then you will never get the same number from the array.
Given the total number of questions
let questionsCount = 100
you can generate a sequence of integers
var naturals = [Int](0..<questionsCount)
Now given the quantity of unique random numbers you need
let randomsCount = 10
that of course should not exceed the total number of questions
assert(randomsCount <= questionsCount)
you can build your list of unique integers
let uniqueRandoms = (1..<randomsCount).map { _ -> Int in
let index = Int(arc4random_uniform(UInt32(naturals.count)))
return naturals.removeAtIndex(index)
}
As an alternative to generating the random number on the client and requesting a question at that specified number, you could download the entire array of questions and shuffle the array. GameKit provides a built-in method to shuffle the array.
import GameKit
// ...
let ref = Firebase(url: "https://test.firebaseio.com/questions")
// Order the questions on their value and get the one that has the random value
ref.queryOrderedByChild("value")
.observeEventType(.ChildAdded, withBlock: {
snapshot in
// Shuffle your array
let shuffledQuestions = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(snapshot)
// Store your array somewhere and iterate through it for the duration of your game
})
You could generate random numbers and store each number in an NSArray. However, when you append it to the array you can check if the array already contains that number.
For example:
for _ in 1...10 {
let amountOfQuestions: UInt32 = 40
var intArray: [Int] = []
let randomNumber = Int(arc4random_uniform(amountOfQuestions - 1)) + 1
if intArray.contains(randomNumber) {
repeat {
randomNumber = Int(arc4random_uniform(amountOfQuestions - 1)) + 1
} while intArray.contains(randomNumber)
if !intArray.contains(randomNumber) {
intArray.append(randomNumber)
}
} else {
intArray.append(randomNumber)
}
print(intArray)
}
After that you can make a FireBase request with your uniquely generated integers. I hope I was able to help :).
Create unsorted Array of ten random Int 0..99
var set = Set<Int>()
while set.count < 10 {
let num = Int(arc4random_uniform(100))
set.insert(num)
}
let arr = Array(set)

Swift: Create Section index for list of names

I have written an algorithm to create a section index for a tableview.
Unfortunately I have a bug when the list contains only one item the result is empty.
Do you have an elegant solution for that?
var sections : [(index: Int, length :Int, title: String)] = Array()
func createSectionIndices(participants: List<Participant>){
sections.removeAll()
var index = 0;
let array = participants.sort({$0.lastName < $1.lastName})
for i in 0.stride(to: array.count, by: 1){
let commonPrefix = array[i].lastName.commonPrefixWithString(array[index].lastName, options: .CaseInsensitiveSearch)
if (commonPrefix.isEmpty) {
let string = array[index].lastName.uppercaseString;
let firstCharacter = string[string.startIndex]
let title = "\(firstCharacter)"
let newSection = (index: index, length: i - index, title: title)
sections.append(newSection)
index = i;
}
}
print("sectionCount: \(sections.count)")
}
Here's a one line solution to build the sections list:
var participants:[(firstName:String, lastName:String)] =
[
("John", "Smith"),
("Paul", "smith"),
("Jane", "Doe"),
("John", "denver"),
("Leo", "Twain"),
("Jude", "law")
]
// case insensitive sort (will keep "denver" and "Doe" together)
participants = participants.sort({$0.lastName.uppercaseString < $1.lastName.uppercaseString})
// The process:
// - get first letter of each name (in uppercase)
// - combine with indices (enumerate)
// - only keep first occurrence of each letter (with corresponding indice)
// - build section tuples using index, letter and number of participants with name begining with letter
let sections = participants
.map({String($0.lastName.uppercaseString.characters.first!)})
.enumerate()
.filter({ $0 == 0 || !participants[$0 - 1].lastName.uppercaseString.hasPrefix($1) })
.map({ (start,letter) in return
(
index: start,
length: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)}).count,
title: letter
)
})
// sections will contain:
// (index:0, length:2, title:"D")
// (index:2, length:1, title:"L")
// (index:3, length:2, title:"S")
// (index:5, length:1, title:"T")
You may already have a lot of existing code based on the sections being stored in an array of tuples but, if not, I would suggest you approach this a little differently and build your sections array with the letter AND the participant data.
let sections = participants
.map({ String($0.lastName.uppercaseString.characters.first!) })
.reduce( Array<String>(), combine: { $0.contains($1) ? $0 : $0 + [$1] })
.map({ (letter) in return
(
title: letter,
participants: participants.filter({$0.lastName.uppercaseString.hasPrefix(letter)})
)
})
This would allow you to respond to the number of sections with sections.count but will also make it easier to manipulate index paths and data within each section:
number of participants in a section : sections[index].participants.count
participant at index path : sections[indexPath.section].participants[indexPath.row]
This is just syntactic candy but if you have a lot of references to the participants list, it will make the code more readable.
Also, if your participants are objects rather than tuples or structs, you can even update the data in you main participant list without having to rebuild the sections (unless a last name is changed).
[EDIT] fixed errors in last tuple syntax
[EDIT2] Swift 4 ...
The dictionaries in Swift 4 provide a much easier way to manage this kind of thing.
for the original reference structure:
let sections = [Character:[Int]](grouping:participants.indices)
{participants[$0].lastName.uppercased().first!}
.map{(index:$1.reduce(participants.count,min), length:$1.count, title:String($0))}
.sorted{$0.title<$1.title}
.
and for a section structure that contains its own sub-lists of participants (my recommendation):
let sectionData = [Character:[Participant]](grouping:participants)
{$0.lastName.uppercased().first!}
.map{(title:$0, participants:$1)}
.sorted{$0.title<$1.title}

Looping JSON from Parse database giving wrong results

Swift 2.0, xcode 7.1
I am trying to retrieve some data from Parse database, filter it to remove duplicate and store in a dictionary. Each row of parse has orders placed by customer (JSON shown below) and I want to retrieve this in UITableView to show the order placed. If the customer has placed multiple orders recently, I want to filter that and show all of his orders in one section of table view under his customer ID.
Filtering is working, but for some reason my loop is not giving me accurate results.
Parse Row 1:
[{"Customer":"9sKSDTG7GY","Product":"Burger","Quantity":"2"}]
Parse Row 2:
[{"Customer":"nyRHskbTwG","Product":"Sizzler","Quantity":"2"},{"Customer":"nyRHskbTwG","Product":"Biryani","Quantity":"2"}]
Retrieved this data and stored in self.custome, self.fQuantity and self.fName variable.
The loop I am using is as below:
let cD = self.customer
print("Customer data before filtering Unique value: \(self.customer)")
self.uniqueValues = self.uniq(cD) //Calling a function to get unique values in customer data
print("Customer data after filtering Unique value: \(self.uniqueValues)")
var newArray = [[String]]()
for var count = 0; count < self.customer.count; count++ {
for sID in self.uniqueValues {
if sID.containsString(self.customer[count]){
let dicValue = [String(self.fQuantity[count]), String(self.fName[count])]
newArray.append(dicValue)
self.dicArray.updateValue(newArray, forKey: sID)
} else {
// Do nothing...
}
}
}
print("Dictionary Values: \(Array(self.dicArray.values))")
print("Dictionary Keys: \(Array(self.dicArray.keys))")
Printed output is as below:
Customer data before filtering Unique value: ["9sKSDTG7GY",
"nyRHskbTwG", "nyRHskbTwG"]
Customer data after filtering Unique value: ["9sKSDTG7GY",
"nyRHskbTwG"]
Dictionary Values: [[["2", "Burger"], ["2", "Sizzler"],
["2", "Biryani"]], [["2", "Burger"]]]
Dictionary Keys: ["nyRHskbTwG", "9sKSDTG7GY"]
Can someone figure out what I am doing wrong?
As #David suggested, you have to swap outer and inner loop. But I also had to delete all values contained in newArray if there was nothing found in the if loop. So this is how I made it work.
let cD = self.customer
print("Customer data before filtering Unique value: \(self.customer)")
self.uniqueValues = self.uniq(cD) //Calling a function to get unique values in customer data
print("Customer data after filtering Unique value: \(self.uniqueValues)")
var newArray = [[String]]()
for sID in self.uniqueValues {
for var count = 0; count < self.customer.count; count++ {
if sID.containsString(self.customer[count]){
let dicValue = [String(self.fQuantity[count]), String(self.fName[count])]
newArray.append(dicValue)
self.dicArray.updateValue(newArray, forKey: sID)
} else {
newArray.removeAll() // ****** Adding this works for me
}
}
}
print("Dictionary Values: \(Array(self.dicArray.values))")
print("Dictionary Keys: \(Array(self.dicArray.keys))")
You filtered the data, but are looping through the un-filtered list of customers.
for var count = 0; count < self.customer.count; count++ {

Resources