I want to make a list of tasks that can change their order, but I am not sure how to store this in a database.
I don't want to use array because I have to do some queries further in future.
Here is the screenshot of my database:
I'm trying to make something like Trello where the user adds tasks and can move tasks upward and downward according to their priority. I need to change the position of the tasks in the database as well to maintain the record. I'm unable to understand how to do that in any database. I'm an experienced developer and I have worked with mongodb and firebase but this is something unique for me.
Here is the code to create and get all tasks. When I try to move some task in collection. I maintained an index in each task.
Let's say when I move a task from the position of index 5 to index 2 then I have to edit all the upcoming indexes by +1. Is there some way I can avoid doing this?
Code Sample
class taskManager {
static let shared = taskManager()
typealias TasksCompletion = (_ tasks:[Task],_ error:String?)->Void
typealias SucessCompletion = (_ error:String?)->Void
func addTask(task:Task,completion:#escaping SucessCompletion){
Firestore.firestore().collection("tasks").addDocument(data: task.toDic) { (err) in
if err != nil {
print(err?.localizedDescription as Any)
}
completion(nil)
}
}
func getAllTask(completion:#escaping TasksCompletion){
Firestore.firestore().collection("tasks")
.addSnapshotListener { taskSnap, error in
taskSnap?.documentChanges.forEach({ (task) in
let object = task.document.data()
let json = try! JSONSerialization.data(withJSONObject: object, options: .prettyPrinted)
var taskData = try! JSONDecoder().decode(Task.self, from: json)
taskData.id = task.document.documentID
if (task.type == .added) {
Task.shared.append(taskData)
}
if (task.type == .modified) {
let index = Task.shared.firstIndex(where: { $0.id == taskData.id})!
Task.shared[index] = taskData
}
})
if error == nil{
completion(Task.shared,nil)
}else{
completion([],error?.localizedDescription)
}
}
}
}
I think the question you're trying to ask about is more about database design.
When you want to be able to keep order with a group of items while being able to reorder them you will need a column to keep the order.
You run into an issue when you try to order them if they are sequentially ordered.
Example
For example if you wanted to move Item1 behind Item4:
Before
An item with an ordering index.
1. Item1, order: 1
2. Item2, order: 2
3. Item3, order: 3
4. Item4, order: 4
5. Item5, order: 5
6. Item6, order: 6
After
Problem: we had to update every record between the item being moved and where it was placed.
Why this is a problem: this is a Big O(n) - for every space we move we have to update that many records. As you get more tasks this becomes more of an issue as it will take longer and not scale well. It would be nice to have a Big O(1) where we have a constant amount of changes or as few as possible.
1. Item2, order: 1 - Updated
2. Item3, order: 2 - Updated
3. Item4, order: 3 - Updated
4. Item1, order: 4 - Updated
5. Item5, order: 5
6. Item6, order: 6
Possible Solution #1 (OK Maybe?) - Spacing
You could try to come up with a crafty method where you try to space the order numbers out so that you have holes that can be filled without updating multiple records.
This could get tricky though, and you may think, "Why not store Item1 at order: 4.5" I added a related question below that goes into that idea and why you should avoid it.
You may be able to verify the safety of the order client side and avoid hitting the database to determine the new order ID of the move.
This also has limitations as you may have to rebalance the spacing or maybe you run out of numbers to items. You may have to check for a conflict and when a conflict arises you perform a rebalance on everything or recursively the items around the conflict making sure that other balancing updates don't cause more conflicts and that additional conflicts are resolved.
1. Item2, order: 200
2. Item3, order: 300
3. Item4, order: 400
4. Item1, order: 450 - Updated
5. Item5, order: 500
6. Item6, order: 600
Possible Solution #2 (Better) - Linked Lists
As mentioned in the related link below you could use a data structure like a linked list. This retains a constant amount of changes to update so it is Big O(1). I will go into a linked list a bit in case you haven't played with the data structure yet.
As you can see below this change only required 3 updates, I believe the max would be 5 as shown in Expected Updates. You may be thinking, "Well it took about that many with the first original problem/example!" The thing is that this will always be a max of 5 updates compared to the possibility of thousands or millions with the original approach [Big O(n)].
1. Item2, previous: null, next: Item3 - Updated // previous is now null
2. Item3, previous: Item2, next: Item4
3. Item4, previous: Item3, next: Item1 - Updated // next is now Item1
4. Item1, previous: Item4, next: Item5 - Updated // previous & next updated
5. Item5, previous: Item1, next: Item4 - Updated // previous is now Item1
6. Item6, previous: Item6, next: null
Expected Updates
Item being moved (previous, next)
Old previous item's next
Old next item's previous
New previous item's next
New next item's previous
Linked Lists
I guess I used a double linked list. You probably could get away with just using a single linked list where it doesn't have a previous attribute and only a next instead.
The idea behind a linked list is to think of it a chain link, when you want to move one item you would decouple it from the link in front of it and behind it, then link those links together. Next you would open up where you would want to place it between, now it would have the new links on each side of it, and for those new links they would now be linked to the new link instead of each other.
Possible Solution #3 - Document/Json/Array Storage
You said you want to stay away from arrays, but you could utilize document storage. You could still have a searchable table of items, and then each collection of items would just have an array of item id/references.
Items Table
- Item1, id: 1
- Item2, id: 2
- Item3, id: 3
- Item4, id: 4
- Item5, id: 5
- Item6, id: 6
Item Collection
[2, 3, 4, 1, 5, 6]
Related Question(s)
Storing a reorderable list in a database
Resources on Big O
A guide on Big O
More on Big O
Wiki Big O
Other Considerations
Your database design will depend on what you're trying to accomplish. Can items belong to multiple boards or users?
Can you offload some ordering to the client side and allow it to tell the server what the new order is? You should still avoid inefficient ordering algorithms on the client side, but you can get them to do some of the dirty work if you trust them and don't have any issues with data integrity if multiple people are working on the same items at the same time (those are other design problems, that may or may not be related to the DB, depending on how you handle them.)
I was stuck on the same problem for a long time. The best solution I found was to order them Lexicographically.
Trying to manage a decimal rank (1, 2, 3, 4...) runs into a lot of problems that are all mentioned in other answers on this question. Instead, I store the rank as a string of characters ('aaa', 'bbb', 'ccc'...) and I use the character codes of the characters in the strings to find a spot between to ranks when adjustments are made.
For example, I have:
{
item: "Star Wars",
rank: "bbb"
},
{
item: "Lord of the Rings",
rank: "ccc"
},
{
item: "Harry Potter",
rank: "ddd"
},
{
item: "Star Trek",
rank: "eee"
},
{
item: "Game of Thrones",
rank: "fff"
}
Now I want to move "Game of Thrones" to the third slot, below "Lord of the Rings" ('ccc') and above "Harry Potter" ('ddd').
So I use the character codes of 'ccc' and 'ddd' to mathematically find the average between the two strings; in this case, that ends up being 'cpp' and I'll update the document to:
{
item: "Game of Thrones",
rank: "cpp"
}
Now I have:
{
item: "Star Wars",
rank: "bbb"
},
{
item: "Lord of the Rings",
rank: "ccc"
},
{
item: "Game of Thrones",
rank: "cpp"
},
{
item: "Harry Potter",
rank: "ddd"
},
{
item: "Star Trek",
rank: "eee"
}
If I run out of room between two ranks, I can simply add a letter to the end of the string; so, between 'bbb' and 'bbc', I can insert 'bbbn'.
This is a benefit over decimal ranking.
Things to be aware of
Do not assign 'aaa' or 'zzz' to any item. These need to be withheld to easily allow for moving items to the top or bottom of the list. If "Star Wars" has rank 'aaa' and I want to move something above it, there would be problems. Solvable problems, but this is easily avoided if you start at rank 'bbb'. Then, if you want to move something above the top rank, you can simply find the average between 'bbb' and 'aaa'.
If your list gets reshuffled frequently, it would be good practice to periodically refresh the rankings. If things are moved to the same spot in a list thousands of times, you may get a long string like 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbn'. You may want to refresh the list when a string gets to be a certain length.
Implementation
The algorithm and an explanation of the functions used to achieve this effect can be found here. Credit for this idea goes to the author of that article.
The code I use in my project
Again, credit for this code goes to the author of the article I linked above, but this is the code I have running in my project to find the average between two strings. This is written in Dart for a Flutter app
import 'dart:math';
const ALPHABET_SIZE = 26;
String getRankBetween({String firstRank, String secondRank}) {
assert(firstRank.compareTo(secondRank) < 0,
"First position must be lower than second. Got firstRank $firstRank and second rank $secondRank");
/// Make positions equal
while (firstRank.length != secondRank.length) {
if (firstRank.length > secondRank.length)
secondRank += "a";
else
firstRank += "a";
}
var firstPositionCodes = [];
firstPositionCodes.addAll(firstRank.codeUnits);
var secondPositionCodes = [];
secondPositionCodes.addAll(secondRank.codeUnits);
var difference = 0;
for (int index = firstPositionCodes.length - 1; index >= 0; index--) {
/// Codes of the elements of positions
var firstCode = firstPositionCodes[index];
var secondCode = secondPositionCodes[index];
/// i.e. ' a < b '
if (secondCode < firstCode) {
/// ALPHABET_SIZE = 26 for now
secondCode += ALPHABET_SIZE;
secondPositionCodes[index - 1] -= 1;
}
/// formula: x = a * size^0 + b * size^1 + c * size^2
final powRes = pow(ALPHABET_SIZE, firstRank.length - index - 1);
difference += (secondCode - firstCode) * powRes;
}
var newElement = "";
if (difference <= 1) {
/// add middle char from alphabet
newElement = firstRank +
String.fromCharCode('a'.codeUnits.first + ALPHABET_SIZE ~/ 2);
} else {
difference ~/= 2;
var offset = 0;
for (int index = 0; index < firstRank.length; index++) {
/// formula: x = difference / (size^place - 1) % size;
/// i.e. difference = 110, size = 10, we want place 2 (middle),
/// then x = 100 / 10^(2 - 1) % 10 = 100 / 10 % 10 = 11 % 10 = 1
final diffInSymbols =
difference ~/ pow(ALPHABET_SIZE, index) % (ALPHABET_SIZE);
var newElementCode = firstRank.codeUnitAt(secondRank.length - index - 1) +
diffInSymbols +
offset;
offset = 0;
/// if newElement is greater then 'z'
if (newElementCode > 'z'.codeUnits.first) {
offset++;
newElementCode -= ALPHABET_SIZE;
}
newElement += String.fromCharCode(newElementCode);
}
newElement = newElement.split('').reversed.join();
}
return newElement;
}
There are several approaches you might follow to achieve such functionality.
Approach #1:
You can give your task distant positions instead of continuous position, something like this:
Date: 10 April 2019
Name: "some task name"
Index: 10
...
Index: 20
...
Index: 30
Here are total 3 tasks with position 10, 20, 30. Now lets say you wanted to move third task in the middle, simply change the position to 15, now you have three task with position 10, 15, 20, I am sure you can sort according to the position when getting all tasks from the database, and I also assume that you can get positions of tasks because user will be re arranging the tasks on a mobile app or web app so you can easily get the positions of surrounding tasks and calculate the middle position of surrounding tasks,
Now lets say you wanted to move the first task(which now have possition index 10) in the middle, simply get the positions of surrounding tasks which is 15 and 20 and calculate the middle which is 17.5 ( (20-15)/2=17.5 ) and here you go, now you have positions 15, 17.5, 20
Someone said there is infinity between 1 and 2 so you are not going to run our of numbers I think, but still of you think you will run out of division soon, you can increase the difference and you can make it 100000...00 instead of 10
Approach #2:
You can save all of your tasks in the same document instead of sperate document in stratified json form, something like this:
Tasks: [ {name:"some name",date: "some date" },{name:"some name",date: "some date"},{name:"some name",date: "some date" } ]
By doing this you will get all task at once on the screen and you will parse the json as local array, when user rearrange the task you will simply change the position of that array element locally and save the stratified version of the tasks in database as well, there are some cons of this approach, if you are using pagination it might be difficult to do so but hopefully you will not be using the pagination in task management app and you probably wanted to show all task on the scree at the same time just like Trello does.
I am new in Swift.
I am trying to make a budget application. This app have a Calculator like keyboard. My idea is when users enter the money app will automatically add a decimal place for users.
For example, if you type 1230 it will give you 12.30 and type 123 it will display 1.23
I wrote a couple lines of code down below. The problem is it only can add decimal point after first digit it won't go backwards when you give more digits. It only can display as X.XXXXX
I tried solve this problem with String.index(maybe increase index?) and NSNumber/NSString format. But I don't know this is the right direction or not.
let number = sender.currentTitle!
let i: String = displayPayment.text!
if (displayPayment.text?.contains("."))!{
displayPayment.text = i == "0" ? number : displayPayment.text! + number
}
else {
displayPayment.text = i == "0" ? number : displayPayment.text! + "." + number
}
Indexing Strings in Swift is not as "straightforward" as many would like, simply due to how Strings are represented internally. If you just want to add a . at before the second to last position of the user input you could do it like this:
let amount = "1230"
var result = amount
if amount.characters.count >= 2 {
let index = amount.index(amount.endIndex, offsetBy: -2)
result = amount[amount.startIndex..<index] + "." + amount[index..<amount.endIndex]
} else {
result = "0.0\(amount)"
}
So for the input of 1230 result will be 12.30. Now You might want to adjust this depending on your specific needs. For example, if the user inputs 30 this code would result in .30 (this might or might not be what you want).
I am a complete beginner at Swift/programming and am making a noughts and crosses app as part of an online course. I wanted to do this on my own before I saw the solution so my logic may be a bit weird.
I have a function which is called each time a button is pressed (there are 9, one for each square). The function acts to:
i) update the number of turns (which allows me to see who's go it is)
ii) change the picture to X or O
iii) deactivate the button after each turn
iv) calculate is anyone has won by changing the value of that squares potential lines. The numbers I have chosen to do this are 3 and 4 (for noughts and crosses), hence the c = 3 or 4 below. This means that if any winning line adds up to 9 or 12, the game stops as someone has won.
I have 9 variables (Int) that hold the score for each square - they are a1o, a2o, a3o, b1o... i.e all end in "o". In the function I want to add the string of "a1" in front of the "o", meaning each button is only relevant to itself with only one line of code above the function (within the button parentheses).
The function looks like this at the moment; calling it by: button(a1, buttonValue: "a1")
func button(buttonName: UIButton, buttonValue: NSString) -> String {
let a = buttonName
var b = buttonValue
b = (b as String) + "o"
print(b)
// the above prints "a1o", the name of the variable, but it is a string...
index += 1
print("Go number \(index)")
if index % 2 == 0 {
// show a nought
a.setImage(UIImage(named: "nought.png"), forState: UIControlState.Normal)
c = 3
// the above line doesn't work as its a string, but I am attempting to set the squares value to 3 (i.e. a1o = 3)
winner()
a.userInteractionEnabled = false
} else {
// show a cross
a.setImage(UIImage(named: "cross.png"), forState: UIControlState.Normal)
c = 4
// the above line doesn't work as its a string
winner()
a.userInteractionEnabled = false
}
return "done"
}
What I can't figure out is how to change the common stem of the variable *a1*o by not making it a string, or if I do, how I convert it back to a variable.
I am struggling with definitions and as a result looking for an answer has been hard. The variable a1o is an integer, but how do I refer to a1o itself?
Thank you in advance,
Sam
It is not possible to access variables dynamically by name like you want to do. However, there are better ways to accomplish the same goal. How about a two-dimensional array?
var scores: [[Int]] = [[0,0,0], [0,0,0], [0,0,0]]
scores[0][1] = 4 //this is the new version of "a2o = 4"
Unlike with variable names, the indices to the array ("0" and "1" in this case) can be dynamically chosen. Or, if you really want to keep your buttons' values exactly the same, you could use a dictionary:
var scores: [String: Int] = [:]
scores[buttonValue as String] = 4
I've started to read the "Exercises in programming style" book recently and one of the tasks there is to implement each programming style in a language of your choice. I decided to go with Scala (I'm fairly new to it) and I'm already stuck with the first "good old school" style. The constraints are:
Very small amount of primary memory, typically orders of magnitude smaller than the data that needs to be processed/generated. (The example sets the limit to 1024 cells)
No labels -- i.e. no variable names or tagged memory addresses. All we have is memory that is addressable with numbers.
Original example (which reads a file line by line and counts the words) is in Python and goes like this:
data = []
data.append([]) # data[1] is line (max 80 characters)
data.append(None) # data[2] is index of the start_char of word
data.append(0) # data[3] is index on characters, i = 0
data.append(False) # data[4] is flag indicating if word was found
data.append('') # data[5] is the word
data.append('') # data[6] is word,NNNN
data.append(0) # data[7] is frequency
...
f = open(sys.argv[1])
# Loop over input file's lines
while True:
data[1] = [f.readline()]
...
So we see there are some variables (f and data) but the main idea is to keep it to a minimum and use python array as a bunch of "memory addresses".
Is it even possible to implement old-school-programming style (no variable names or tagged memory addresses) in Scala? Specifically is there a way to avoid "line" variable when reading file content?
for (line <- Source.fromFile("example.txt").getLines) {
println(line.toUpperCase)
}
Reading the file content into an array similar to the original example doesn't work because it's doesn't have an extractor (value data is not a case class, nor does it have an unapply/unapplySeq member).
P.S. I'm very well aware of the fact that the whole task is probably a 5-liner in Scala but that's not the point.
Sure you can abstain from introducing variables besides the data-array (and solve the problem imperative-style). Simply put everything into your array instead of assigning it to a local variable.
Obviously, the code will be a nightmare, because the array won't be typed and you won't have any meaningful names for any of your data, but I'm assuming that's what you're aiming for with this exercise.
import scala.io.Source
/**
* data 0 : file as line iterator
* data 1 : index of first unused data cell
* data 2 : current line
* data 3 : index of the first letter of the current word
* data 4 : index of the last letter of the current word
* data 5 : current word
* data 6 : temp index to find already initialized words
* data 7 : flag: Word found
* data 8, 10, 12, ... words
* data 9, 11, 13, ... frequencies
*/
object GoodOldSchool {
def main(args: Array[String]): Unit = {
val data: Array[Any] = new Array[Any](1024)
data(0) = Source.fromFile(args(0)).getLines()
data(1) = 8 // first free cell
while (data(0).asInstanceOf[Iterator[String]].hasNext) {
data(2) = data(0).asInstanceOf[Iterator[String]].next()
data(3) = 0 // index first letter of current word
data(4) = 0 // index last letter of current word
// find index last letter of current word
while (data(4).asInstanceOf[Int] < data(2).asInstanceOf[String].length) {
// find the next space (we ignore punctuation)
while (data(4).asInstanceOf[Int] < data(2).asInstanceOf[String].length && data(2).asInstanceOf[String].charAt(data(4).asInstanceOf[Int]) != ' ') {
data(4) = data(4).asInstanceOf[Int] + 1
}
data(5) = data(2).asInstanceOf[String].substring(data(3).asInstanceOf[Int], data(4).asInstanceOf[Int]) // current word
data(6) = 8 // cell index
data(7) = false // word already found
8 until data(1).asInstanceOf[Int] by 2 foreach { _ =>
// Here, we do a case-sensitive word comparison
if (data(5) == data(data(6).asInstanceOf[Int])) {
data(data(6).asInstanceOf[Int] + 1) = data(data(6).asInstanceOf[Int] + 1).asInstanceOf[Int] + 1 // increment frequency
data(7) = true
}
data(6) = data(6).asInstanceOf[Int] + 2
}
if (data(7) == false) {
// create new frequency, because word was not discovered before
data(data(1).asInstanceOf[Int]) = data(5) // set word
data(data(1).asInstanceOf[Int] + 1) = 1 // set frequency
data(1) = data(1).asInstanceOf[Int] + 2 // used up two cells, update index of next free cell
}
// move to next word
data(3) = data(4).asInstanceOf[Int] + 1
data(4) = data(3)
}
}
data foreach println // let's have a look at our result
}
}
To get the total words count in the given file, the following scala code can be used:
Source.fromFile("example.txt")
.getLines.map { line => line.trim.split(" ").length}
.reduceLeft { _ + _ }
Does anyone know how to determine the dimensions of a multidimensional array in Swift? For example, the function below needs to accept a [[AnyObject]] of unknown size. I then need to iterate through the rows of one column.
public convenience init(variableNames:[String], observations: [[AnyObject]]){
self.init()
for i in 0..<numRows {
//do something with one column of observations
}
}
The total number of elements is provided by
observations.count
which is not useful in this case. Ideally I would be able to do:
(rows, cols) = observations.size
or something similar, but I haven't been able to find any such method. Going the long way I can determine the number of columns by
let cols = observations[i].count
but because
let columnOfArray = observations[][i]
does not seem to be allowed I am stuck trying to determine the number of rows. Does anyone have an answer?
What about this?
var a : [[AnyObject]] = [[1,2,3],[4,5,6]]
var row = a.count
var col = a[0].count
println("row = \(row)") // "row = 2"
println("col = \(col)") // "col = 3"
But be carreful because this var a : [[AnyObject]] = [[1,2,3],[4,6]]
will return the same since the [[AnyObject]] is not a rectangular, each array in it can have different size.
If you know that all lines have same number of row, you are safe, else, there is no solution, you will have to count cols of each rows.
for(var i=0;i<row;++i)
{
println("col = \(a[i].count)")
}
This returns:
"col = 3"
"col = 2"