Konami Key Sequence Array.splice() explanation needed - javascript

I'm doing a Konami Code exercise in JavaScript and while I got it to work on my own, the answer makes no sense to me. Would someone care to explain?
My solution:
const pressed = [];
var secretCode = 'wesbos';
window.addEventListener('keyup', e => {
//My code
if (pressed.length < 6) {
pressed.push(e.key)
} else if (pressed.length === 6) {
pressed.shift()
pressed.push(e.key)
console.log(pressed)
}
//End my code
if (pressed.join('').toLowerCase() === secretCode) {
console.log("SECRET COMMAND ACTION CODE TRIGGERED! COMMENCE KAMEHAMEHA");
$.getScript('http://www.cornify.com/js/cornify.js', function() {
cornify_add();
$(document).keydown(cornify_add);
});
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
An answer from https://medium.com/#nikkoortega/key-sequence-detection-f90773e3aa60 which I don't understand:
const pressed = [];
const secretCode = 'wesbos';
window.addEventListener('keyup', e => {
//Answer code
pressed.push(e.key)
pressed.splice(-secretCode.length - 1, pressed.length - secretCode.length)
//End answer code
if (pressed.join('').toLowerCase() === secretCode) {
console.log("SECRET COMMAND ACTION CODE TRIGGERED! COMMENCE KAMEHAMEHA");
}
})

The point of the code is to create a queue, a FIFO structure.
Array#splice is a confusing in-place function that removes elements starting at the first parameter up to the second parameter, with negative indices wrapping. The third parameter optionally adds new elements but it's not used here.
In the solution, -secretCode.length - 1 is basically a constant, -7 if the length of the secret code is 6. This is totally pointless and can just be replaced with 0 since they're really trying to access the first element, which is what should be dequeued.
The second parameter is pressed.length - secretCode.length which takes the difference between the number of keys collected so far and the total length of the secret code. This is <= 0 up until the pressed queue exceeds the size of the secret code, at which point it's 1, meaning the first element is dequeued because the splice call will look like splice(0, 1). When splice is called with a negative number like splice(0, -1) or splice(0, 0) it doesn't have any effect.
Here's a simplified and annotated version:
const pressed = [];
var secretCode = 'wesbos';
window.addEventListener('keyup', e => {
pressed.push(e.key);
console.log(
"after push, before splice",
pressed + "",
pressed.length - secretCode.length
);
pressed.splice(0, pressed.length - secretCode.length);
console.log("after splice", pressed + "");
console.log("______________");
if (pressed.join('').toLowerCase() === secretCode) {
console.log("SECRET COMMAND ACTION CODE TRIGGERED! COMMENCE KAMEHAMEHA");
}
})
<p>type: "wesbos"</p>
My opinion is that splice should usually be avoided, especially when messing with negative indices and adding elements. It's linear, clever, hard to understand and you're usually using the wrong data structure if you have to pop elements out of the middle of an array.
I prefer your approach but I'd write it like:
const pressed = [];
var secretCode = 'wesbos';
window.addEventListener('keyup', e => {
pressed.push(e.key);
while (pressed.length > secretCode.length) {
pressed.shift();
}
if (pressed.join('').toLowerCase() === secretCode) {
console.log("SECRET COMMAND ACTION CODE TRIGGERED! COMMENCE KAMEHAMEHA");
}
})
<p>type: "wesbos"</p>
The while could be if since we know we're always adding 1 element, but it also doesn't really hurt to keep it while either -- the point is that it's enforcing dequeues until the queue is the same size as the target word.
One of the annoying things about JS is that it doesn't have a good builtin queue structure, so we have to shift() an array. This is still linear, but at least it communicates intent of implementing a queue more clearly than a splice that's always operating at index 0 and always removing no more than 1 element, in spite of negative indexing obfuscation.

Related

How to let Cypress contains return boolean instead of failing the test

I am testing a virtual dropdown list, my code is like this:
while (!cy.contains('.ant-select-item',/^Cypress$/)) {
cy.get('.ant-select-dropdown').trigger('wheel', {deltaX:0,deltaY:100});
}
It keeps wheeling down until finds a specific element. However, this code does not work, when contains does not find the specific element, it fails the test instead of return false.
How to make the while loop work?
You can use jQuery, Cypress.$ instead.
while (!Cypress.$('.ant-select-item:contains('Cypress').length) {
cy.get('.ant-select-dropdown').trigger('wheel', {deltaX:0,deltaY:100});
}
One thing - :contains() will match partially, so this is no good if more than one item has the string.
Long version - Cypress.$('.ant-select-item:contains('Cypress') gets a list of matching elements. If none found, it does not fail but the length of the list is 0. Since 0 is falsy, the loop continues.
The loop idea is only good if the dropdown does actually contain the value somewhere, otherwise it spins forever.
While loops generally don't work with Cypress, it would be safer to use a repeating (recursive) function
function findItem(item, loop = 0) {
if (loop === 10) throw 'Too many attempts'
cy.get('.ant-select-item')
.invoke('text')
.then(textsOfCurrentList => {
if (!textsOfCurrentList.contains(item)) {
// wheel down and try next
cy.get('.ant-select-dropdown').trigger('wheel', {deltaX:0,deltaY:100})
findItem(item, ++loop)
} else {
return
}
})
})
findItem('Cypress')
Or with package cypress-recurse
recurse(
() => cy.get('.ant-select-item').invoke('text'),
(textsOfCurrentList) => {
const found = textsOfCurrentList.contains(item)
if (!found) {
cy.get('.ant-select-dropdown').trigger('wheel', {deltaX:0,deltaY:100})
}
return found // true means finish, false means repeat
},
{
log: true,
limit: 10, // max number of iterations
timeout: 30000, // time limit in ms
},
)
A good background info avoid-while-loops-in-cypress

Comparing 2 Json Object using javascript or underscore

PS: I have already searched the forums and have seen the relevant posts for this wherein the same post exists but I am not able to resolve my issue with those solutions.
I have 2 json objects
var json1 = [{uid:"111", addrs:"abc", tab:"tab1"},{uid:"222", addrs:"def", tab:"tab2"}];
var json2 = [{id:"tab1"},{id:"new"}];
I want to compare both these and check if the id element in json2 is present in json1 by comparing to its tab key. If not then set some boolean to false. ie by comparing id:"tab1" in json2 to tab:"tab1 in json1 .
I tried using below solutions as suggested by various posts:
var o1 = json1;
var o2 = json2;
var set= false;
for (var p in o1) {
if (o1.hasOwnProperty(p)) {
if (o1[p].tab!== o2[p].id) {
set= true;
}
}
}
for (var p in o2) {
if (o2.hasOwnProperty(p)) {
if (o1[p].tab!== o2[p].id) {
set= true;
}
}
}
Also tried with underscore as:
_.each(json1, function(one) {
_.each(json2, function(two) {
if (one.tab!== two.id) {
set= true;
}
});
});
Both of them fail for some test case or other.
Can anyone tell any other better method or outline the issues above.
Don't call them JSON because they are JavaScript arrays. Read What is JSON.
To solve the problem, you may loop over second array and then in the iteration check if none of the objects in the first array matched the criteria. If so, set the result to true.
const obj1 = [{uid:"111", addrs:"abc", tab:"tab1"},{uid:"222",addrs:"def", tab:"tab2"}];
const obj2 = [{id:"tab1"},{id:"new"}];
let result = false;
for (let {id} of obj2) {
if (!obj1.some(i => i.tab === id)) {
result = true;
break;
}
}
console.log(result);
Unfortunately, searching the forums and reading the relevant posts is not going to replace THINKING. Step away from your computer, and write down, on a piece of paper, exactly what the problem is and how you plan to solve it. For example:
Calculate for each object in an array whether some object in another array has a tab property whose value is the same as the first object's id property.
There are many ways to do this. The first way involves using array functions like map (corresponding to the "calculate for each" in the question, and some (corresponding to the "some" in the question). To make it easier, and try to avoid confusing ourselves, we'll do it step by step.
function calculateMatch(obj2) {
return obj2.map(doesSomeElementInObj1Match);
}
That's it. Your program is finished. You don't even need to test it, because it's obviously right.
But wait. How are you supposed to know about these array functions like map and some? By reading the documentation. No one help you with that. You have to do it yourself. You have to do it in advance as part of your learning process. You can't do it at the moment you need it, because you won't know what you don't know!
If it's easier for you to understand, and you're just getting started with functions, you may want to write this as
obj2.map(obj1Element => doesSomeElementInObj1Match(obj1Element))
or, if you're still not up to speed on arrow functions, then
obj2.map(function(obj1Element) { return doesSomeElementInObj1Match(obj1Element); })
The only thing left to do is to write doesSomeElementInObj2Match. For testing purposes, we can make one that always returns true:
function doesSomeElementInObj2Match() { return true; }
But eventually we will have to write it. Remember the part of our English description of the problem that's relevant here:
some object in another array has a tab property whose value is the same as the first object's id property.
When working with JS arrays, for "some" we have the some function. So, following the same top-down approach, we are going to write (assuming we know what the ID is):
In the same way as above, we can write this as
function doesSomeElementInObj2Match(id) {
obj2.some(obj2Element => tabFieldMatches(obj2Element, id))
}
or
obj2.some(function(obj2Element) { return tabFieldMatches(obj2Element, id); })
Here, tabFieldMatches is nothing more than checking to make sure obj2Element.tab and id are identical.
We're almost done! but we still have to write hasMatchingTabField. That's quite easy, it turns out:
function hasMatchingTabField(e2, id) { return e2.tab === id; }
In the following, to save space, we will write e1 for obj1Element and e2 for obj2Element, and stick with the arrow functions. This completes our first solution. We have
const tabFieldMatches = (tab, id) { return tab === id; }
const hasMatchingTabField = (obj, id) => obj.some(e => tabFieldMatches(e.tab, id);
const findMatches = obj => obj.some(e => hasMatchingTabField(e1, obj.id));
And we call this using findMatches(obj1).
Old-fashioned array
But perhaps all these maps and somes are a little too much for you at this point. What ever happened to good old-fashioned for-loops? Yes, we can write things this way, and some people might prefer that alternative.
top: for (e1 of obj1) {
for (e2 of (obj2) {
if (e1.id === e2.tab) {
console.log("found match");
break top;
}
}
console.log("didn't find match);
}
But some people are sure to complain about the non-standard use of break here. Or, we might want to end up with an array of boolean parallel to the input array. In that case, we have to be careful about remembering what matched, at what level.
const matched = [];
for (e1 of obj1) {
let match = false;
for (e2 of obj2) {
if (e1.id === e2.tab) match = true;
}
matched.push(match);
}
We can clean this up and optimize it bit, but that's the basic idea. Notice that we have to reset match each time through the loop over the first object.

Need a Non-recursive, iterative based negamax algorithm for a chess AI

Any idea or pseudo code for a Non-recursive, iterative based negamax algorithm?
I use negamax as the search heart of my Chess AI.
My engine is written in JavaScript and according to the literature can benefit 4x if iteration was used over recursion.
The JavaScript to C penalty is about 3x slower in terms of node depth. This one tweak could level the playing field, but take both factors with a grain of salt :)
Instead of the longer negamax code. Similar recursive code is my "Static Exchange Eval" (SEE)
function _see(sq, fen, depth, maxDepth, color, chess) {
"use strict";
if (chess.fen() !== fen) {
console.error("s fen/chess sync error");
chess.load(fen);
}
if (chess.in_checkmate() || chess.game_over()) {
return MATE;
} else if (chess.in_check()) {
return 0; // ????
}
var value = 0, moves, index, move_score, tfen, foo, bar;
if (depth < maxDepth) {
moves = chess.moves({
square: sq,
verbose: true
});
if (moves.length > 0) {
counter.seeNodes = counter.seeNodes + 1;
moves = _.chain(moves)
//only captures
.reject(function (e) {
return !e.hasOwnProperty('captured');
})
//material MVV
.sortBy(function (s) {
return evalPiece(s.piece);
})
//captures LVA
.sortBy(function (s) {
return -evalPiece(s.captured);
})
.value();
//counter.sDepth = Math.max(depth, counter.sDepth);
//counter.maxSDepth = Math.max(maxDepth, counter.maxSDepth); console.error(JSON.stringify(moves));
for (index = 0; index < moves.length; index += 1) {
foo = chess.move(moves[index]);
if (foo === null) {
console.error("see move generated error, aborting loop");
break;
}
tfen = chess.fen();
value = Math.max(0, evalPiece(foo.captured) - _see(sq, tfen, depth + 1, maxDepth, -color, chess));
bar = chess.undo();
if (bar === null) {
console.error("see: bar=null");
}
}
}
}
return value;
}
You can translate a recursive algorithm to an iterative one using a stack. In general, the object you push on the stack will be the same as the parameters you make your recursive call with.
I wrote a non-recursive Negamax routine as an option in the EasyAI python library. The specific source code is at:
https://github.com/Zulko/easyAI/blob/master/easyAI/AI/NonRecursiveNegamax.py
It uses a simple loop with a fixed array of objects (size determined by target depth) to move up and down the tree in an ordered fashion. For the particular project I was using it on, it was six times faster than the recursive version. But I'm sure each game would respond differently.
There is no way to deny that this is some dense and complex code and conversion to Javascript will be ... challenging. It is pretty much nothing but border cases. :)
If you convert it to Javascript, I would love to see the results. Place an link in the comment?

Accumulating and resetting values in a stream

I'm playing with Reactive Programming, using RxJS, and stumbled upon something I'm not sure how to solve.
Let's say we implement a vending machine. You insert a coin, select an item, and the machine dispenses an item and returns change. We'll assume that price is always 1 cent, so inserting a quarter (25 cents) should return 24 cents back, and so on.
The "tricky" part is that I'd like to be able to handle cases like user inserting 2 coins and then selecting an item. Or selecting an item without inserting a coin.
It seems natural to implement inserted coins and selected items as streams. We can then introduce some sort of dependency between these 2 actions — merging or zipping or combining latest.
However, I quickly ran into an issue where I'd like coins to be accumulated up until an item is dispensed but not further. AFAIU, this means I can't use sum or scan since there's no way to "reset" previous accumulation at some point.
Here's an example diagram:
coins: ---25---5-----10------------|->
acc: ---25---30----40------------|->
items: ------------foo-----bar-----|->
combined: ---------30,foo--40,bar--|->
change:------------29------39------|->
And a corresponding code:
this.getCoinsStream()
.scan(function(sum, current) { return sum + current })
.combineLatest(this.getSelectedItemsStream())
.subscribe(function(cents, item) {
dispenseItem(item);
dispenseChange(cents - 1);
});
25 and 5 cents were inserted and then "foo" item was selected. Accumulating coins and then combining latest would lead to "foo" being combined with "30" (which is correct) and then "bar" with "40" (which is incorrect; should be "bar" and "10").
I looked through all of the methods for grouping and filtering and don't see anything that I can use.
An alternative solution I could use is to accumulate coins separately. But this introduces state outside of a stream and I'd really like to avoid that:
var centsDeposited = 0;
this.getCoinsStream().subscribe(function(cents) {
return centsDeposited += cents;
});
this.getSelectedItemsStream().subscribe(function(item) {
dispenseItem(item);
dispenseChange(centsDeposited - 1);
centsDeposited = 0;
});
Moreover, this doesn't allow for making streams dependent on each other, such as to wait for coin to be inserted until selected action can return an item.
Am I missing already existing method? What's the best way to achieve something like this — accumulating values up until the moment when they need to be merged with another stream, but also waiting for at least 1 value in 1st stream before merging it with the one from the 2nd?
You could use your scan/combineLatest approach and then finish the stream with a first followed up with a repeat so that it "starts over" the stream but your Observers would not see it.
var coinStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#add5'), 'click').map(5),
Rx.Observable.fromEvent($('#add10'), 'click').map(10),
Rx.Observable.fromEvent($('#add25'), 'click').map(25)
);
var selectedStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#coke'), 'click').map('Coke'),
Rx.Observable.fromEvent($('#sprite'), 'click').map('sprite')
);
var $selection = $('#selection');
var $change = $('#change');
function dispense(selection) {
$selection.text('Dispensed: ' + selection);
console.log("Dispensing Drink: " + selection);
}
function dispenseChange(change) {
$change.text('Dispensed change: ' + change);
console.log("Dispensing Change: " + change);
}
var dispenser = coinStream.scan(function(acc, delta) { return acc + delta; }, 0)
.combineLatest(selectedStream,
function(coins, selection) {
return {coins : coins, selection : selection};
})
//Combine latest won't emit until both Observables have a value
//so you can safely get the first which will be the point that
//both Observables have emitted.
.first()
//First will complete the stream above so use repeat
//to resubscribe to the stream transparently
//You could also do this conditionally with while or doWhile
.repeat()
//If you only will subscribe once, then you won't need this but
//here I am showing how to do it with two subscribers
.publish();
//Dole out the change
dispenser.pluck('coins')
.map(function(c) { return c - 1;})
.subscribe(dispenseChange);
//Get the selection for dispensation
dispenser.pluck('selection').subscribe(dispense);
//Wire it up
dispenser.connect();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<button id="coke">Coke</button>
<button id="sprite">Sprite</button>
<button id="add5">5</button>
<button id="add10">10</button>
<button id="add25">25</button>
<div id="change"></div>
<div id="selection"></div>
Generally speaking you have the following set of equations:
inserted_coins :: independent source
items :: independent source
accumulated_coins :: sum(inserted_coins)
accumulated_paid :: sum(price(items))
change :: accumulated_coins - accumulated_paid
coins_in_machine :: when items : 0, when inserted_coins : sum(inserted_coins) starting after last emission of item
The hard part is coins_in_machine. You need to switch the source observable based on some emissions from two sources.
function emits ( who ) {
return function ( x ) { console.log([who, ": "].join(" ") + x);};
}
function sum ( a, b ) {return a + b;}
var inserted_coins = Rx.Observable.fromEvent(document.getElementById("insert"), 'click').map(function ( x ) {return 15;});
var items = Rx.Observable.fromEvent(document.getElementById("item"), 'click').map(function ( x ) {return "snickers";});
console.log("running");
var accumulated_coins = inserted_coins.scan(sum);
var coins_in_machine =
Rx.Observable.merge(
items.tap(emits("items")).map(function ( x ) {return {value : x, flag : 1};}),
inserted_coins.tap(emits("coins inserted ")).map(function ( x ) {return {value : x, flag : 0};}))
.distinctUntilChanged(function(x){return x.flag;})
.flatMapLatest(function ( x ) {
switch (x.flag) {
case 1 :
return Rx.Observable.just(0);
case 0 :
return inserted_coins.scan(sum, x.value).startWith(x.value);
}
}
).startWith(0);
coins_in_machine.subscribe(emits("coins in machine"));
jsbin : http://jsbin.com/mejoneteyo/edit?html,js,console,output
[UPDATE]
Explanations:
We merge the insert_coins stream with the items stream while attaching a flag to them to know which one of the two emitted when we receive a value in the merged stream
When it is the items stream emitting, we want to put 0 in coins_in_machine. When it is the the insert_coins we want to sum the incoming values, as that sum will represent the new amount of coins in the machine. That means the definition of insert_coins switches from one stream to another under the logic defined before. That logic is what is implemented in the switchMapLatest.
I use switchMapLatest and not not switchMap as otherwise the coins_in_machine stream would continue to receive emission from former switched streams, i.e. duplicated emission as in the end there are ever only two streams to and from which we switch. If I may, I would say this is a close and switch that we need.
switchMapLatest has to return a stream, so we jump through hoops to make a stream that emits 0 and never ends (and does not block the computer, as using the repeat operator would in that case)
we jump through some extra hoops to make the inserted_coins emit the values we want. My first implementation was inserted_coins.scan(sum,0) and that never worked. The key and I found that quite tricky, is that when we get to that point in the flow, inserted_coins already emitted one of the values that is a part of the sum. That value is the one passed as a parameter of flatMapLatest but it is not in the source anymore, so calling scan after the fact won-t get it, so it is necessary to get that value from the flatMapLatest and reconstitute the correct behaviour.
You can also use Window to group together multiple coin events, and use item selection as the window boundary.
Next we can use zip to acquire the item value.
Notice we instantly try to give out items. So the user does have to insert coins before he decide on an item.
Notice i decided to publish both selectedStream and dispenser for safety reasons, we don't want to cause a race-condition where events fire while we're building up the query and zip becomes unbalanced. That would be a very rare condition, but notice that when our sources had been cold Observables, they pretty much start generating as soon as we subscribe, and we must use Publish to safeguard ourselves.
(Shamelessly stolen paulpdaniels example code).
var coinStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#add5'), 'click').map(5),
Rx.Observable.fromEvent($('#add10'), 'click').map(10),
Rx.Observable.fromEvent($('#add25'), 'click').map(25)
);
var selectedStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#coke'), 'click').map('Coke'),
Rx.Observable.fromEvent($('#sprite'), 'click').map('Sprite')
).publish();
var $selection = $('#selection');
var $change = $('#change');
function dispense(selection) {
$selection.text('Dispensed: ' + selection);
console.log("Dispensing Drink: " + selection);
}
function dispenseChange(change) {
$change.text('Dispensed change: ' + change);
console.log("Dispensing Change: " + change);
}
// Build the query.
var dispenser = Rx.Observable.zip(
coinStream
.window(selectedStream)
.flatMap(ob => ob.reduce((acc, cur) => acc + cur, 0)),
selectedStream,
(coins, selection) => ({coins : coins, selection: selection})
).filter(pay => pay.coins != 0) // Do not give out items if there are no coins.
.publish();
var dispose = new Rx.CompositeDisposable(
//Dole out the change
dispenser
.pluck('coins')
.map(function(c) { return c - 1;})
.subscribe(dispenseChange),
//Get the selection for dispensation
dispenser
.pluck('selection')
.subscribe(dispense),
//Wire it up
dispenser.connect(),
selectedStream.connect()
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<button id="coke">Coke</button>
<button id="sprite">Sprite</button>
<button id="add5">5</button>
<button id="add10">10</button>
<button id="add25">25</button>
<div id="change"></div>
<div id="selection"></div>

rx: unfold array to multiple streams

I have a stream holding an array, each element of which has an id. I need to split this into a stream per id, which will complete when the source stream no longer carries the id.
E.g. input stream sequence with these three values
[{a:1}, {b:1}] [{a:2}, {b:2}, {c:1}] [{b:3}, {c:2}]
should return three streams
a -> 1 2 |
b -> 1 2 3
c -> 1 2
Where a has completed on the 3rd value, since its id is gone, and c has been created on the 2nd value, since its id has appeared.
I'm trying groupByUntil, a bit like
var input = foo.share();
var output = input.selectMany(function (s) {
return rx.Observable.fromArray(s);
}).groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return input.filter(
function (s) { return !findkey(s, g.key); }
); }
)
So, group by the id, and dispose of the group when the input stream no longer has the id. This seems to work, but the two uses of input look odd to me, like there could a weird order dependency when using a single stream to control the input of the groupByUntil, and the disposal of the groups.
Is there a better way?
update
There is, indeed, a weird timing problem here. fromArray by default uses the currentThread scheduler, which will result in events from that array being interleaved with events from input. The dispose conditions on the group are then evaluated at the wrong time (before the groups from the previous input have been processed).
A possible workaround is to do fromArray(.., rx.Scheduler.immediate), which will keep the grouped events in sync with input.
yeah the only alternative I can think of is to manage the state yourself. I don't know that it is better though.
var d = Object.create(null);
var output = input
.flatMap(function (s) {
// end completed groups
Object
.keys(d)
.filter(function (k) { return !findKey(s, k); })
.forEach(function (k) {
d[k].onNext(1);
d[k].onCompleted();
delete d[k];
});
return Rx.Observable.fromArray(s);
})
.groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return d[g.key] = new Rx.AsyncSubject(); });

Categories

Resources