Whether you're a kata solver or a kata author, Codewars allows you to embed client-side
HTML, CSS, and JavaScript in your outputs.
This goes well beyond ASCII art. The door is open to more sophisticated debugging and visualization techniques. For a contrived and sloppily coded example, run this kata; in the output panel there should be a snake which, if luck will have it, you can control via the arrow keys on your keyboard.
Some JavaScript methods are disabled in this environment, but even so, for kata authors with JavaScript+CSS experience and full DOM control, the sky's the limit.
// The client-side stuff that this demo is made of is
// in the preloaded section, but it's not pretty or necessary
// to look at so I'm leaving it out of the main code view.
// The purpose of this kumite isn't to demonstrate *how* to embed HTML.
// That's as trivial as console.log(). The point, rather, is
// to make a public service anouncement to remind everyone
// that HTML, CSS, and JavaScript are available, and when
// deployed appropriately they can improve your katas.
const Test = require("@codewars/test-compat");
describe("Kumite", function() {
it("should exist in a sane universe", function() {
Test.assertEquals(1 + 1, 2);
});
});
Here, I use a lookup table and map over the order array instead of sorting.
Advantages
- Unlike sort, this won't mutate the original array
- This should be significantly faster for large arrays
Limitations
- Keys must be literal values or - in the limited and error prone case - be flat arrays of literal values
function mapOrder(array, order, key) { if (!Array.isArray(array)) return []; const lookupTable = array.reduceRight( (table, o) => { table[o[key]] = o; return table; }, {} ); return order.map(k => lookupTable[k]); }
- function mapOrder(array, order, key) {
if (typeof array !== 'object' || typeof order !== 'object' || typeof key !== 'string') return [];- if (!Array.isArray(array)) return [];
array.sort((a, b) => {let A = a[key], B = b[key];if (order.indexOf(A) > order.indexOf(B)) {return 1;} else return -1;});return array;- const lookupTable = array.reduceRight(
- (table, o) => {
- table[o[key]] = o;
- return table;
- },
- {}
- );
- return order.map(k => lookupTable[k]);
- }
const chai = require("chai"); const assert = chai.assert; const Test = require("@codewars/test-compat"); describe("Solution", function() { it("Must return the ordered array of objects", function() { const arr = [ {id: 5, name: 'Hugo'}, {id: 2, name: 'Julian'}, {id: 3, name: 'Anne'}, {id: 1, name: 'April'}, {id: 4, name: 'John'} ]; const orderedArray = [ {id: 1, name: "April"}, {id: 2, name: "Julian"}, {id: 3, name: "Anne"}, {id: 4, name: "John"}, {id: 5, name: "Hugo"} ]; assert.strictEqual(JSON.stringify(mapOrder(arr, [ 1, 2, 3, 4, 5 ], 'id')), JSON.stringify(orderedArray)); }); it("Should be able to repeat items", function() { const arr = [ {id: 5, name: 'Hugo'}, {id: 2, name: 'Julian'}, {id: 3, name: 'Anne'}, {id: 1, name: 'April'}, {id: 4, name: 'John'} ]; const orderedArray = [ {id: 1, name: "April"}, {id: 2, name: "Julian"}, {id: 3, name: "Anne"}, {id: 3, name: "Anne"}, {id: 4, name: "John"}, {id: 4, name: "John"}, {id: 5, name: "Hugo"} ]; assert.strictEqual(JSON.stringify(mapOrder(arr, [ 1, 2, 3, 3, 4, 4, 5 ], 'id')), JSON.stringify(orderedArray)); }); it("Should return the first occurrence if keys are duplicated", function() { const arr = [ {id: 2, name: 'Julian'}, {id: 3, name: 'Anne'}, {id: 1, name: 'April'}, {id: 2, name: 'John'} ]; const orderedArray = [ {id: 1, name: "April"}, {id: 2, name: "Julian"}, {id: 3, name: "Anne"} ]; assert.strictEqual(JSON.stringify(mapOrder(arr, [ 1, 2, 3 ], 'id')), JSON.stringify(orderedArray)); }); it("Should complete in a reasonable amount of time", function() { let idxs = []; let randomArray = []; for (let i = 0; i < 100000; i++) { let randIdx = Math.floor(Math.random() * 1000000); idxs.push(randIdx); randomArray.push({ idx: randIdx }); } shuffleInPlace(randomArray); shuffleInPlace(idxs); mapOrder(randomArray, idxs, "idx") function shuffleInPlace(array) { for (let i = 0; i < array.length; i++) { let rand = Math.floor(Math.random() * array.length); [array[rand], array[i]] = [array[i], array[rand]]; } } }); it("Must return an empty array in case of any wrong parameter", function() { assert.strictEqual(String(mapOrder('randomString', 'foo', 3)), ''); }); });
- const chai = require("chai");
- const assert = chai.assert;
- const Test = require("@codewars/test-compat");
- describe("Solution", function() {
- it("Must return the ordered array of objects", function() {
- const arr = [
- {id: 5, name: 'Hugo'},
- {id: 2, name: 'Julian'},
- {id: 3, name: 'Anne'},
- {id: 1, name: 'April'},
- {id: 4, name: 'John'}
- ];
- const orderedArray = [
- {id: 1, name: "April"},
- {id: 2, name: "Julian"},
- {id: 3, name: "Anne"},
- {id: 4, name: "John"},
- {id: 5, name: "Hugo"}
- ];
- assert.strictEqual(JSON.stringify(mapOrder(arr, [
- 1, 2, 3, 4, 5
- ], 'id')), JSON.stringify(orderedArray));
- });
- it("Should be able to repeat items", function() {
- const arr = [
- {id: 5, name: 'Hugo'},
- {id: 2, name: 'Julian'},
- {id: 3, name: 'Anne'},
- {id: 1, name: 'April'},
- {id: 4, name: 'John'}
- ];
- const orderedArray = [
- {id: 1, name: "April"},
- {id: 2, name: "Julian"},
- {id: 3, name: "Anne"},
- {id: 3, name: "Anne"},
- {id: 4, name: "John"},
- {id: 4, name: "John"},
- {id: 5, name: "Hugo"}
- ];
- assert.strictEqual(JSON.stringify(mapOrder(arr, [
- 1, 2, 3, 3, 4, 4, 5
- ], 'id')), JSON.stringify(orderedArray));
- });
- it("Should return the first occurrence if keys are duplicated", function() {
- const arr = [
- {id: 2, name: 'Julian'},
- {id: 3, name: 'Anne'},
- {id: 1, name: 'April'},
- {id: 2, name: 'John'}
- ];
- const orderedArray = [
- {id: 1, name: "April"},
- {id: 2, name: "Julian"},
- {id: 3, name: "Anne"}
- ];
- assert.strictEqual(JSON.stringify(mapOrder(arr, [
- 1, 2, 3
- ], 'id')), JSON.stringify(orderedArray));
- });
- it("Should complete in a reasonable amount of time", function() {
- let idxs = [];
- let randomArray = [];
- for (let i = 0; i < 100000; i++) {
- let randIdx = Math.floor(Math.random() * 1000000);
- idxs.push(randIdx);
- randomArray.push({ idx: randIdx });
- }
- shuffleInPlace(randomArray);
- shuffleInPlace(idxs);
- mapOrder(randomArray, idxs, "idx")
- function shuffleInPlace(array) {
- for (let i = 0; i < array.length; i++) {
- let rand = Math.floor(Math.random() * array.length);
- [array[rand], array[i]] = [array[i], array[rand]];
- }
- }
- });
- it("Must return an empty array in case of any wrong parameter", function() {
- assert.strictEqual(String(mapOrder('randomString', 'foo', 3)), '');
- });
- });
Same thing in Prolog, with results rounded to the nearest hundredth.
final_price(Price, Result) :- Vat = 0.1, Lux = 0.3, ( Price >= 1000 -> Unrounded is Price*(1+Vat+Lux) ; Unrounded is Price*(1+Vat) ), Result is round(Unrounded*100)/100.
function finalPrice(price) {var vat = 0.1var lux = 0.3if(price >= 1000) {return Math.floor(price * (1+vat+lux))} else {return Math.floor(price * (1+vat))}}- final_price(Price, Result) :-
- Vat = 0.1, Lux = 0.3,
- ( Price >= 1000 -> Unrounded is Price*(1+Vat+Lux)
- ; Unrounded is Price*(1+Vat)
- ),
- Result is round(Unrounded*100)/100.
:- begin_tests(example). :- include(example). test(basic) :- final_price(100, R), assertion(R == 110). test(basic) :- final_price(900, R), assertion(R == 990). test(basic) :- final_price(2000, R), assertion(R == 2800). test(edge_case) :- final_price(1000, R), assertion(R == 1400). test(edge_case) :- final_price(999, R), assertion(R == 1098.9). test(rounding) :- final_price(11.11, R), assertion(R == 12.22). test(rounding) :- final_price(11.16, R), assertion(R == 12.28). :- end_tests(example).
const chai = require("chai");const assert = chai.assert;const Test = require("@codewars/test-compat");- :- begin_tests(example).
- :- include(example).
describe("Solution", function() {it("Final Price", function() {assert.equal(finalPrice(100), 110);assert.equal(finalPrice(100), 110);assert.equal(finalPrice(1000), 1400);assert.equal(finalPrice(900), 990);assert.equal(finalPrice(2000), 2800);});});- test(basic) :- final_price(100, R), assertion(R == 110).
- test(basic) :- final_price(900, R), assertion(R == 990).
- test(basic) :- final_price(2000, R), assertion(R == 2800).
- test(edge_case) :- final_price(1000, R), assertion(R == 1400).
- test(edge_case) :- final_price(999, R), assertion(R == 1098.9).
- test(rounding) :- final_price(11.11, R), assertion(R == 12.22).
- test(rounding) :- final_price(11.16, R), assertion(R == 12.28).
- :- end_tests(example).
toFixed
can be used on any number directly, thus making parseFloat
superfluous.
function finalPrice(price) { const vat = 0.1, lux = 0.3, luxThreshold = 1000; const multiplier = price >= luxThreshold ? 1+vat+lux : 1+vat; return (price * multiplier).toFixed(2); }
- function finalPrice(price) {
const vat = 0.1;const lux = 0.3;const luxThreshold = 1000;- const vat = 0.1,
- lux = 0.3,
- luxThreshold = 1000;
const extra = price >= luxThreshold ? lux : 0;return Number.parseFloat(price * (1 + vat + extra)).toFixed(2);- const multiplier = price >= luxThreshold ? 1+vat+lux : 1+vat;
- return (price * multiplier).toFixed(2);
- }
Not an improvement in any objective sense, but here's another way of doing it...
function getTree(strings, root={}) { for (let path of strings) { let tree = root, parts = path.split("/"); for (let part of parts.slice(0,-1)) tree = tree[part] = tree[part] || {}; tree[parts.pop()] = "file"; } return root; }
function set(obj, path, value) {if(path.length === 1) {return {...obj, [path[0]]: value};}const first = path.shift();return {...obj, [first]: set(obj[first] || {}, path, value)};}function getTree(strings) {return strings.reduce(function r(tree, curr) {return set(tree, curr.split('/'), "file");}, {});- function getTree(strings, root={}) {
- for (let path of strings) {
- let tree = root, parts = path.split("/");
- for (let part of parts.slice(0,-1))
- tree = tree[part] = tree[part] || {};
- tree[parts.pop()] = "file";
- }
- return root;
- }
function getTree(strings) { return strings.reduce( (root, path) => { let items = path.split("/").filter(x=>x); if (!items.length) return root; let branch = root; while (items.length > 1) { let item = items.shift(); branch=branch[item]=branch[item]||{}; } branch[items[0]] = "file"; return root; }, {} ); }
function set(obj, path, value) {if(path.length === 1) {return {...obj, [path[0]]: value};}const first = path.shift();return {...obj, [first]: set(obj[first] || {}, path, value)};}- function getTree(strings) {
return strings.reduce(function r(tree, curr) {return set(tree, curr.split('/'), "file");}, {});- return strings.reduce(
- (root, path) => {
- let items = path.split("/").filter(x=>x);
- if (!items.length) return root;
- let branch = root;
- while (items.length > 1) {
- let item = items.shift();
- branch=branch[item]=branch[item]||{};
- }
- branch[items[0]] = "file";
- return root;
- },
- {}
- );
- }
// Since Node 10, we're using Mocha. // You can use `chai` for assertions. const chai = require("chai"); const assert = chai.assert; // Uncomment the following line to disable truncating failure messages for deep equals, do: // chai.config.truncateThreshold = 0; // Since Node 12, we no longer include assertions from our deprecated custom test framework by default. // Uncomment the following to use the old assertions: // const Test = require("@codewars/test-compat"); const arr = [ '', 'src/main.js', 'src/second.js', 'src/components/header.js', 'src/components/helpers/sidebar.js' ]; const result = { "src": { "main.js": "file", "second.js": "file", "components": { "header.js": "file", "helpers": { "sidebar.js": "file", }, } } } describe("Solution", function() { it("should return object", function() { assert.deepEqual(getTree(arr), result) }); });
- // Since Node 10, we're using Mocha.
- // You can use `chai` for assertions.
- const chai = require("chai");
- const assert = chai.assert;
- // Uncomment the following line to disable truncating failure messages for deep equals, do:
- // chai.config.truncateThreshold = 0;
- // Since Node 12, we no longer include assertions from our deprecated custom test framework by default.
- // Uncomment the following to use the old assertions:
- // const Test = require("@codewars/test-compat");
- const arr = [
- '',
- 'src/main.js',
- 'src/second.js',
- 'src/components/header.js',
- 'src/components/helpers/sidebar.js'
- ];
- const result = {
- "src": {
- "main.js": "file",
- "second.js": "file",
- "components": {
- "header.js": "file",
- "helpers": {
- "sidebar.js": "file",
- },
- }
- }
- }
- describe("Solution", function() {
- it("should return object", function() {
- assert.deepEqual(getTree(arr), result)
- });
- });
Here's the golf edition nobody asked for.
In my opinion, the code is cleaner with destructuring and math.hypot. Also, snake_case is preferred.
import math def goes_to_jail(directions): x = 0 ; y = 0 for north,east,south,west in directions: x += (east - west) * 274 y += (north - south) * 80 if math.hypot(x, y) > 2025: return True return False
def goesToJail(directions):location=[0,0]#North+East are postive numbers West+South are a negative numbersfor direction in directions:location[0]=location[0]+(direction[0]*80)-(direction[2]*80)location[1]=location[1]+(direction[1]*274)-(direction[3]*274)#a squared + b squared = c squaredif( (location[0]**2+location[1]**2)**(1/2) > 2000 ):- import math
- def goes_to_jail(directions):
- x = 0 ; y = 0
- for north,east,south,west in directions:
- x += (east - west) * 274
- y += (north - south) * 80
- if math.hypot(x, y) > 2025:
- return True
- return False
import codewars_test as test # TODO Write tests import solution # or from solution import example # test.assert_equals(actual, expected, [optional] message) @test.describe("Example") def test_group(): @test.it("") def test_case(): test.assert_equals(goes_to_jail([[1,0,0,0],[0,0,1,0]]), False) test.assert_equals(goes_to_jail([[9,0,0,4],[15,0,0,1],[3,5,3,1],[0,1,5,0]]), True) test.assert_equals(goes_to_jail([[2,0,2,4],[3,0,3,1],[0,1,5,0],[1,0,3,4]]), True) test.assert_equals(goes_to_jail([[1,0,0,3],[3,0,1,2],[5,0,1,0]]), False) test.assert_equals(goes_to_jail( [[0,0,0,3],[2,0,1,0],[3,0,1,5],[0,3,1,0]] ), True)
- import codewars_test as test
- # TODO Write tests
- import solution # or from solution import example
- # test.assert_equals(actual, expected, [optional] message)
- @test.describe("Example")
- def test_group():
- @test.it("")
- def test_case():
test.assert_equals(goesToJail([[1,0,0,0],[0,0,1,0]]), False)test.assert_equals(goesToJail([[9,0,0,4],[15,0,0,1],[3,5,3,1],[0,1,5,0]]), True)test.assert_equals(goesToJail([[2,0,2,4],[3,0,3,1],[0,1,5,0],[1,0,3,4]]), True)test.assert_equals(goesToJail([[1,0,0,3],[3,0,1,2],[5,0,1,0]]), False)test.assert_equals(goesToJail( [[0,0,0,3],[2,0,1,0],[3,0,1,5],[0,3,1,0]] ), True)- test.assert_equals(goes_to_jail([[1,0,0,0],[0,0,1,0]]), False)
- test.assert_equals(goes_to_jail([[9,0,0,4],[15,0,0,1],[3,5,3,1],[0,1,5,0]]), True)
- test.assert_equals(goes_to_jail([[2,0,2,4],[3,0,3,1],[0,1,5,0],[1,0,3,4]]), True)
- test.assert_equals(goes_to_jail([[1,0,0,3],[3,0,1,2],[5,0,1,0]]), False)
- test.assert_equals(goes_to_jail( [[0,0,0,3],[2,0,1,0],[3,0,1,5],[0,3,1,0]] ), True)
Golfed solution.
function* generateInterval(a,b,c){for(;c>0?a<=b:a>=b;a+=c)yield a}
function* generateInterval(start, stop, step) {if (step >= 0) { // infinite lists with step 0 are a-okayfor (let cursor=start; cursor<=stop; cursor+=step) yield cursor;} else if (step < 0) {for (let cursor=start; cursor>=stop; cursor+=step) yield cursor;}}- function* generateInterval(a,b,c){for(;c>0?a<=b:a>=b;a+=c)yield a}
Using a generator and two for loops for clarity.
function* generateInterval(start, stop, step) { if (step >= 0) { // infinite lists with step 0 are a-okay for (let cursor=start; cursor<=stop; cursor+=step) yield cursor; } else if (step < 0) { for (let cursor=start; cursor>=stop; cursor+=step) yield cursor; } }
function generateInterval(start, stop, step) {const arr = [];let value = start;for (let index = 0; index <= (stop - start) / step; index++) {arr.push(value);value += step;- function* generateInterval(start, stop, step) {
- if (step >= 0) { // infinite lists with step 0 are a-okay
- for (let cursor=start; cursor<=stop; cursor+=step) yield cursor;
- } else if (step < 0) {
- for (let cursor=start; cursor>=stop; cursor+=step) yield cursor;
- }
return arr;- }
// Since Node 10, we're using Mocha. // You can use `chai` for assertions. const chai = require("chai"); const assert = chai.assert; const Test = require("@codewars/test-compat"); describe("generateInterval(2, 10, 2)", function() { it("startLessThenStop", function() { Test.assertSimilar([...generateInterval(2, 10, 2)], [2, 4, 6, 8, 10]); }); }); describe("generateInterval(2, 10, -2)", function() { it("startBiggerThenStop", function() { Test.assertSimilar([...generateInterval(2, -10, -2)], [2,0,-2,-4,-6,-8,-10]); }); });
- // Since Node 10, we're using Mocha.
- // You can use `chai` for assertions.
- const chai = require("chai");
- const assert = chai.assert;
- const Test = require("@codewars/test-compat");
- describe("generateInterval(2, 10, 2)", function() {
- it("startLessThenStop", function() {
Test.assertSimilar(generateInterval(2, 10, 2), [2, 4, 6, 8, 10]);- Test.assertSimilar([...generateInterval(2, 10, 2)], [2, 4, 6, 8, 10]);
- });
- });
- describe("generateInterval(2, 10, -2)", function() {
- it("startBiggerThenStop", function() {
Test.assertSimilar(generateInterval(2, -10, -2), [2,0,-2,-4,-6,-8,-10]);- Test.assertSimilar([...generateInterval(2, -10, -2)], [2,0,-2,-4,-6,-8,-10]);
- });
- });
I'm using capturing split instead of match. This handles a couple of edge cases including empty string inputs and inputs containing two numbers in a row.
function transformText(s) { return s.split(/(\d+)/) .map(w => w.trim()) .filter(w => w) .map(w => ( /\d/.test(w) ? `<div>${w}</div>` : `<span>${w}</span>` )) .join``; }
function transformText(string) {const regExp = /[\D]+|([\d+\.]+)/gconst numsRegExp = /[0-9\.]+/gconst splited = string.match(regExp)const res = splited.map((item) => {if (numsRegExp.test(item)) {return `<div>${item}</div>`} else {return `<span>${item.replace(/^\s|\s$/g, "")}</span>`}})return res.join("")- function transformText(s) {
- return s.split(/(\d+)/)
- .map(w => w.trim())
- .filter(w => w)
- .map(w => (
- /\d/.test(w) ? `<div>${w}</div>` : `<span>${w}</span>`
- ))
- .join``;
- }
// Since Node 10, we're using Mocha. // You can use `chai` for assertions. const chai = require("chai"); const assert = chai.assert; // Uncomment the following line to disable truncating failure messages for deep equals, do: // chai.config.truncateThreshold = 0; // Since Node 12, we no longer include assertions from our deprecated custom test framework by default. // Uncomment the following to use the old assertions: // const Test = require("@codewars/test-compat"); describe("Solution", function() { it("Example", function() { // Test.assertEquals(1 + 1, 2); // assert.strictEqual(1 + 1, 2); assert.strictEqual(transformText('Number 2'), '<span>Number</span><div>2</div>') assert.strictEqual(transformText('12 people in 2 rooms'), '<div>12</div><span>people in</span><div>2</div><span>rooms</span>') assert.strictEqual(transformText('from 2 to 9'), '<span>from</span><div>2</div><span>to</span><div>9</div>') assert.strictEqual(transformText(''), '') assert.strictEqual(transformText('word'), '<span>word</span>') assert.strictEqual(transformText('34 45'), '<div>34</div><div>45</div>') }); });
- // Since Node 10, we're using Mocha.
- // You can use `chai` for assertions.
- const chai = require("chai");
- const assert = chai.assert;
- // Uncomment the following line to disable truncating failure messages for deep equals, do:
- // chai.config.truncateThreshold = 0;
- // Since Node 12, we no longer include assertions from our deprecated custom test framework by default.
- // Uncomment the following to use the old assertions:
- // const Test = require("@codewars/test-compat");
- describe("Solution", function() {
- it("Example", function() {
- // Test.assertEquals(1 + 1, 2);
- // assert.strictEqual(1 + 1, 2);
- assert.strictEqual(transformText('Number 2'), '<span>Number</span><div>2</div>')
- assert.strictEqual(transformText('12 people in 2 rooms'), '<div>12</div><span>people in</span><div>2</div><span>rooms</span>')
- assert.strictEqual(transformText('from 2 to 9'), '<span>from</span><div>2</div><span>to</span><div>9</div>')
- assert.strictEqual(transformText(''), '')
- assert.strictEqual(transformText('word'), '<span>word</span>')
- assert.strictEqual(transformText('34 45'), '<div>34</div><div>45</div>')
- });
- });