I have a Swift n00b question.
I'm having a hard time understanding why I cannot remove an element from an array.
I first filter it twice to contain only the values I need:
let filteredShowtimes = movieShowtimes.filter{$0.dateTime.laterDate(newStartTime!).isEqualToDate($0.dateTime)}
var furtherFilteredShowtimes = filteredShowtimes.filter{$0.endTime.earlierDate(endTime!).isEqualToDate($0.endTime)}
And, down the line, inside a while loop that depends on the size of the array - but doesn't iterate over it or modify it - I try removing the first element like so:
furtherFilteredShowtimes.removeAtIndex(0)
But the element count remains the same.
Any idea what I'm missing?
Here's the whole code:
while(furtherFilteredShowtimes.count > 0) {
println("showtime \(furtherFilteredShowtimes.first!.dateTime)")
//if the start time of the movie is after the start of the time period, and its end before
//the requested end time
if (newStartTime!.compare(furtherFilteredShowtimes.first!.dateTime) == NSComparisonResult.OrderedAscending) && (endTime!.compare(furtherFilteredShowtimes.first!.endTime) == NSComparisonResult.OrderedDescending) {
let interval = 1200 as NSTimeInterval
//if the matching screenings dict already contains one movie,
//make sure the next one starts within 20 min of the previous
//one
if(theaterMovies.count > 1 && endTime!.timeIntervalSinceDate(newStartTime!) < interval {
//add movie to the matching screenings dictionary
println("we have a match with \(movies[currentMovie.row].title)")
theaterMovies[furtherFilteredShowtimes.first!.dateTime] = movies[currentMovie.row].title
//set the new start time for after the added movie ends
newStartTime = movieShowtimes.first!.endTime
//stop looking for screenings for this movie
break
}
else if(theaterMovies.count == 0) {
//add movie to the matching screenings dictionary
theaterMovies[furtherFilteredShowtimes.first!.dateTime] = movies[currentMovie.row].title
println("we have a new itinerary with \(movies[currentMovie.row].title)")
//set the new start time for after the added movie ends
newStartTime = furtherFilteredShowtimes.first!.endTime
//stop looking for screenings for this movie
break
}
}
else { //if the showtime doesn't fit, remove it from the list
println("removing showtime \(furtherFilteredShowtimes.first!.dateTime)")
furtherFilteredShowtimes.removeAtIndex(0)
}
}
You only say removeAtIndex(0) in one place, in an else.
So if it doesn't happen, that means that line is never being executed because the else is not executed - the if is executed instead.
And you break at the end of each if, so that's the end of the while loop!
In other words, let's pretend that the first two nested if conditions succeed. Your structure is like this:
while(furtherFilteredShowtimes.count > 0) {
if (something) {
if (somethingelse) {
break
break means jump out of the while, so if those two if conditions succeed, that's the end! We never loop. We certainly will never get to the removeAtIndex().
Related
I have a spreadsheet that I use to keep track of climbing progress (snippet shown below). I have formulas and graphs that keep track of counts of specific grades over time, but I am having trouble with a formula to keep a running total (by year) of feet climbed. I intent to put this in another sheet.
Basically I would like a single cell that does something like ... if Sheet1!A:A begins with "21." and if Sheet1!E:E,"<>*%" (which means I actually completed the climb) then add the rows total climb length (Sheet1!J:J * Sheet1!I:I) to the running total for that year.
What is the best way to do this?
You can try using Apps Script and creating a script in order to manage your task.
So for example, you might want to take a look at the snippet below:
Code
function calculateTotal() {
let ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');
let date = ss.getRange('A2:A').getDisplayValues();
let tries = ss.getRange('E2:E').getDisplayValues();
let lengths = ss.getRange('I2:I').getDisplayValues();
let total = 0;
for (let i =0; i<date.length; i++) {
if (date[i][0].toString().startsWith('21') != false && tries[i][0].toString().includes('%') == false) {
total = total+lengths[i][0];
}
}
ss.getRange('M2').setValue(total);
}
Explanation
The script above gathers all the values from the Sheet1 and loops through them. If the conditions check (the date should start with 21 and the E column does not contain %) then the corresponding length is added to the total; the total is then saved in the M2 cell in this case.
Further improvement
The advantage of using a script is that it is versatile and easier to manage. In this situation, you can make use of Apps Script's time-driven triggers; so assuming you plan on updating your spreadsheet every day at a specific time, you can create a trigger which will run right after it.
For example, the below function creates a trigger for the function above which will run every day at ~9.
function createTrigger() {
ScriptApp.newTrigger("calculateTotal")
.timeBased()
.atHour(9)
.everyDays(1)
.create();
}
Reference
Google Apps Script;
Apps Script Installable Triggers.
Thanks Ale13 ... using your example and adding a couple of things (also needed to parseInt totals) ...
function calculateTotal() {
let ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');
let s7 = SpreadsheetApp.getActive().getSheetByName('Sheet7');
let date = ss.getRange('A2:A').getDisplayValues();
let type = ss.getRange('F2:F').getDisplayValues();
let tries = ss.getRange('E2:E').getDisplayValues();
let lengths = ss.getRange('I2:I').getDisplayValues();
let laps = ss.getRange('J2:J').getDisplayValues();
let btotal = 0;
let rtotal = 0;
for (let i =0; i<date.length; i++) {
if (date[i][0].toString().startsWith('21') != false && tries[i][0].toString().includes('%') == false) {
// Totals for Bouldering
if (type[i][0] == "B") {
btotal = btotal + parseInt(lengths[i][0]*laps[i][0]);
}
// Totals for Top Rope or Sport
else {
rtotal = rtotal + parseInt(lengths[i][0]*laps[i][0])
}
}
}
console.log("Roped total = " + rtotal)
console.log("Bouldering total = " + btotal)
s7.getRange('B2').setValue(rtotal);
s7.getRange('B3').setValue(btotal);
}
I have following code:
...
Transaction xodusTransaction = xodusEnvironment.beginReadonlyTransaction();
Store leftStore = xodusEnvironment.openStore(leftName, StoreConfig.USE_EXISTING, xodusTransaction, false);
Store rightStore = xodusEnvironment.openStore(rightName, StoreConfig.USE_EXISTING, xodusTransaction, false);
try(Cursor leftCursor = leftStore.openCursor(xodusTransaction);
Cursor rightCursor = rightStore.openCursor(xodusTransaction)) {
while(leftCursor.getNext()) {
while(rightCursor.getNext()) {
// Do actual work with data from both stores
}
}
}
...
I expect that internal loop will be fired N*M times, where N - cardinality of leftStore and M - cardinality of rightStore.
On practice external loop fires only once and internal loop fires M-times.
If I rewrite the code in following way (flattering nested loops):
...
while(leftCursor.getNext()) {
...
}
while(rightCursor.getNext()) {
...
}
...
Then both loops fires as expected N-times for leftStore and M-times for rightStore.
The question is: is it possible to make nested cursor traveling? If yes, kindly please guide me.
Thank you!
-Taras
Once cursor.getNext() returned false (there is no next key/value pair), it will never return true for this Cursor instance. To traverse a Store again, reopen cursor.
Here is the code traversing two Stores as a matrix, i.e. all pairwise combinations of key/value pairs from both Stores:
try (Cursor leftCursor = leftStore.openCursor(txn)) {
while (leftCursor.getNext()) {
try (Cursor rightCursor = rightStore.openCursor(txn)) {
while (rightCursor.getNext()) {
// Do actual work with data from both stores
}
}
}
}
I loop through an Array and every time a found an object with the category equal to 1 I want to check if there is another object in the rest of the array with this category. I wrote this method
func getNextIndex(startIndex:Int , t : [Client]) -> Int{
var index = startIndex
while(index < t.count && Int(t[index].category!) != 1){
index += 1
}
if(t[index].category == 1){
return index
}else{
return -1
}
}
This return always the same index I use to call the method. If I call it with index+1 the app crashes in the line if(t[index].category == 1)
The behavior you describe is exactly what one would expect from this code.
When you fall out of your "while" loop, either you've found another entry or index has passed the end of the array. The latter case is what is causing your crash.
There are many ways to code this, but to keep your code mostly the same, after the loop, simply test if index is less than t.count. If it is, you have found what you are looking for, return index. If not, then you have gone all the way through the loop without finding what you are looking for, so return -1.
Loop condition ends the iteration in two situations:
When t[index].category is 1 - this is the situation that you are looking for, and your code is handling it.
When index reaches the end of the array - this is the situation when the desired item is not found; you code does not handle this condition, that's why your app crashes.
To fix this, and also to look for the item after the current index, change the code as follows:
func getNextIndex(startIndex:Int , t : [Client]) -> Int {
var index = startIndex
while(index < t.count && Int(t[index].category!) != 1){
index += 1
}
if index == t.count {
return -1
}
// Otherwise, category is 1. Continue searching
while(index < t.count && Int(t[index].category!) != 1){
index += 1
}
return index != t.count ? index : -1
}
While I try to create a for in loop, it gives me an error stating "cant form Range with end < start". Value of i comes 4362626962. How can I fix it?
for i in 1...slashList.count{
let index = slashList[i]
if !quotaList.contains(index+1)
{
slashList.removeAtIndex(index)
}
}
Thank you in advance!
Your for has two problems:
If slashList.count is 0 (because slashList is empty), it would try to count from 1 to 0 adding 1 (which results in an infinite loop), that's why the compiler gives you the error start > end.
if slashList.count is greater than 0 (slashList is not empty), it would use an index which is out of bounds, because you count from 1 to slashList.count, while the indexes go from 0 to slashList.count - 1
to check all indexes it should be:
for i in 0 ..< slashList.count {
// your code
}
to ignore the first element (index 0) do:
for i in 1 ..< slashList.count {
// your code
}
for your special case, it would seem better to me to do something like:
for element in slashList {
if !quotaList.contains(element+1)
{
slashList.removeObject(element)
}
}
You can use removeObject from this answer. If, for some reason, you also need the index, do:
for (index, element) in slashList.enumerate() {
// your code
}
Try with, it should be 0.. not 1...
for i in 0..slashList.count{
let index = slashList[i]
if !quotaList.contains(index)
{
slashList.removeAtIndex(index)
}
}
Why you start loop from index 1 instead 0? If you really need this, you must check if slashList empty array:
if !slashList.isEmpty {
// your code here
}
Also removing items from array you numerating is bad practice. Assume that you want to loop from 0, then there is swifty way to get what you want:
slashList = slashList.enumerate().flatMap{quotaList.contains($0.index + 1) ? $0.element : nil}
I have tried both FireBase and PubNub to create this simple multiplayer game. Created with only two players. One big(and justified) concern is conflicting users. Let me explain :
each "game" constructed with just two players(not more). If 4 players logs as the same time. And each player search for a "match". player one might match with player two.while player two might match with player three, and so on.
How can i avoid it, and guarantee that each player will get a single and unique match?, or in other words , prevent matching one user with more than other one
With Firebase, security rules and transactions would be the key to an elegant solution.
If you're willing to set up a node.js script or other server-side worker, this is fairly straightforward. Players would write to a "lobby" when they want a match. The server script would perform the matches and write back the "game room" they are going to join. The structure would be basically thus:
/games/$game_id/users/user1/<user_id>
/games/$game_id/users/user2/<user_id>
/lobby/$user_id/false (an unmatched user)
/lobby/$user_id/$game_id (a matched user)
Now clients would simply write to the lobby when they want to join a game, and then wait for the server to assign them a game id:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby/' + <my user id>);
lobbyRef.set(false); // I want to join a game
lobbyRef.on('value', function(snap) {
if( snap.val() !== null ) {
console.log('I joined a game!', snap.val());
}
});
The server is nearly as simple. Assuming node.js:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
var gamesRef = ref.child('games');
var player1 = null;
// listen for requests to join
lobbyRef.on('child_added', function(snap) {
// assign as player1 if nobody is waiting
if( !player1 ) {
player1 = snap.ref();
}
// if someone is already waiting, assign both players a room
else {
var player2 = snap.ref();
var gameRef = gamesRef.push({
players: {
player1: player1.key(),
player2: snap.key()
}
}, function(err) {
if( err ) { throw err; } // a bug
// let the players know they have been matched and which room to join
player1.set(gameRef.key());
player2.set(gameRef.key());
});
}
});
Obviously there is some work to make all this fault tolerant and security rules would be needed to prevent cheating.
Doing this entirely on the client is slightly more involved, but certainly manageable.
Have each player attempt to match themselves to anybody in the lobby. If nobody is in the lobby, then wait there. This is done with a transaction to prevent conflicts.
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
function waitInLobby() {
lobbyRef.once('value', lobbyUpdated);
}
function lobbyUpdated(snap) {
if( snap.val() === null ) {
// lobby is empty, so join and wait
var ref = lobbyRef.child('<my user id>').push(false);
ref.on('value', someoneMatchedMe);
function someoneMatchedMe(snap) {
if( snap.val() !== false ) {
console.log('I got matched in room', snap.val());
ref.off('value', someoneMatchedMe); // stop monitoring
snap.ref().remove(); // leave the lobby
}
}
}
else {
// lobby is not empty, so try to match someone
var possibles = [];
snap.forEach(function(ss) {
possibles.push(ss.ref());
});
}
}
function matchUser(possibles) {
if( !possibles.length ) { waitInLobby(); }
var opponentRef = lobbyRef.child(possibles.shift());
opponentRef.transaction(function(currentVal) {
if( currentVal !== false ) {
// already claimed, start over
matchUser(possibles);
}
});
}
Some security rules would be critical here, in addition to the transactions. There is also plenty of room for optimization, but at the point that you're optimizing for production, that's a job for your engineering team, rather than a Stack Overflow contributor.
matching
[p1] - [p2] - [p3] - [p4] - [p5] - etc...
Ok so you match odd numbered player (N) with the next even numbered player (N + 1).
Of course P5 stays alone and should wait for the next round, make him P1 for that round. That way he never has to wait 2 rounds
You can create a tuple for the pairs, but I would make the Player class also have a field oponent of type Player
edit1: You keep track of the raw queue in a regular array of Players. As soon as the array reaches it's desired size you trigger the above algorithm which stops the ability to change to current player pool and all matches will be definitive.
idle check
Ok so you let players enter the queue and you display a timer / empty slot counter so they have feedback how long they have to wait.
As soon as the match starts you let them lock in (League of Legends does it this way as well)
If 1 or more players do not lock in you start the queue process over, maybe with a decreased timer so the players don't have to wait to long.
If you make this time based (not slot based) then if 1 players does not respond (let's say P2) you move the last player (P5) to his slot (P5 is now P2) and everyone can play.
If you have more questions I will edit this answer.