Calculating slide index after ad offset - javascript

I've rigged up a simple JS gallery to implement "deep linking" allowing a URL with a hash like #slide-3 to automatically load the gallery with the third slide active. There's an ad in the gallery every seventh slide. These ad slides aren't tracked so the hash becomes #slide-x followed by an image slide, i.e. #slide-7.]
I've created a helper function to convert the slide numbers from the hash into the 0-indexed slide number, taking into account these ads every seventh slide, but was curious if anyone could think of a more graceful way to calculate the proper index as my implementation looks way too complicated to my eye:
var slideNum = parseInt( window.location.hash.replace( '#slide-', '' ), 10 );
slideNum += Math.floor( ( slideNum + Math.floor( slideNum / 7 ) ) / 7 ) - 1;
return slideNum;
That works but having two floors seems like overkill. There must be a simpler way! I'm not sure of the algebraic rules that govern the floor operation however so I can't figure out how to expand/simplify myself. Any help would be appreciated.
I've included a basic JS fiddle which outputs values for the first 36 slides compared to their proper values. Feel free to change the testFunc with your solution and see if works!
var nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36];
var correct = [0,1,2,3,4,5,7,8,9,10,11,12,14,15,16,17,18,19,21,22,23,24,25,26,28,29,30,31,32,33,35,36,37,38,39,40];
var testFunc = function( n ) {
var offset = ( n + Math.floor( n / 7 ) ) / 7;
return n + Math.floor( offset ) - 1;
};
document.getElementById('text').innerHTML += 'Input Expected Output<br/>';
for( var i = 0; i < nums.length; i++ ) {
document.getElementById('text').innerHTML += nums[i] + ' ' + correct[i] + ' ' + testFunc( nums[i] ) + '<br/>';
if ( ( i + 1 ) % 6 === 0 ) {
document.getElementById('text').innerHTML += 'AD<br/>';
}
}
<div id="text"></div>

Yes, one integer division is enough:
var testFunc = function( n ) {
n = n - 1;
return Math.floor(n / 6) + n
};

Realized we have both indices on each slide element so I can simply select using the URL index and get the true slide index from the DOM. Still a fun problem to think about perhaps!?

Related

Milliseconds in Lap Time Average dropping the Zero in Tenths place

I am using code I found on this site to average lap times in MM:SS.mmm (Averaging Times using Javascript)
It works great until the result has a zero in the tenths place. For example, the result should be 01:00.096 however, the zero is dropped and the answer is 01:00.96
I have simplified the input of 'times' to highlight the problem.
I have looked at different formatting issues, tried converting the strings to numbers and I've looked at the offsetify function thinking it was somehow interpreting the milliseconds incorrectly.
I am a novice at JavaScript with no formal training but enjoy hobby-programming. I have learned a lot from the examples on this site for use in my own little apps.
var times = ['01:00.096'];
var date = 0
var result = '';
function offsetify(t)
{
return t < 10 ? '0' + t : t;
}
for(var x = 0; x < times.length; x++ ) {
var tarr = times[x].split(':');
date += new Date(0, 0, 0, 0, tarr[0], tarr[1].split('.')[0], tarr[1].split('.')[1]).getTime();
}
var avg = new Date(date/times.length);
result = offsetify(avg.getMinutes()) + ':' + offsetify(avg.getSeconds()) + '.' + offsetify(avg.getMilliseconds());
The reason you see the 0 dropping is because in the offsetify function you have
return t < 10 ? '0' + t : t;
and you are passing it the value 96, which is not less than 10, so the function returns 96.
If you are able to find a datetime-formatting library like date-fns or moment, and you should use one, then great! Let the library do the work for you.
If you would like the practice, which is great for learning, use
s.padStart(3, '0')
for milliseconds, and
s.padStart(2, '0')
for minutes. For example, for your milliseconds:
> "5".padStart(3, "0")
'005'
> "55".padStart(3, "0")
'055'
> "383".padStart(3, "0")
'383'
Your function offsetify(t) appends a 0 in case your minutes/seconds is only single-digit - however milliseconds should be 3 digits!
You could create a new function that appends 0 if it is already 2 digits (less than 100), and 00 if it is only single-digit (less than 10) and just returns the result if it is already 3 digits.
function offsetifyMilliseconds(t)
{
return t < 10 ? '00' + t : t < 100 ? '0' + t : t;
}
and then do
result = offsetify(avg.getMinutes()) + ':' + offsetify(avg.getSeconds()) + '.' + offsetifyMilliseconds(avg.getMilliseconds());
However recent versions of Javascript (from ES2017) has access to the .padStart() function on any string:
The padStart() method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start (left) of the current string.
-- String.prototype.padStart() - MDN
Using this would make your code much more readable (although you'd have to convert the numeric result to a string first). You could even change your offsetify function to use this method, and prevent code duplication!
In the example below I have defined 2 as the default padding length for the method, but you can pass an additional parameter to the function when you want to use it for milliseconds:
var times = ['01:00.096'];
var date = 0
var result = '';
function offsetify(t, len = 2)
{
return t.toString().padStart(len, '0');
}
for(var x = 0; x < times.length; x++ ) {
var tarr = times[x].split(':');
date += new Date(0, 0, 0, 0, tarr[0], tarr[1].split('.')[0], tarr[1].split('.')[1]).getTime();
}
var avg = new Date(date/times.length);
var minutes = offsetify(avg.getMinutes());
var seconds = offsetify(avg.getSeconds());
var milliseconds = offsetify(avg.getMilliseconds(), 3);
result = minutes + ':' + seconds + ':' + milliseconds;
console.log(result);

Trying to optimize my code to either remove nested loop or make it more efficient

A friend of mine takes a sequence of numbers from 1 to n (where n > 0)
Within that sequence, he chooses two numbers, a and b
He says that the product of a and b should be equal to the sum of all numbers in the sequence, excluding a and b
Given a number n, could you tell me the numbers he excluded from the sequence?
Have found the solution to this Kata from Code Wars but it times out (After 12 seconds) in the editor when I run it; any ideas as too how I should further optimize the nested for loop and or remove it?
function removeNb(n) {
var nArray = [];
var sum = 0;
var answersArray = [];
for (let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
var length = nArray.length;
for (let i = Math.round(n / 2); i < length; i++) {
for (let y = Math.round(n / 2); y < length; y++) {
if (i != y) {
if (i * y === sum - i - y) {
answersArray.push([i, y]);
break;
}
}
}
}
return answersArray;
}
console.log(removeNb(102));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think there is no reason for calculating the sum after you fill the array, you can do that while filling it.
function removeNb(n) {
let nArray = [];
let sum = 0;
for(let i = 1; i <= n; i++) {
nArray.push(i);
sum += i;
}
}
And since there could be only two numbers a and b as the inputs for the formula a * b = sum - a - b, there could be only one possible value for each of them. So, there's no need to continue the loop when you find them.
if(i*y === sum - i - y) {
answersArray.push([i,y]);
break;
}
I recommend looking at the problem in another way.
You are trying to find two numbers a and b using this formula a * b = sum - a - b.
Why not reduce the formula like this:
a * b + a = sum - b
a ( b + 1 ) = sum - b
a = (sum - b) / ( b + 1 )
Then you only need one for loop that produces the value of b, check if (sum - b) is divisible by ( b + 1 ) and if the division produces a number that is less than n.
for(let i = 1; i <= n; i++) {
let eq1 = sum - i;
let eq2 = i + 1;
if (eq1 % eq2 === 0) {
let a = eq1 / eq2;
if (a < n && a != i) {
return [[a, b], [b, a]];
}
}
}
You can solve this in linear time with two pointers method (page 77 in the book).
In order to gain intuition towards a solution, let's start thinking about this part of your code:
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
...
You already figured out this is the part of your code that is slow. You are trying every combination of i and y, but what if you didn't have to try every single combination?
Let's take a small example to illustrate why you don't have to try every combination.
Suppose n == 10 so we have 1 2 3 4 5 6 7 8 9 10 where sum = 55.
Suppose the first combination we tried was 1*10.
Does it make sense to try 1*9 next? Of course not, since we know that 1*10 < 55-10-1 we know we have to increase our product, not decrease it.
So let's try 2*10. Well, 20 < 55-10-2 so we still have to increase.
3*10==30 < 55-3-10==42
4*10==40 < 55-4-10==41
But then 5*10==50 > 55-5-10==40. Now we know we have to decrease our product. We could either decrease 5 or we could decrease 10, but we already know that there is no solution if we decrease 5 (since we tried that in the previous step). So the only choice is to decrease 10.
5*9==45 > 55-5-9==41. Same thing again: we have to decrease 9.
5*8==40 < 55-5-8==42. And now we have to increase again...
You can think about the above example as having 2 pointers which are initialized to the beginning and end of the sequence. At every step we either
move the left pointer towards right
or move the right pointer towards left
In the beginning the difference between pointers is n-1. At every step the difference between pointers decreases by one. We can stop when the pointers cross each other (and say that no solution can be obtained if one was not found so far). So clearly we can not do more than n computations before arriving at a solution. This is what it means to say that the solution is linear with respect to n; no matter how large n grows, we never do more than n computations. Contrast this to your original solution, where we actually end up doing n^2 computations as n grows large.
Hassan is correct, here is a full solution:
function removeNb (n) {
var a = 1;
var d = 1;
// Calculate the sum of the numbers 1-n without anything removed
var S = 0.5 * n * (2*a + (d *(n-1)));
// For each possible value of b, calculate a if it exists.
var results = [];
for (let numB = a; numB <= n; numB++) {
let eq1 = S - numB;
let eq2 = numB + 1;
if (eq1 % eq2 === 0) {
let numA = eq1 / eq2;
if (numA < n && numA != numB) {
results.push([numA, numB]);
results.push([numB, numA]);
}
}
}
return results;
}
In case it's of interest, CY Aries pointed this out:
ab + a + b = n(n + 1)/2
add 1 to both sides
ab + a + b + 1 = (n^2 + n + 2) / 2
(a + 1)(b + 1) = (n^2 + n + 2) / 2
so we're looking for factors of (n^2 + n + 2) / 2 and have some indication about the least size of the factor. This doesn't necessarily imply a great improvement in complexity for the actual search but still it's kind of cool.
This is part comment, part answer.
In engineering terms, the original function posted is using "brute force" to solve the problem, iterating every (or more than needed) possible combinations. The number of iterations is n is large - if you did all possible it would be
n * (n-1) = bazillio n
Less is More
So lets look at things that can be optimized, first some minor things, I'm a little confused about the first for loop and nArray:
// OP's code
for(let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
??? You don't really use nArray for anything? Length is just n .. am I so sleep deprived I'm missing something? And while you can sum a consecutive sequence of integers 1-n by using a for loop, there is a direct and easy way that avoids a loop:
sum = ( n + 1 ) * n * 0.5 ;
THE LOOPS
// OP's loops, not optimized
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
if(i != y) {
if(i*y === sum - i - y) {
Optimization Considerations:
I see you're on the right track in a way, cutting the starting i, y values in half since the factors . But you're iterating both of them in the same direction : UP. And also, the lower numbers look like they can go a little below half of n (perhaps not because the sequence start at 1, I haven't confirmed that, but it seems the case).
Plus we want to avoid division every time we start an instantiation of the loop (i.e set the variable once, and also we're going to change it). And finally, with the IF statements, i and y will never be equal to each other the way we're going to create the loops, so that's a conditional that can vanish.
But the more important thing is the direction of transversing the loops. The smaller factor low is probably going to be close to the lowest loop value (about half of n) and the larger factor hi is probably going to be near the value of n. If we has some solid math theory that said something like "hi will never be less than 0.75n" then we could make a couple mods to take advantage of that knowledge.
The way the loops are show below, they break and iterate before the hi and low loops meet.
Moreover, it doesn't matter which loop picks the lower or higher number, so we can use this to shorten the inner loop as number pairs are tested, making the loop smaller each time. We don't want to waste time checking the same pair of numbers more than once! The lower factor's loop will start a little below half of n and go up, and the higher factor's loop will start at n and go down.
// Code Fragment, more optimized:
let nHi = n;
let low = Math.trunc( n * 0.49 );
let sum = ( n + 1 ) * n * 0.5 ;
// While Loop for the outside (incrementing) loop
while( low < nHi ) {
// FOR loop for the inside decrementing loop
for(let hi = nHi; hi > low; hi--) {
// If we're higher than the sum, we exit, decrement.
if( hi * low + hi + low > sum ) {
continue;
}
// If we're equal, then we're DONE and we write to array.
else if( hi * low + hi + low === sum) {
answersArray.push([hi, low]);
low = nHi; // Note this is if we want to end once finding one pair
break; // If you want to find ALL pairs for large numbers then replace these low = nHi; with low++;
}
// And if not, we increment the low counter and restart the hi loop from the top.
else {
low++;
break;
}
} // close for
} // close while
Tutorial:
So we set the few variables. Note that low is set slightly less than half of n, as larger numbers look like they could be a few points less. Also, we don't round, we truncate, which is essentially "always rounding down", and is slightly better for performance, (though it dosenit matter in this instance with just the single assignment).
The while loop starts at the lowest value and increments, potentially all the way up to n-1. The hi FOR loop starts at n (copied to nHi), and then decrements until the factor are found OR it intercepts at low + 1.
The conditionals:
First IF: If we're higher than the sum, we exit, decrement, and continue at a lower value for the hi factor.
ELSE IF: If we are EQUAL, then we're done, and break for lunch. We set low = nHi so that when we break out of the FOR loop, we will also exit the WHILE loop.
ELSE: If we get here it's because we're less than the sum, so we need to increment the while loop and reset the hi FOR loop to start again from n (nHi).

Random lottery number generator

What I'm trying to do is generate 6 random numbers, five in a range of 1-45 and one in a range of 1-25 for a Greek lottery game (Tzoker). The first 5 numbers should be unique. By pressing a button, I want to add these numbers to a div using jQuery (I have some working code for this part).
I thought it would be pretty easy using a loop, but I've found myself unable to check if the number generated already exists. The loop would only contain the first 5 numbers, because the last number can be equal to one of the other 5.
Let me propose you some simpler solution.
Make a list of all numbers from 1 to 45.
Sort the list using Math.random (plus minus something, read the docs of Array.sort to find out) as the comparison function. You will get the list in random order.
Take 5 first items from the list.
Then, when you already have the numbers, put them all into your div.
This way you don't mix your logic (getting the numbers) with your presentation (putting stuff into the DOM).
I leave the implementation as an exercise for the reader. :)
Like this?
$(function() {
$('button').on('click', function(e) {
e.preventDefault();
var numArray = [];
while( numArray.length < 5 ) {
var number = Math.floor((Math.random() * 45 ) + 1);
if( $.inArray( number, numArray ) == -1 ) {
numArray.push( number );
}
}
numArray.push( Math.floor((Math.random() * 25 ) + 1) );
$('div').html( numArray.join("<br />") );
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Generate</button>
<div></div>
While this might be not exactly what you were asking for, if you would use lodash, this would be as simple as:
_.sample(_.range(1, 46), 5) // the 5 numbers from 1..45
_.random(1, 26) // one more from 1..25
This is why functional programming is so cool. You can read for example Javascript Allonge to find out more.
http://jsfiddle.net/015d05uu/
var tzoker = $("#tzoker");
var results = $("#results");
tzoker.click(function() {
results.empty();
var properResults = [];
var rand = 0;
var contains = false;
for (i = 1; i < 7; i++) {
do
{
(rand = Math.floor((Math.random() * (i != 6 ? 45 : 25)) + 1));
contains = properResults.indexOf(rand) > -1;
} while(contains)
results.append("<br />", rand, "<br />");
properResults.push(rand);
}
});
Here is the main idea of a solution. You can define the max value as a parameter for the random.
Then, check the existence of the item in a simple array with only the data you want.
You may use a general function which generates random numbers from 1 to maxValue, and adds them to an array only if they don't exist. Then, to display, cycle through the array items and append them to #randomNumbers.
HTML
<div id="randomNumbers"></div>
JS (with jQuery)
var randomNumbersArray = [];
$(function() {
generateRandomNumbers();
displayRandomNumbers();
});
function generateRandomNumbers() {
for (i = 0; i < 5; i++) {
generateRandomNumberFrom1To(45);
}
generateRandomNumberFrom1To(25);
}
function generateRandomNumberFrom1To(maxValue) {
var randomNumber;
do {
randomNumber = Math.ceil(Math.random() * maxValue);
} while ($.inArray(randomNumber, randomNumbersArray) > -1);
randomNumbersArray.push(randomNumber);
}
function displayRandomNumbers() {
for (i in randomNumbersArray) {
$("#randomNumbers").append(randomNumbersArray[i] + "<br>");
}
}

Twitter widget for moderated live tweeting

I am looking for a widget/jquery plugin/hosted service or similar, that you can drop onto a html-page and it will display and update tweets live that contains a certain hashtag, but only from one or more configurable accounts + moderation to approve other people's tweets with same hashtag.
Think newspaper that wants to live-tweet an event, but have control over what shows up on the page from others.
I have searched, but not found anything suitable - and I am sure it must exist.
Hiya demo http://jsfiddle.net/RGrgM/ or http://jsfiddle.net/RGrgM/show/
I have linked someone (my) tweets with this demo but you can link yours :) I am lazy in twitter anyhooo :P this will help you. It will work with live feeds.
Rest everything is there and you can right click and see the source as well as read below code. B-)
This [link] might come handy: http://boxmodeljunkie.com/create-a-simple-twitter-widget-with-yui-3-and-yql/
:)
D'uh don't forget to accept and up vote innit!
it uses:
<title>Twitter Feed Widget with YUI 3 & YQL - jsFiddle demo</title>
<script type='text/javascript' src='/js/lib/yui-min-3.2.0.js'></script>
Jquery code
// top-level global namespace
YUI.namespace('CIF');
// accepts a tweet timestamp and produces relational time text
YUI.CIF.relativeTime = function ( c ) {
var origStamp = Date.parse( c ),
curDate = new Date(),
currentStamp = curDate.getTime(),
difference = parseInt( ( currentStamp - origStamp ) / 1000, 10 ),
dateArr = c.toString().split(' ');
// if no difference, do nothing
if ( difference < 0 ) {
return false;
}
if ( difference <= 5 ) {
return "Just now";
}
if ( difference <= 20 ) {
return "Seconds ago";
}
if ( difference <= 60 ) {
return "A minute ago";
}
if ( difference < 3600 ) {
return parseInt( difference / 60, 10 ) + ' minutes ago';
}
if (difference <= 1.5 * 3600) {
return "One hour ago";
}
if ( difference < 23.5 * 3600 ) {
return Math.round( difference / 3600 ) + ' hours ago';
}
if (difference < 1.5*24*3600) {
return "One day ago";
}
// produce date stamp for tweets older than a day
return dateArr[3].replace( /\:\d+$/,'' ) + ' ' + dateArr[2] + ' ' + dateArr[1] + dateArr[5] !== curDate.getFullYear().toString() ? ' ' + dateArr[5] : '';
};
// load required modules and set up YUI instance
YUI().use( 'node', 'substitute', 'yql', function ( Y ) {
var n = Y.one( '#twitterFeed' ),
// accepts a YQL JSON result object and produces a list of
// tweets using Y.substitute for templating
formatTwitterFeed = function ( r ) {
if (r) {
var s = r.query.results.statuses.status,
// HTML markup template
t = '<li><span class="status-text">{sText}</span> <span ' +
'class="quiet status-time">{sTime}</span></li>',
l = s.length,
f = '<ul>',
i;
for ( i = 0; i < l; i++ ) {
// Y.substitute method to merge HTML markup and result object
f += Y.substitute( t, {
// convert usernames, hash tags and URLs to links
sText : s[i].text
.replace(/(http\S+)/i,'$1')
.replace(/(#)([a-z0-9_\-]+)/i,
'$1$2')
.replace(/(#)(\S+)/ig,
'<a href="http://search.twitter.com/search' +
'?q=%23$2" target="_blank">$1$2</a>'),
sTime : YUI.CIF.relativeTime( s[i].created_at )
} );
}
f += '</ul>';
f += '<a class="button" href="http://twitter.com/tats_innit" title="Follow #Tats_innit on Twitter" target="_blank">Follow on Twitter »</a>';
// append output to target parent node
n.append( f );
}
};
// YQL Twitter query limited to five results for a specified username
Y.YQL( 'select * from twitter.user.timeline( 5 ) ' +
'where screen_name="#tats_innit"', formatTwitterFeed );
});
​

Get the next and previous element of an array given an index

Given I have an array in Javascript, with values as such;
0 => 0x0000FF
1200 => 0x00CCFF
28800 => 0xFF0AFF
36000 => 0xFFFFFF
How can I determine which elements a given index value falls between? With the previous example, if I have the value 31073, I need to retrieve 28800 => 0xFF0AFF and 36000 => 0xFFFFFF
There's no "built-in" way to accomplish this with Javascript's sparse arrays. The simplest way to do this for arbitrary sparse indexes while maintaining some efficiency is to keep another lookaside sorted array of the indexes into the main array. Then you can walk the lookaside list to find the right neighbor indexes, and go back to the main array to get their values.
If your array will be huge or accesses need to be faster than O(items), you could look into various tree structures for the lookaside object.
Here's one way that simply uses a couple while() loops with no body.
It assumes the starting point will always be in between. You'll need a couple of quick additional tests if that's not the case.
Also, I wasn't sure what should happen if the starting point is directly on a color, so I didn't account for that.
Example: http://jsfiddle.net/tcVxP/4/
var num = 31234,
curr = num,
prev,
next;
while( !colors[--curr] && curr );
prev = colors[curr];
curr = num;
while( !colors[++curr] );
next = colors[curr];
I just wanted to follow up here;
Thanks to both quixoto and patrick dw for your detailed comments and answers. I have however, gone with a slightly different solution. While I wanted to maintain my initial approach of using a one dimensional array, it's much easier and efficient from what I can see, to add another dimension as shown below. The array used here does exhibit predictability, but that may not be the case once the project is finalized.
var colors = [
[0, '121D4A'],
[10800, '000000'],
[21600, 'FF5900'],
[32400, 'D3EEF0'],
[43200, '7DCDFF'],
[54000, '7DA6FF'],
[64800, 'FF5900'],
[75600, '31428C'],
[86399, '121D4A'],
];
function gradientStop(color1, color2, gradStop){
var r = Math.floor(gradStop * parseInt(color2.substr(0, 2), 16) + (1 - gradStop) * parseInt(color1.substr(0, 2), 16)).toString(16);
var g = Math.floor(gradStop * parseInt(color2.substr(2, 2), 16) + (1 - gradStop) * parseInt(color1.substr(2, 2), 16)).toString(16);
var b = Math.floor(gradStop * parseInt(color2.substr(4, 2), 16) + (1 - gradStop) * parseInt(color1.substr(4, 2), 16)).toString(16);
return (r.length < 2 ? '0' + r : r) + (g.length < 2 ? '0' + g : g) + (b.length < 2 ? '0' + b : b);
}
function getColor(colors, currentIndex){
for(var i = 0, m = colors.length; i < m; i++){
if(currentIndex >= colors[i][0]){
if(typeof(colors[i + 1]) !== 'undefined'){
if(currentIndex <= colors[i + 1][0]){
return gradientStop(colors[i][1], colors[i + 1][1], (currentIndex - colors[i][0]) / (colors[i + 1][0] - colors[i][0]));
}
}
}
}
}
And also; Yes Hemlock it was an expansion on my question at Programmatic gradient stops with Javascript, hence the gradientStop() function.

Categories

Resources