Horizontal Scroll Array of JSON Objects - arrays

Ok so i have the results from the JSON array all lined up nicely. They dont seem to move across the screen though. Is it because my txtX = 0; is still in draw? Ive played around but cant seem to remedy this and as a result, still have static text :-(
var scores;
var txtX;
var txtY;
function preload() {
scores = loadJSON("stats.json");
}
function setup() {
createCanvas(700, 700);
}
function draw() {
background(254);
var txtX = 0;
var txtY = 550;
var stats = scores.results;
for (var i = 0; i < stats.length; i++) {
textSize(12);
text(stats[i], txtX, txtY);
var wordWidth = textWidth(stats[i]);
var currentOffset = 15;
txtX = txtX + wordWidth + currentOffset;
}
txtX = txtX - 1;
}
Here is my JSON:
{
"description" : "FA Cup results; 7th January 2017.",
"source" : "http://www.bbc.co.uk/sport/football/results",
"results":[
"Manchester United 4-0 Reading",
"Accrington Stanley 2-1 Luton Town",
"Barrow 0-2 Rochdale",
"Birmingham City 1-1 Newcastle United",
"Blackpool 0-0 Barnsley",
"Bolton Wanderers 0-0 Crystal Palace",
"Brentford 5-1 Eastleigh",
"Brighton & Hove Albion 2-0 Milton Keynes Dons",
"Bristol City 0-0 Fleetwood Town",
"Everton 1-2 Leicester City",
"Huddersfiled Town 4-0 Port Vale",
"Hull City 2-0 Swansea City",
"Ipswich Town 2-2 Lincoln City",
"Millwall 3-0 Bournemouth",
"Norwich City 2-2 Southampton",
"Queens Park Rangers 1-2 Blackburn Rovers",
"Rotherham United 2-3 Oxford United",
"Stoke City 0-2 Wolverhampton Wanderers",
"Sunderland 0-0 Burnley",
"Sutton United 0-0 AFC Wimbledon",
"Watford 2-0 Burton Albion",
"West Bromwich Albion 1-2 Derby County",
"Wigan Athletic 2-0 Nottingham Forest",
"Wycombe Wanderers 2-1 Stourbridge",
"Preston North End 1-2 Arsenal"
]
}
Any help would be greatly appreciated. Thanks.

There's a lot about your code that doesn't make sense:
Why are you using a nested for loop? Your inner for loop seems to iterate over each word in an individual string. Why not just pass the whole string into the text() function instead of each word one at a time?
Why are you only changing currentOffset after that inner loop completes? You're drawing a whole string but only changing the offset for a single word.
Why are you adding 500 to the currentOffset?
Why don't you ever use the txtX variable?
You need to take a step back and rethink exactly what you're doing. If I were you, I would do the following:
You need to keep track of two variables: the txtX variable at the sketch level that keeps track of the start, which scrolls to the left, and a currentWordX variable that keeps track of the offset of the current string relative to the txtX variable.
You only need a single for loop. Even if your eventual goal requires drawing each word separately, start with a single for loop that iterates over the results array.
For each string in the results array, you draw that string. Its X position is txtX + currentWordX. Then you get that string's width and add that value to the currentWordX variable.
After the for loop, you need to decrease the txtX variable to cause the whole thing to scroll to the left.

Related

Swift Accidental Infinite ForEach Loop

I'm trying to use a foreach loop to show multiple Elements in an [[String]] with the help of an incremented Index in a list. But when I want to generate the list the loop repeats infinity regardless of the specified loop-count.
This is my function that gets called in the loop, to return the wanted Array of Strings:
func getPlaces (PlaceNumber: Int) -> [String]{
if allPlaces.count - 1 >= PlaceNumber{
return allPlaces[PlaceNumber]
} else{
return ["Not found",
"Not found",
"https://www.wikipedia.com",
"Not found",
"Not found"]
}
}
The Variable 'allPlaces' is an [[String]] and has 25 String-Arrays in it (0-24):
let allPlaces: [[String]] =
[["no sight",
"No information",
"https://www.wikipedia.com",
"No information",
"No information"],
["Tower of London",
"The Tower of London, officially Her Majesty's Royal Palace and Fortress of the Tower of London, is a historic castle on the north bank of the River Thames in central London. It lies within the London Borough of Tower Hamlets, which is separated from the eastern edge of the square mile of the City of London by the open space known as Tower Hill. It was founded towards the end of 1066 as part of the Norman Conquest. The White Tower, which gives the entire castle its name, was built by William the Conqueror in 1078 and was a resented symbol of oppression, inflicted upon London by the new ruling elite. The castle was also used as a prison from 1100 until 1952, although that was not its primary purpose. A grand palace early in its history, it served as a royal residence.",
"https://en.wikipedia.org/wiki/London_Bridge",
"1066",
"4"],
usw...
This is my view and the function to increment (I use the index to access the different Elements in the [[String]]). I thought that the loop should trigger just as often as the 'places.allPlaces'- Array count is. But it triggers infinitely. Even when I use '0...24' instead of "places.allPlaces'.
struct ListOfPlacesView: View {
#StateObject var location = Location()
#StateObject var places = Places()
#State var index = 0
var body: some View {
NavigationView{
List{
ForEach (places.allPlaces, id: \.self) { _ in
Text(incrementIndex())
}
}
}
.navigationTitle("All Places")
}
func incrementIndex() -> String{
index += 1
print(index)
return (places.getPlaces(PlaceNumber: index)[0])
}
}
But when I start this, the 'print(index)' counts to infinity in the console and the view doesn't even load. When I delete the 'index += 1' the loop prints the first Element of the Array 25 times, like it should.
I don't want to include the first Element of the [[String]] in the list, that's why im starting at an index of 0 and increment first.
Do you have an idea why this occurs and how to stop the app from doing that? Or know a better way to increment?
Sorry if this is described badly, ask if you can't figure out something.
Your incrementIndex() function updates the variable #State var index. This will cause the view to re-render and in turn call the incrementIndex() again. This causes the infinite loop.
Currently, you are not using the function parameter ForEach supplies, but instead discard it by naming it _. Instead of an index, I'd suggest using the value ForEach already supplies:
ForEach(places.allPlaces, id: \.self) { place in
Text(place[0])
}

Cant figure out how to go through array in Swift and find specific data

Hello I have question about arrays.
I have an array with following data, also I have corresponding Struct for SpiritRelation():
var spiritRelations = [
SpiritRelation(relationName: "Thunder Lantern", relationSpirit1: "Razor", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Double resist +5%, ATK +1600", relationSpiritIcons: ["razor", "genie"]),
SpiritRelation(relationName: "Illusive Fantasy", relationSpirit1: "Heavenly Maiden", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Excellent strike +15%, Dmg Penetration +15%, Max HP +11500", relationSpiritIcons: ["maiden", "genie"]),
SpiritRelation(relationName: "Grand Demonlord Gathering", relationSpirit1: "Sand Golem", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Excellency Resist +20%, Double Dmg +5%, ATK +1600", relationSpiritIcons: ["golem", "genie"])
}
array which contains data which will be selected by user:
var selectedSpiritsForRelation = [String]()
array of type String because I put there values which corresponds to image names in Assets. I need that to display images
array where I want to keep found relations and use it to display all found relationStats in UI
var foundRelations = [SpiritRelation]()
My problems is:
lets say user has selected 2 spirits for example: selectedSpiritsForRelation["golem", "genie"]
I’m able to find and save correctly found relation by
let result3 = spiritRelations.filter{$0.relationSpiritIcons == (selectedSpiritsForRelation) } // = 3rd relation in spiritRelations[]
foundRelations.append(contentsOf: result3)
but after user select another one spirit and array become: selectedSpiritsForRelation["golem", "genie", "maiden"]
same code as for result3 does not work anymore, because how I understand it tries to filter exactly combination of 3, but my expectation is that 2nd relation from spiritRelation[] will be found also
and here is my problem, I cant figure out how to correctly go through spiritRelations[] and find all relations related to selectedSpiritsForRelation[] every time user selects new spirit
You need to use allSatisfy in your filter by checking that all relationSpiritIcons elements exists in selectedSpiritsForRelation
foundRelations = spiritRelations.filter {
$0.relationSpiritIcons.allSatisfy { icon in
selectedSpiritsForRelation.contains(icon)
}
}

Optimising a Google sheets script that takes an array as an input and is run in approx. 200 cells

I'm setting up a fairly complex Google sheet and trying to automate some routine interpolation with a script. I now have a script that works, but I want to optimise it.
Let me briefly describe the set up with some (simple) example data:
A B C D E
1 Lookup date Result Recorded date Value
2 17/8/2018 - 31/12/2018 210
3 31/12/2018 210 31/3/2019 273
4 14/2/2019 241.5 12/6/2019 411
5 31/3/2019 273
6 12/6/2019 411
7 1/7/2019 411
In this example, I have a small number of recorded values (columns D and E) and I want to compute the value for any date (column A). Column B is the output of the script. The problem is that my script is very slow, taking quite a while on my laptop (sometimes I must refresh the page), and never fully executing on my iPad.
I think part of this may be the volume of requests: I run this script for about 200 cells in my sheet.
I will briefly explain the script (full javascript code below).
It creates a custom function getvalue(x, y, lookupdate) which, for a given x-range (col. D) y-range (col. E) and "lookup date" (eg A4) will return the correct result (eg B4). This result is either:
blank if the lookup date occurs before the first recorded date
the exact value if the lookup date equals a recorded date
an interpolated value if the lookup date is in between two recorded dates
the final recorded value if the lookup date is beyond the range of the recorded dates
Now I have optimised this somewhat. In my implementation, I actually run it as an array for 100 cells in column A (only some of which actually need to run the script). I have another simple system that basically auto-populates the date in column A as a binary flag to say the script needs to run. So using ISBLANK() as a switch, my array formula for cells B3:B103 is:
=ArrayFormula(IF(ISBLANK(A3:A103),"",getvalue(D:D,E:E,A3:A103)))
Even though the array covers 100 cells, only about 50 of them are "activated" with a date in the A column, so only about 50 of them actually need to run the getvalue function. However, as a final complication, I am actually doing this to calculate four different values for each "lookup date", running four different arrays in four columns, so that's what I say the script runs approx. 200 times.
Here is my actual script:
function getvalue(x, y, lookupdate) {
/// LOOKUP AN ARRAY
if (lookupdate.map) {
return lookupdate.map(function(v) {
return getvalue(x, y, v);
});
}
/// GET RID OF EMPTY CELLS IN COLUMN
var xf = x.filter(function(el) {
return el != "";
});
var yf = y.filter(function(el) {
return el != "";
});
/// GET RID OF HEADER ROW
xf.shift()
yf.shift()
/// SAVE THE FIRST AND LAST VALUES
var firstx = xf[0][0]
var firsty = yf[0][0]
var lastx = xf[xf.length - 1][0]
var lasty = yf[yf.length - 1][0]
/// FIGURE OUT WHAT TO RETURN
if (lookupdate < firstx) {
return "";
} else if (lookupdate.valueOf() == firstx.valueOf()) {
return firsty;
} else if (lookupdate > lastx) {
return lasty;
} else {
var check = 0, index;
for(var i = 0, iLen = xf.length; i < iLen; i++) {
if(xf[i][0] == lookupdate) {
return yf[i][0];
} else {
if(xf[i][0] < lookupdate && ((xf[i][0] - check) < (lookupdate - check))) {
check = xf[i][0];
index = i;
}
}
}
var xValue, yValue, xDiff, yDiff, xInt;
yValue = yf[index][0];
xDiff = xf[index+1][0] - check;
yDiff = yf[index+1][0] - yValue;
xInt = lookupdate - check;
return (xInt * (yDiff / xDiff)) + yValue;
}
}
The error message on the iPad is simply the cells never move past "Loading...", and on the laptop it takes much longer than expected.
The most confusing thing is that I think it has gotten worse since I set it up as an array. I previously had it where all 400 cells would run the ISBLANK() check, and then for the approx 200 triggered cells, they would individually run the script. This at least would load on the iPad.
I read on here and on general Google support that scripts will run a lot faster if you batch operations, but it seems to have gotten slower since moving from 200 single cells to 4 arrays.
Does this need to be optimised further, or is there some other reason it might be stalling on my iPad?
Is it even possible to optimise it down and do this in a single call, instead of in 4 arrays?
Accounting for the case
else if (lookupdate.valueOf() == firstx.valueOf()) return firsty;
is superfluous because it is covered already by if(xf[i][0] == lookupdate)
(xf[i][0] - check) < (lookupdate - check) can be simplified to xf[i][0] < lookupdate
You are using pure javascript code, but keep in mind that App Script has many additional functions which are handy when working with Spreadsheet.
https://developers.google.com/apps-script/reference/spreadsheet/
So, e.g. for running your function only for the populated range functions like getDataRange() or getRange() in combination with getNextDataCell() and getLastRow() will be very useful for you.
The main important point - the functionality of your script. Are you assuming that there is an approximately linear relationship between Recorded date and value, and thus interpolate the value for not recorded dates?
In this case the statistically most precise way (and the programmatically simplest one) would be to calculate your slope between the first and last x and y respectively. That is:
Result=first_y+((y_last-y_first)/(x_last-x_first)*(Lookup_Date-first_x))
If this approach is suitable for you, your code would simplify and would look in App Script something like:
function myFunction() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var Result_Range=ss.getRange("A2:B")
var limit=Result_Range.getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow()
var Result_values=Result_Range.getValues();
var valueRange=ss.getRange("D1:E");
var values=valueRange.getValues();
var last_Index=valueRange.getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow()
var last_y=values[last_Index-1][1];
var last_x=values[last_Index-1][0].valueOf();
var first_y=values[1][1];
var first_x=values[1][0].valueOf();
var slope=(last_y-first_y)/(last_x-first_x);
for(var i=1;i<limit;i++)
{
Result_Range.getCell(i,2).setValue(first_y+(slope*(Result_values[i-1][0].valueOf()-first_x)))
Logger.log(i)
Logger.log(Result_values[i][0].valueOf()-first_x)
}
}

as3 combine elements of array that match, use math to count the numbers

My head is spinning around for not finding the solution to my problem. I've searched all over the web to find an answer or solution to get it done. I thought what I want is simple to achieve, but I can't get it right. Maybe it is a lot more difficult or I am just looking wrong.
I've made a as3 project where content will be writen to a .txt file. Each day there will be made a new file and on this txt file will be saved al the data of the actions of the day.
The txt file looks like this:
79, 2-1-2015, Orange,1,4.00, 15:59:43
79, 2-1-2015, Blue,1,1.00, 15:59:43
80, 2-1-2015, Orange,1,4.00, 16:2:52
80, 2-1-2015, Black,1,1.00, 16:2:52
(actionumber, date, article, amount, cost, time)
I now want to read this data in my project and look for the same articles, count the amounts and costs and put it back as one row in a new file. For example, based on above txt file:
2, Orange, 8.00
1, Blue, 1.00
1, Black, 1.00
total: 4, , 10.00
I know I can read and write files via Filestream.
After I read the file, I've put the content into a array and split each row in a different element. But then, when I need to compare, and do the math, I'm stuck.
I tried to use IndexOf to search in the array,but I don't know what to do next. I read about .concat and saw examples of duplicates being removed from an array. but I want duplicates based on the article (amount and cost may vary) combine and count instead of deleting. And I think that is a different cup of tea?!
Has some one a good example or can point me in the right direction? Thank you so much
You can use an Object ( or associative array ) to stock your articles with their names as keys like this :
var url_request:URLRequest = new URLRequest('data.txt')
var url_loader:URLLoader = new URLLoader()
url_loader.addEventListener(
Event.COMPLETE,
function(e:Event){
load_data(e.target.data);
}
)
url_loader.load(url_request);
function load_data(data:String):void {
var actions:Object = {};
// convert all data to an Array
var temp_arr:Array = data.split(String.fromCharCode(13));
for(var i = 0; i < temp_arr.length; i++){
var action_line:String = temp_arr[i];
// convert every line to an Array
var action_arr:Array = action_line.split(', ');
var article:String = action_arr[2];
var amount:int = action_arr[3];
var cost:int = action_arr[4];
if(actions[article]){
var t_a:Array = actions[article]
t_a[0] += int(amount); // cumulate amount
t_a[2] += int(cost); // cumulate cost
// update article
actions[article] = t_a;
// example : actions['Orange'] = [1, 'Orange', 4]
// => actions['Orange'] = [2, 'Orange', 8]
} else {
// create new article as an Array
actions[article] = [int(amount), article, int(cost)];
// example : actions['Orange'] = [1, 'Orange', 4]
}
}
var amounts:int = 0;
var costs:int = 0;
for(var action:String in actions){
amounts += actions[action][0];
costs += actions[action][2];
trace(actions[action][0]+', '+actions[action][1]+', '+actions[action][2].toFixed(2));
}
trace('total : '+amounts+', '+costs.toFixed(2));
// gives :
//
// 1, Black, 1.00
// 2, Orange, 8.00
// 1, Blue, 1.00
// total : 4, 10.00
}
Of course you have to add the part that will save these data into a text file.
Hope that can help you.

Sorted array: how to get position before and after using name? as3

I have been working on a project and Stack Overflow has helped me with a few problems so far, so I am very thankful!
My question is this:
I have an array like this:
var records:Object = {};
var arr:Array = [
records["nh"] = { medinc:66303, statename:"New Hampshire"},
records["ct"] = { medinc:65958, statename:"Connecticut"},
records["nj"] = { medinc:65173, statename:"New Jersey"},
records["md"] = { medinc:64596, statename:"Maryland"},
etc... for all 50 states. And then I have the array sorted reverse numerically (descending) like this:
arr.sortOn("medinc", Array.NUMERIC);
arr.reverse();
Can I call the name of the record (i.e. "nj" for new jersey) and then get the value from the numeric position above and below the record in the array?
Basically, medinc is medium income of US states, and I am trying to show a ranking system... a user would click Texas for example, and it would show the medinc value for Texas, along with the state the ranks one position below and the state that ranks one position above in the array.
Thanks for your help!
If you know the object, you can use the array.indexOf().
var index:int = records.indexOf(records["nj"]);
var above:Object;
var below:Object;
if(index + 1 < records.length){ //make sure your not already at the top
above = records[index+1];
}
if(index > 0){ //make sure your not already at the bottom
below = records[index-1];
}
I think this is the answer based on my understanding of your data.
var index:int = arr.indexOf(records["nh"]);
That will get you the index of the record that was clicked on and then for find the ones below and above just:
var clickedRecord:Object = arr[index]
var higherRecord:Object = arr[index++]
var lowerRecord:Object = arr[index--]
Hope that answers your question
Do you really need records to be hash?
If no, you can simply move key to record field and change records to simple array:
var records: Array = new Array();
records.push({ short: "nh", medinc:66303, statename:"New Hampshire"}),
records.push({ short: "ct", medinc:65958, statename:"Connecticut"}),
....
This gives you opportunity to create class for State, change Array to Vector and make all of this type-safe, what is always good.
If you really need those keys, you can add objects like above (with "short" field) in the same way you are doing it now (maybe using some helper function which will help to avoid typing shortname twice, like addState(records, data) { records[data.short] = data }).
Finally, you can also keep those records in two objects (or an object and an array or whatever you need). This will not be expensive, if you will create state object once and keep references in array/object/vector. It would be nice idea if you need states sorted on different keys often.
This is not really a good way to have your data set up - too much typing (you are repeating "records", "medinc", "statename" over and over again, while you definitely could've avoided it, for example:
var records:Array = [];
var states:Array = ["nh", "ct", "nj" ... ];
var statenames:Array = ["New Hampshire", "Connecticut", "New Jersey" ... ];
var medincs:Array = [66303, 65958, 65173 ... ];
var hash:Object = { };
function addState(state:String, medinc:int, statename:String, hash:Object):Object
{
return hash[state] = { medinc: medinc, statename: statename };
}
for (var i:int; i < 50; i++)
{
records[i] = addState(states[i], medincs[i], statenames[i], hash);
}
While you have done it already the way you did, that's not essential, but this could've saved you some keystrokes, if you haven't...
Now, onto your search problem - first of all, true, it would be worth to sort the array before you search, but if you need to search an array by the value of the parameter it was sorted on, there is a better algorithm for that. That is, if given the data in your example, your specific task was to find out in what state the income is 65958, then, knowing that array is sorted on income you could employ binary search.
Now, for the example with 50 states the difference will not be noticeable, unless you do it some hundreds of thousands times per second, but in general, the binary search would be the way to go.
If the article in Wiki looks too long to read ;) the idea behind the binary search is that at first you guess that the searched value is exactly in the middle of the array - you try that assumption and if you guessed correct, return the index you just found, else - you select the interval containing the searched value (either one half of the array remaining) and do so until you either find the value, or check the same index - which would mean that the value is not found). This reduces asymptotic complexity of the algorithm from O(n) to O(log n).
Now, if your goal was to find the correspondence between the income and the state, but it wasn't important how that scales with other states (i.e. the index in the array is not important), you could have another hash table, where the income would be the key, and the state information object would be the value, using my example above:
function addState(state:String, medinc:int, statename:String,
hash:Object, incomeHash:Object):Object
{
return incomeHash[medinc] =
hash[state] = { medinc: medinc, statename: statename };
}
Then incomeHash[medinc] would give you the state by income in O(1) time.

Resources