So I'm learning react and js, and I'm trying to loop every 20 (the limit in a single page) and show this pictures, also show in the bottom this pages index using bootstrap. but it not really working this is my code:
const pictureItems = this.state.imgFiles.map((img, index) => {
return (
<PictureListItem
id={index}
key={`img-${img.name}`}
imgFile={img}
pictureDataUpdate={this.onUpdatePicture}
/>
);
});
const pageNumber = this.state.imgFiles.length / 20;
let pages = "";
for (let i = 0; i < pageNumber; i++) {
pages += <li><a>{i}</a></li>;
return pages;
}
I was thinking may be I can pass the value of the index to the loop and multiply by 20 for the start and then add 20 to the end. but I can't even get the pages to show well.
protip: don't do yourself what the language already does on its own.
const picturesPerPage = 20;
const images = this.state.imgFiles;
...
// get the current page, rounded down. We don't want fractions
let currentPageNumber = (images.length / picturesPerPage)|0;
// find the start and end position in the "images" array for this page
let start = currentPageNumber * picturesPerPage;
let end = (1+currentPageNumber) * picturesPerPage;
// cool: make JS get those items for us, and then map those items to bits of JSX
let pages = images.slice(start, end).map(img => {
return (
<li key={img.src}>
<a href={img.href}>
<img src={img.src} alt={img.alt}/>
</a>
</li>
);
});
// and we're done.
return <ul>{ pages }</ul>;
Note that if you're building an array of on-the-fly React elements, they need to have a key attribute so that the React diff engine can properly do its job - the key needs to uniquely identify the actual thing, so you can't use array position (['a','b'] and ['b','a'] are the same array, but if you pretend the array position is a key, rather than "just swap the two elements" you're lying about what happens going from one to the other, claiming actual content changes occurred when they didn't, and things get horribly inefficient).
Also note that you tried to use += for adding elements into an array - that is illegal syntax, += is string concatenation. To add individual elements to an array you use array.push (or if you need to so something weird, array.splice)
Related
When displaying list data, one can iterate through the items using v-for. I have some filter controls which increase or decrease the number of visible items (size, type, name - stuff like that).
Regardless of this, I'd like to limit the amount visible on page to, say, 10 items.
In other words, if the filter hides 50 out of 100 result items (which are still stored in Vuex) I still want to be able to paginate through 5 pages (10 at a time only).
There's a few plugins such as this one which seem to help with that.
However, I'd like to try to do it manually to understand how it's done, though I'm a bit stumped.
Since you have Vuex on board, a getter seems easiest.
export const getters = {
pagedItems: state => {
return pageNo => {
const pageSize = state.pageSize || 10
const items = state.items || []
return items.slice(pageNo * pageSize, pageSize)
}
}
}
Default values (e.g state.items || []) are there to stop the calculation erroring before initial load completes.
Use it on the component in a computed property, which will make it reactive when pageNo changes,
computed: {
pagedItems() {
return this.$store.getters['pagedItems'](this.pageNo)
},
},
It just occurred to me that if you are filtering, you probably want to apply that before the pagination otherwise the display may not be be consistent in size (e.g page 1 filters to 4 items, page 2 to 6 items).
Depends on your exact filter, should be easy to add a getter for filteredItems and use that as source for pagedItems.
well, i would just divide the number of items by the number of data i want to display per page with the rest operator and create number of pages + 1, of course with some validations to empty data and so on.
Imagine you recieve an object that contains lists, this lists represent all the arrays with your data, each array is a row.
Just get the length, divide it with module operator and add one more, in your case, if you have 52 items, and want to have 10 per page:
52 % 10 = 2
52 / 10 = 5
you need 5 pages + 1 for the 2 items.
so i would do something like this:
const NUMBER_ITEMS_PER_PAGE = 10;
const numberItems = list.length;
const pages = numberItems / NUMBER_ITEMS_PER_PAGE
if(numberItems % NUMBER_ITEMS_PER_PAGE > 0) {
pages++;
}
function buildPages(numberPages) {
const pageObj = {}
for(var i = 0; i < pages; i++) {
pageObj.page[i+1]
const arr = []
for(var j = 0; j < (NUMBER_ITEMS_PER_PAGE) * (i + 1); j++) {
arr.push(lists[i])
}
pageObj.page[i+1] = arr;
}
}
of course this is just one possible solution, but i think this can let you start in some way, the code is just to help. good luck
I will try to be specific as possible as I can't find anything on this through the Google gods.
I have a list of 10 movies. I would like to display the movies in pairs. The user picks their favorite of the two. The next pair is displayed. The user picks their favorite of those two, so on and so on until I can faithfully output the list in their order of preference from 1-10.
I'm doing this in Javascript, but even just a way to do it that is language agnostic would be great. No need to worry about syntax or UI stuff.
With three movies it's pretty easy (initial order of movies and order of pairs shouldn't matter):
1.sw
2.esb
3.rotj
example 1
1vs2: winner = 2
2vs3: winner = 2
1vs3: winner = 1
Sorted List: 2,1,3
example 2
1vs3: winner = 1
1vs2: winner = 2
2vs3: winner = 2
Sorted List: 2,1,3
First time posting so if I need to be more specific, need to have exact syntax, etc., please don't hesitate to let me know.
The minimum number of comparisons required to sort 10 items is 22. (See https://oeis.org/A036604). Do you really think your users will suffer through 22 "which movie do you like better?" questions? And do you honestly believe that the result will be useful? You'll have many cases where a user will say that he liked movie A better than B, and B better than C, but he liked movie C better than he liked movie A. So now you have the problem that:
A > B
B > C
C > A
And there's no reasonable way to resolve that conflict.
In short, your user interface is flawed. You can try to build it, but your users won't like it and your results will not be reliable.
A better interface would be to list the 10 movies and allow the users to arrange them in order of preference. Or let the user rate the movies on a scale from 1 to 5. But expecting users to answer 22 questions and get a complete ordering is a fool's errand.
The basic problem is easy. We have a ton of sorting algorithms that will work with O(n log(n)) comparisons. For instance mergesort:
// Split the array into halves and merge them recursively
function mergeSort (arr) {
if (arr.length === 1) {
// return once we hit an array with a single item
return arr
}
const middle = Math.floor(arr.length / 2) // get the middle item of the array rounded down
const left = arr.slice(0, middle) // items on the left side
const right = arr.slice(middle) // items on the right side
return merge(
mergeSort(left),
mergeSort(right)
)
}
// compare the arrays item by item and return the concatenated result
function merge (left, right) {
let result = []
let indexLeft = 0
let indexRight = 0
while (indexLeft < left.length && indexRight < right.length) {
if (left[indexLeft] < right[indexRight]) {
result.push(left[indexLeft])
indexLeft++
} else {
result.push(right[indexRight])
indexRight++
}
}
return result.concat(left.slice(indexLeft)).concat(right.slice(indexRight))
}
So in principle you need to simply replace left[indexLeft] < right[indexRight] with an arbitrary comparison function that asks the user, gets an answer, and then continues.
Now there is a catch. The catch is that you need to make this code asynchronous. When you go to ask the user, you need to ask the user, then return to inside your code. If you're using node at the console, then you can do this with async/await. If you are not, then you'll need to figure out how to do it with promises. Modifying mergeSort is easy, just make the end into:
return Promise.all([mergeSort(left), mergeSort(right)]
).then(function (values) {return merge(values[0], values[1])});
The trick is in turning the loop inside of merge into a function that takes the current state of your iteration, and returns a promise that asks the question, then either returns the final sorted array, or returns a promise that handles the next iteration.
Since this looks like homework, whose whole purpose is to make you face that mental challenge, I'll leave my answer off here. Fair warning, I gave you a hint about how to do it, but if you're just learning about async code your head is SUPPOSED to spin as you figure it out.
To determine all possible combinations from your array try the following loop. Assuming order does not matter and we do not want repeats:
var arr = [1,2,3,4,5,6,7,8,9,10]
var arr_count = arr.length
var combinations_array = []
for (i = 0; i < arr_count; i++) {
var combinations = arr_count - (i+1)
for (y = 0; y < combinations; y++) {
combination = [arr[i],arr[(combinations - y)]];
combinations_array.push(combination);
}
}
In your example I'd pass Movie ID's into arr then iterate through the combinations_array to determine which combination of movies should be displayed next.
To produce a list of pairs, I would use a nested loop like this:
var items = [1,2,3,4,5,6,7,8,9,10],
result = [],
x = 0,
y = 0;
for (x = items.length; x--;)
{
for(y = x; y--;)
{
result.push({ a: items[x], b: items[y] });
}
}
console.debug(result);
The second loop is initialised from the outer loops incrementor so that you don't end up with duplicates.
Once you have the pairs, you should be able to build the ui from that.
Hope it helps!
I'm working on an Extendscript tool for Adobe After Effects, and I'm trying to figure out one thing. Extendscript is just Javascript with some Adobe fluff added on top, but pretty much anything Javascript that isn't browser-specific works.
I have a script I've been using/developing for some time now which greatly simplifies importing thousands of images in image sequences. Everything is working great but there's one specific area that runs really slowly when you start getting into thousands of images and I'm trying to figure out how to speed it up.
What I am trying to optimize is going through an array of potentially thousands of image files, and concatenating it down to just the first image in the sequence. For example my array might be: [img.001.png, img.002.png, img.003.png, img.004.png, img.005.png] and what I want to do is remove every instance of "img.###.png" except for the first one. So after my function I would have just: [img.001.png].
Here's how I do it right now:
1) I take array item A, regex out the number sequence
2) then array item B, regex out the number sequence
3) and compare the two to see if they are identical. If they are I splice out item B
This works 100% of the time which is awesome, but is quite slow when you get above a thousand or two images... What I'm trying to do is find a way to do this "pruning" step more quickly. A friend of mine told me regex is pretty slow so maybe I need to do it without regex? I also found several native JavaScript array methods that might help such as forEach() every() and filter() but I don't see how these are going to be faster because I still need to do 2 regex evaluations, and I still need to compare the items to each other.
Any help would be immensely appreciated!
Spencer
Here's a snippet of my existing code:
currentFolder = new Folder ("//12.34.5.67/my folder on the network/")
var folderChildren = currentFolder.getFiles().sort();
var searcher = new RegExp("\\d{3,5}[.]");
for (var i = 0; i < folderChildren.length; i++) {
// Go through the array and strip out all elements that are the same once their numbers have been removed with a regex
if (i > 0) {
currentResult = searcher.exec(folderChildren[i].name); //check if a sequence
if (currentResult) { // it is a sequence
// first parse out the comparison strings - current item and item before
var testNameBefore = folderChildren[i-1].name;
//if we have a sequence before our current item, we need to delete the numbers.
var beforeNum = searcher.exec(testNameBefore);
if (beforeNum) {
testNameBefore = testNameBefore.substring(0, testNameBefore.length-8);
}
var testNameCurrent = folderChildren[i].name;
testNameCurrent = folderChildren[i].name.substring(0, testNameCurrent.length-8);
//compare to the element before it and delete if the same!!
if (testNameBefore == testNameCurrent) {
folderChildren.splice(i, 1);
i--;
}
}
}
}
You don't have a code snippet to compare, but this runs in just a few milliseconds.
var files = getFiles(),
cleanedFiles = getDesequencedArray(files);
console.log(`Parsing ${files.length} files.`);
console.log(`Found ${cleanedFiles.length} unique sequences:`);
console.log(cleanedFiles);
function getDesequencedArray(files) {
return files.reduce(
function(memo, file) {
var sequence = file.match(/(.*)\.?\d{3,5}(.*)/);
if (sequence) {
if (!memo.found[sequence[1] + sequence[2]]) {
memo.found[sequence[1] + sequence[2]] = 1;
memo.files.push(file);
}
} else {
//Did you want the other files included too?
//memo.files.push(file);
}
return memo;
}, {
found: {},
files: []
}
).files;
}
function getFiles() {
var files = [
"img1.001.jpg",
"img1.002.jpg",
"img1.001.png",
"img1.002.png",
"img1.003.png",
"img1.004.png",
"img2.011.jpg",
"img2.012.jpg",
"img2.013.jpg",
"img2.014.jpg",
"img300001.png",
"img300002.png",
"img300003.png",
"img300004.png",
"img300002.100.png",
"img300002.200.png",
"readme.md",
"index.html"
];
// Adding a bunch of records
for (let x = 0; x < 100; x++)
for (
let i = 1;
i <= 1000;
files.push(`img-${x}.` + ("00" + i++).substr(-3) + '.png')
);
return files;
}
I have for quite some time now been trying to figure out how I can stop my code to print the same quote twice.
Also, when every single object in the array has been printed out, I'd like for it to reset somehow. So that you can browse through the quotes once you've gone through all of them.
This is the essential parts of my code:
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
The printQuote function simply contains information that's accessing information from my array:
var randomObjectNumber = getRandomQuote();
var html = "<p class='quote'>"
+ quotes[randomObjectNumber].quote +
"</p>";
document.getElementById('quote-box').innerHTML = html;
One random object is displayed each time you click the eventListener:
function getRandomQuote () {
var randomObjectNumber = Math.floor(Math.random() * quotes.length );
return randomObjectNumber;
}
I have some ideas on how to do this and I have tried them but without success. I tried giving each object a boolean property but I can't really seem to assign each property a boolean value without messing the printQuote function up.
I also tried assigning the object displayed to a different array but the same problem occurred there.
I feel like there is some concepts around the eventListener that I don't fully understand, because every time I try to manipulate a displayed object I just end up changing every single object.
This is what a typical object in the array looks like by the way:
{quote : "Darkness is merely the absence of light"}
(I also have other properties assigned to the object but i feel like presenting them would be redundant)
If someone could explain, or give me a hint, on how to solve this problem I've been struggling with for some time.
Some hints would be greatly appreciated!
Have a nice day.
Sebastian.
EDIT: All code: https://jsfiddle.net/fusqb7hz/
Basically what you need:
Create a separate array that will store all quotes that you've already used.
Remove quote from initial array.
Check if you still have quotes in initial array, if not, get them back from backup array.
The problem is that you call addEventListener twice:
//Let's developers create multiple eventListeners without being redundant.
function onClicking (printFunction) {
document.getElementById('loadQuote').addEventListener("click", printFunction, false);
}
onClicking(printColor);
onClicking(printQuote);
by calling onClicking twice you make the click happen twice, so addEventListener is added twice, meaning one click counts as two.
Change the above code for this:
//Let's developers create multiple eventListeners without being redundant.
document.getElementById('loadQuote').addEventListener("click", function(){
printColor();
printQuote();
});
Here is the jsfiddle:
https://jsfiddle.net/fusqb7hz/3/
I think the easiest approach is to shuffle your quote array and then go through them one by one. This gives you the next "random" as yet unseen quote. The only part I'm not keen on is this shuffler (a derivation of Fisher Yates) modifies the original quote array. You might not care about that though.
// --------------------------------
// A bunch of quotes
// --------------------------------
var quotes = [];
quotes.push({quote : "Darkness is merely the absence of light"});
quotes.push({quote : "quote 2"});
quotes.push({quote : "quote 3"});
quotes.push({quote : "quote 4"});
quotes.push({quote : "quote 5"});
// --------------------------------
// --------------------------------
// Your favorite array shuffle utility
// --------------------------------
var shuffle = function(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
// --------------------------------
// --------------------------------
// construct a function to get a random unseen quote until
// all quotes have been seen. Then reset...
// --------------------------------
var getQuote = (function(quotes, shuffle){
var current = 0;
var get = function(){
if ( !quotes || !quotes.length ) { return ""; }
if ( current >= quotes.length ){ current = 0; }
if ( current === 0 ){
console.log("randomizing quotes...");
shuffle(quotes);
}
return quotes[current++].quote;
};
return get;
})(quotes, shuffle);
// --------------------------------
var printQuote = function(){
document.getElementById('quote').innerText = getQuote();
};
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
<div id="quote"></div>
<button id="loadQuote">get quote</button>
Situation
I'm currently writing a javascript widget that displays a random quote into a html element. the quotes are stored in a javascript array as well as how many times they've been displayed into the html element. A quote to be displayed cannot be the same quote as was previously displayed. Furthermore the chance for a quote to be selected is based on it's previous occurences in the html element. ( less occurrences should result in a higher chance compared to the other quotes to be selected for display.
Current solution
I've currently made it work ( with my severely lacking javascript knowledge ) by using a lot of looping through various arrays. while this currently works ( !! ) I find this solution rather expensive for what I want to achieve.
What I'm looking for
Alternative methods of removing an array element from an array, currently looping through the entire array to find the element I want removed and copy all other elements into a new array
Alternative method of calculating and selecting a element from an array based on it's occurence
Anything else you notice I should / could do different while still enforcing the stated business rules under Situation
The Code
var quoteElement = $("div#Quotes > q"),
quotes = [[" AAAAAAAAAAAA ", 1],
[" BBBBBBBBBBBB ", 1],
[" CCCCCCCCCCCC ", 1],
[" DDDDDDDDDDDD ", 1]],
fadeTimer = 600,
displayNewQuote = function () {
var currentQuote = quoteElement.text();
var eligibleQuotes = new Array();
var exclusionFound = false;
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i];
if (exclusionFound === false) {
if (currentQuote == iteratedQuote[0].toString())
exclusionFound = true;
else
eligibleQuotes.push(iteratedQuote);
} else
eligibleQuotes.push(iteratedQuote);
}
eligibleQuotes.sort( function (current, next) {
return current[1] - next[1];
} );
var calculatePoint = eligibleQuotes[0][1];
var occurenceRelation = new Array();
var relationSum = 0;
for (var i = 0; i < eligibleQuotes.length; i++) {
if (i == 0)
occurenceRelation[i] = 1 / ((calculatePoint / calculatePoint) + (calculatePoint / eligibleQuotes[i+1][1]));
else
occurenceRelation[i] = occurenceRelation[0] * (calculatePoint / eligibleQuotes[i][1]);
relationSum = relationSum + (occurenceRelation[i] * 100);
}
var generatedNumber = Math.floor(relationSum * Math.random());
var newQuote;
for (var i = 0; i < occurenceRelation.length; i++) {
if (occurenceRelation[i] <= generatedNumber) {
newQuote = eligibleQuotes[i][0].toString();
i = occurenceRelation.length;
}
}
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i][0].toString();
if (iteratedQuote == newQuote) {
quotes[i][1]++;
i = quotes.length;
}
}
quoteElement.stop(true, true)
.fadeOut(fadeTimer);
setTimeout( function () {
quoteElement.html(newQuote)
.fadeIn(fadeTimer);
}, fadeTimer);
}
if (quotes.length > 1)
setInterval(displayNewQuote, 10000);
Alternatives considered
Always chose the array element with the lowest occurence.
Decided against this as this would / could possibly reveal a too obvious pattern in the animation
combine several for loops to reduce the workload
Decided against this as this would make the code to esoteric, I'd probably wouldn't understand the code anymore next week
jsFiddle reference
http://jsfiddle.net/P5rk3/
Update
Rewrote my function with the techniques mentioned, while I fear that these techniques still loop through the entire array to find it's requirements, at least my code looks cleaner : )
References used after reading the answers here:
http://www.tutorialspoint.com/javascript/array_map.htm
http://www.tutorialspoint.com/javascript/array_filter.htm
http://api.jquery.com/jQuery.each/
I suggest array functions that are mostly supported (and easily added if not):
[].splice(index, howManyToDelete); // you can alternatively add extra parameters to slot into the place of deletion
[].indexOf(elementToSearchFor);
[].filter(function(){});
Other useful functions include forEach and map.
I agree that combining all the work into one giant loop is ugly (and not always possible), and you gain little by doing it, so readability is definitely the winner. Although you shouldn't need too many loops with these array functions.
The answer that you want:
Create an integer array that stores the number of uses of every quote. Also, a global variable Tot with the total number of quotes already used (i.e., the sum of that integer array). Find also Mean, as Tot / number of quotes.
Chose a random number between 0 and Tot - 1.
For each quote, add Mean * 2 - the number of uses(*1). When you get that that value has exceeded the random number generated, select that quote.
In case that quote is the one currently displayed, either select the next or the previous quote or just repeat the process.
The real answer:
Use a random quote, at the very maximum repeat if the quote is duplicated. The data usages are going to be lost when the user reloads/leaves the page. And, no matter how cleverly have you chosen them, most users do not care.
(*1) Check for limits, i.e. that the first or last quota will be eligible with this formula.
Alternative methods of removing an array element from an array
With ES5's Array.filter() method:
Array.prototype.without = function(v) {
return this.filter(function(x) {
return v !== x;
});
};
given an array a, a.without(v) will return a copy of a without the element v in it.
less occurrences should result in a higher chance compared to the other quotes to be selected for display
You shouldn't mess with chance - as my mathematician other-half says, "chance doesn't have a memory".
What you're suggesting is akin to the idea that numbers in the lottery that haven't come up yet must be "overdue" and therefore more likely to appear. It simply isn't true.
You can write functions that explicitly define what you're trying to do with the loop.
Your first loop is a filter.
Your second loop is a map + some side effect.
I don't know about the other loops, they're weird :P
A filter is something like:
function filter(array, condition) {
var i = 0, new_array = [];
for (; i < array.length; i += 1) {
if (condition(array[i], i)) {
new_array.push(array[i]);
}
}
return new_array;
}
var numbers = [1,2,3,4,5,6,7,8,9];
var even_numbers = filter(numbers, function (number, index) {
return number % 2 === 0;
});
alert(even_numbers); // [2,4,6,8]
You can't avoid the loop, but you can add more semantics to the code by making a function that explains what you're doing.
If, for some reason, you are not comfortable with splice or filter methods, there is a nice (outdated, but still working) method by John Resig: http://ejohn.org/blog/javascript-array-remove/