Xmas can be a somewhat exciting time, but the weather is frightful. We've had snow already this year (IKR! We never get snow before January usually) and the heating has even come on a couple of times during the day! My gaffer at work introduced us to Advent of Code, which has been excellent, but my main go-to has been Scrimba's #JavaScriptmas.
I've been reading about Digital Inclusion in rural communities in my role as a Parish Councillor and so I am as keen as Scrimba to get the word out there about how much fun coding can be.
Now I've been doing this long enough to have picked up all sorts of bad habits and, at work, I have to support IE11, so I rarely get to play with arrow functions or template literals. Except when doing challenges like this, that is: so please do forgive the almost deliberate lack of clarity. I'll document my solutions below, and will continue to add to them as I go along:
const candies = (children, candy) => Math.floor(candy / children) * children
const depositProfit = (d, r, t) => {
let y = 0
while (d <= t) {
y++
d += (r / 100) * d
}
return y
}
const chunkyMonkey = (values, size) => {
var arr = []
for (let i = 0, len = values.length; i < len; i += size){
arr.push(values.slice(i, i + size))
}
return arr
}
const centuryFromYear = num => Math.floor((!(num % 100) ? num - 1 : num) / 100) + 1
//const reverseAString = str => str.split("").reverse().join("")
const reverseAString = (str) => str === "" ? "" : reverseAString(str.substr(1)) + str.charAt(0)
const sortByLength = strs => strs.sort((a, b) => a.length - b.length)
const countVowelConsonant = str => str.split("").reduce((a, c) => a + (!!~['a','e','i','o','u'].indexOf(c) ? 1 : 2), 0)
I'm not going to put the code here, as it's enormous, but most of the heavy lifting is done via CSS with only a tiny bit of JS. I've used this before, and I can't remember where I got the original idea from, but thank you, whoever you are! I've made the odd change to the markup, but not much TBH , it was mainly converting it to SCSS.
Actually, I've changed my mind, here's the CSS:
body {
background-color: #eee; }
#view[data-value="1"] > .dice {
transform: rotateY(360deg) !important; }
#view[data-value="2"] > .dice {
transform: rotateY(-90deg) !important; }
#view[data-value="6"] > .dice {
transform: rotateY(180deg) !important; }
#view[data-value="4"] > .dice {
transform: rotateY(90deg) !important; }
#view[data-value="3"] > .dice {
transform: rotateX(-90deg) !important; }
#view[data-value="5"] > .dice {
transform: rotateX(90deg) !important; }
#view {
cursor: pointer;
width: 200px;
height: 200px;
margin: 100px;
perspective: 600px; }
#view .dice {
width: 200px;
height: 200px;
position: absolute;
transform-style: preserve-3d;
transition: transform 1s; }
#view .dice .side {
position: absolute;
width: 200px;
height: 200px;
background: #fff;
box-shadow: inset 0 0 40px #ccc;
border-radius: 40px; }
#view .dice .side.inner {
background: #e0e0e0;
box-shadow: none; }
#view .dice .side.front {
transform: translateZ(100px); }
#view .dice .side.front.inner {
transform: translateZ(98px); }
#view .dice .side.right {
transform: rotateY(90deg) translateZ(100px); }
#view .dice .side.right.inner {
transform: rotateY(90deg) translateZ(98px); }
#view .dice .side.back {
transform: rotateY(180deg) translateZ(100px); }
#view .dice .side.back.inner {
transform: rotateX(-180deg) translateZ(98px); }
#view .dice .side.left {
transform: rotateY(-90deg) translateZ(100px); }
#view .dice .side.left.inner {
transform: rotateY(-90deg) translateZ(98px); }
#view .dice .side.top {
transform: rotateX(90deg) translateZ(100px); }
#view .dice .side.top.inner {
transform: rotateX(90deg) translateZ(98px); }
#view .dice .side.bottom {
transform: rotateX(-90deg) translateZ(100px); }
#view .dice .side.bottom.inner {
transform: rotateX(-90deg) translateZ(98px); }
#view .dice .side .dot {
position: absolute;
width: 46px;
height: 46px;
border-radius: 23px;
background: #444;
box-shadow: inset 5px 0 10px #000; }
#view .dice .side .dot.center {
margin: 77px 0 0 77px; }
#view .dice .side .dot.dtop {
margin-top: 20px; }
#view .dice .side .dot.dleft {
margin-left: 134px; }
#view .dice .side .dot.dright {
margin-left: 20px; }
#view .dice .side .dot.dbottom {
margin-top: 134px; }
#view .dice .side .dot.center.dleft {
margin: 77px 0 0 20px; }
#view .dice .side .dot.center.dright {
margin: 77px 0 0 134px; }
const fib = n => {
const arr = [0, 1]
let i = 1
while(arr[arr.length - 1] <= n){
arr.push(arr[arr.length - 2] + arr[arr.length - 1])
i++
}
arr.pop()
return arr
}
const sumOddFibonacciNumbers = num => fib(num).reduce((a, c) => Math.abs(c % 2) === 1 ? c + a : a, 0)
const byTwo = a => {
const r = []
for(let i = 0; i < a.length; i++){
if(i < a.length - 1 ){
r.push([a[i], a[i + 1]])
}
}
return r
}
const adjacentElementsProduct = nums => Math.max(...byTwo(nums).map(e => e[0] * e[1]))
const avoidObstacles = nums => {
let n = 2,
found = false
while (!found) {
if (nums.every(num => num % n !== 0)) {
found = true
} else {
n++
}
}
return n
}
const validTime = str => parseInt(str.split(":")[0], 10) >= 0 && parseInt(str.split(":")[0], 10) <= 24 && parseInt(str.split(":")[1], 10) >= 0 && parseInt(str.split(":")[1], 10) < 60
// const validTime = str => ~~str.split(":")[0] >= 0 && ~~str.split(":")[0] <= 24 && ~~str.split(":")[1] >= 0 && ~~str.split(":")[1] < 60
// In the 24-hour time notation, the day begins at midnight, 00:00, and the last minute of the day begins at 23:59. Where convenient, the notation 24:00 may also be used to refer to midnight at the end of a given date[5] — that is, 24:00 of one day is the same time as 00:00 of the following day.
const extractEachKth = (nums, index) => nums.filter((_,i) => (i + 1) % index)
const byTwo = a => {
const r = []
for(let i = 0; i < a.length; i++){
if(i < a.length - 1 ){
r.push([a[i], a[i + 1]])
}
}
return r
}
const arrayMaximalAdjacentDifference = nums => Math.max(...byTwo(nums).map(a => Math.abs(a[0] - a[1])))
This one took far too long, mainly because I got distracted by matrix transforms in CSS - horrible! Still, got in there just in time.
// javascript
const previous = document.querySelector(".previous")
const next = document.querySelector(".next")
const gallery = document.querySelector(".gallery")
const cards = Array.from(document.querySelectorAll(".card"))
const matrixR = /matrix\(\s*\d+,\s*\d+,\s*\d+,\s*(\-*\d+),\s*\d+,\s*\d+\)/
gallery.style.transform = window.getComputedStyle(gallery).transform
next.addEventListener('click', e => {
let current = null
cards.forEach((c, i) => {
if(c.classList.contains('current')){
current = i
}
})
if(current < cards.length - 1){
cards[current].classList.remove('current')
cards[current + 1].classList.add('current')
gallery.style.transform = `matrix(1, 0, 0, 1, ${-220 * (current + 1)}, 0)`
previous.style.opacity = 1
if(current + 1 === cards.length - 1){
next.style.opacity = 0.3
}
}
})
previous.addEventListener('click', e => {
let current = null
cards.forEach((c, i) => {
if(c.classList.contains('current')){
current = i
}
})
if(current > 0){
cards[current].classList.remove('current')
cards[current - 1].classList.add('current')
gallery.style.transform = `matrix(1, 0, 0, 1, ${-220 * (current - 1)}, 0)`
next.style.opacity = 1
if(current - 1 === 0){
previous.style.opacity = 0.3
}
}
})
const insertDashes = arr => arr.split('').map((c, i, a) => c === ' ' ? ' ' : a[i+1] === ' ' ? c : i < a.length - 1 ? `${c}-` : c ).join('')
const differentSymbolsNaive = str => new Set(str.split('')).size
The byTwo
function I used in Days 10 and 14 above can be re-written like this:
[1,2,3,4,5,6].map((c, i, a) => a[i+1] && [c, a[i+1]]).filter(e => Array.isArray(e))
Much nicer, and means that Day 10 can be written like this:
const adjacentElementsProduct = nums => Math.max(...nums.map((c, i, a) => a[i+1] && [c, a[i+1]]).filter(e => Array.isArray(e)).map(e => e[0] * e[1]))
With Day 14 written like this:
const arrayMaximalAdjacentDifference = nums => Math.max(...nums.map((c, i, a) => a[i+1] && [c, a[i+1]]).filter(e => Array.isArray(e)).map(a => Math.abs(a[0] - a[1])))
There was mention of a for loop in the description, but map seemed the most suited to this one. I'm really not sure why, but I soirt of get a kick out of using c,i,a
as the arguments. c
is the current array element, i
is the index of that element in the (a
) array. Works, doesn't it?
const arrayPreviousLess = nums => nums.map((c,i,a) => !i ? -1 : a[i-1] < c ? a[i-1] : -1)
Or shrunk:
const arrayPreviousLess=n=>n.map((c,i,a)=>!i?-1:a[i-1]<c?a[i-1]:-1)
After playing with it some more, I clocked that the byTwo refactor above was a bit pants. We return the appropriately chunked array, but only after removing the extraneous element with the filter - this seems inefficient to me. I got to thinking about using a reduce function instead but got into all sorts of issues trying to return the array after pushing the new element. I had problems because I'd forgotten that push returns the length of the array after the addition rather than the array with the new element. That's where concat came to the rescue, now we have:
[1,2,3,4,5,6].reduce((n,c,i,a)=>(a[i+1])?n.concat([[c,a[i+1]]]):n,[])
Much cleaner, but it messes up my c,i,a
, especially as the first value should be another a
, to represent the accumulator. Ah well, nothing's perfect I guess, and a,c,i,a
won't work. Replacing the first a
with n
seems to work OK though.
That was a fun one, mainly because I wanted to try alternative ways of solving it, and ways of solving it which didn't use charCodeAt or a Set.
const alphabetSubsequence = str =>
str === str.split('')
.reduce((arr, c) =>
!!~arr.indexOf(c)
? arr
: arr.concat([c]), [])
.sort((a, b) => a.localeCompare(b))
.join('')
const alphabetSubsequence = str =>
str === [...new Set(str.split('')
.sort((a, b) =>
a > b
? 1
: a < b
? -1
: 0))]
.join('')
const alphabetSubsequence = str =>
str === [...new Set(str.split('')
.sort((a, b) =>
a.localeCompare(b)
)
)
].join('')
const alphabetSubsequence = str =>
str === [...new Set(str.split('')
.sort((a, b) =>
a.charCodeAt(0) - b.charCodeAt(0)
)
)
].join('')
I did learn an interesting thing though. Running this:
"abcdcba".split('').reduce((arr, c) => !!~arr.indexOf(c) ? arr : arr.concat([c]))
Produces the string "abcd"
. Whereas, runing this:
"abcdcba".split('').reduce((arr, c) => !!~arr.indexOf(c) ? arr : arr.concat([c]), [])
Gives us ["a", "b", "c", "d"]
. This shouldn't be surprising really - I'd just forgotten about how indexOf and concat can both work on strings as well as arrays. Neat!
// Using an Object literal:
function getTLD (tld) {
const tlds = {
'org': () => 'organization',
'com': () => 'commercial',
'net': () => 'network',
'info': () => 'information',
'default': () => 'unrecognised',
};
return (tlds[tld] || tlds['default'])();
}
// Using a plain Object:
// const tlds = {
// org: 'organization',
// com: 'commercial',
// net: 'network',
// info: 'information'
// }
// const domainType = ds => ds.map(d => tlds[d.split('.')[d.split('.').length - 1]])
const domainType = ds => ds.map(d => getTLD(d.split('.')[d.split('.').length - 1]))
I was going to go to town and list all the TLDs from here. Then I realsied I'd be doing that most of the day, so I just added a default unrecognised
.
Not big nor clever, too tired to be clever today!
const sumOfTwo = (nums1, nums2, value) => {
for(let i = 0; i < nums1.length; i++){
for(let j = 0; j < nums2.length; j++){
if(nums1[i] + nums2[j] === value){
return true
}
}
}
return false
}
The coffee kicked in:
const sumOfTwo=(a1,a2,t)=>!!~a1.map(e=>a2.map(a=>a+e)).flat().indexOf(t)
Perforamce wise, there's not much in it.
const extractMatrixColumn = (matrix, column) => matrix.reduce((a, c) => a.concat(c[column]), [])
That was fun! Using the concat thing I mentioned above within a reduce, such a nice way of generating an array from another.
Insomnia meant I saw the original solution via the email... I was going to do something similar so had to re-think things and this is what I came up with:
document.getElementById('btn').setAttribute('disabled', true)
document.getElementById('string').addEventListener('input', ev => {
const remaining = 140 - ev.target.value.length
document.getElementById('counterFooter').textContent = remaining + '/140'
document.getElementById('counterFooter').style.color = ''
document.getElementById('btn').removeAttribute('disabled')
remaining < 0 && document.getElementById('btn').setAttribute('disabled', true)
remaining === 140 && document.getElementById('btn').setAttribute('disabled', true)
remaining < 20 && (document.getElementById('counterFooter').style.color = 'red')
})
document.getElementById('btn').addEventListener('click', ev => {
window.open(`https://twitter.com/intent/tweet?text=${encodeURI(document.getElementById('string').value)}&hashtags=JavaScriptmas&via=DThompsonDev`)
})
I also tweaked the CSS a smidge:
button{
width:50%;
background-color: rgb(29, 161, 242);
border-radius: 10px;
padding: 0 10%;
}
button h2{
color: #fff;
}
button:disabled {
opacity: .5;
cursor: default;
}
Not a huge change I know, but I do prefer disabling things via an attribute rather than a class - it also means that the click
event handler isn't fired on a button with the disabled attribute set; so less code! Oh, I almost forgot, it tweets too, and with the appropriate hashtag and target too! I've gone back to this and changed the keyup
to input
, helps a smidge, especially for odd times when input is added via the right-click mechanism.
One more to go, yippee!
//EDIT THIS FUNCTION
const spin = async () => {
//WRITE YOUR CODE HERE
let counter = 0
spinningElem.innerHTML = counter
while(!pushed) {
await sleep(75) //Paste this wherever you need to sleep the incrimentor
counter++
spinningElem.innerHTML = counter
}
stop(counter); //Trigger this function when the STOP button has been pushed
}
//EDIT THIS FUNCTION
function stop(i){
//WRITE YOUR CODE HERE
pushed = true
var result = document.getElementById('result'); //display your result message here
result.innerHTML = (i === targetInt) ? "Congratulations!" : `Too bad, you were out by ${Math.abs(targetInt - i)}, refresh the page to try again.`
}
Conclusion
Well, that was nice, the certificate for this post came though just now: