Tuesday, 30 June 2020

The 10-Day JavaScript Challenge

Yay! There's another one started, such fun!

I'll add to this is I go along.

Day 1

const add = (...a) => a.reduce((t, v) => t + v)

Day 2

const allLongestStrings = a => a.filter(i => i.length === Math.max(...(a.map(a => a.length))))

This is not overly efficient, but it works. Then I got to remembering about default values and altered it to this:

const allLongestStrings = (a, i = Math.max(...(a.map(a => a.length)))) => a.filter(e => e.length === i)

Longer, but we only check for the longest string the once, rather than over each iteration.

Day 3

const allLongestStrings = (a, i = Math.max(...(a.map(a => a.length)))) => a.filter(e => e.length === i)

Day 4

const arrayReplace = (arr, o, n) => arr.map(e => e === o ? n : e)

Day 5

const caseInsensitivePalindrome = (s) => s.toLowerCase().replace(/[\W_]/g, '') === s.toLowerCase().replace(/[\W_]/g, '').split('').reverse().join('')

I like this way because it deals with "A man, a plan, a canal, Panama!". But I really like this one (not mine):

const caseInsensitivePalindrome = s => [...s = s.toLowerCase()].reverse().join`` === s;

Day 6

This was a fun one as we needed to produce three different approaches, here are mine:

const encloseInBrackets = s => `(${s})`
const encloseInBrackets = s => ['(', s, ')'].join('')
const encloseInBrackets = s => ['('].concat(s).concat(')').join('')

This can be improved to this though:

const encloseInBrackets = s => '('.concat(s, ')')
const encloseInBrackets = s => [...s.split('')].map((c, i, a) => i === 0 ? `(${c}` : i === a.length - 1 ? `${c})` : c).join('')

This isn't mine (it's from musa#0677), but I love it:

const encloseInBrackets = s => String.fromCharCode(40, ...s.split('').map((_, i) => s.charCodeAt(i)), 41);

Day 7

const factorialNumber = n => n ? n * factorialNumber(n - 1) : 1

Day 8

const firstDigit = s => s.match(/\d/)[0]

Day 9

const largestNumber = n => ~~[...Array(n)].fill(9).join``

Tuesday, 23 June 2020

ISBN-10: 1780174764 & ASIN: B08BK5TGKS

I thought I'd spend a few minutes this morning trying to figure out what categories my book belongs to on Amazon. It was an interesting exploration.

  • Books
    • Business, Finance & Law
      • Careers
    • Computing & Internet
      • Computer Science
        • Programming
        • Software Design, Testing & Engineering
      • Professionals
        • Software Design, Testing & Engineering
      • Programming
        • Introduction to Programming
        • Software Design, Testing & Engineering
          • Functional Programming
          • Software Architecture
      • Software & Graphics
        • Software Design & Development
          • Software Design, Testing & Engineering
      • Web Development
        • E-commerce
          • Web Administration
          • Web Design
          • Web Scripting & Programming
        • Web Administration
          • Web Scripting & Programming
        • Web Design
          • Web Scripting & Programming
        • Web Scripting & Programming
    • Health, Family & Lifestyle
      • Self Help
    • Mind, Body & Spirit
      • Self Help
    • University Textbooks
      • Computer Science
        • Software Design, Testing & Engineering
          • Software Development
  • Kindle eBooks
    • Computing
      • Programming
        • Software Design, Testing & Engineering
    • Nonfiction
      • Computing

Red, Amber and Green progress bar

I've been doing some Moodle theming over the past few weeks and ended up discussing progress bars with the designer involved. Little progress is bad whereas finishing a Moodle course is good so I, of course, started waxing lyrical about colours. If red means bad and green means good, what about the middle ground? Then I remembered a traffic light visualisation I was working on ages ago and the joys of amber. Interestingly that post is ten years old! I must've been thinking about colours all these years - guess that's a good (green?) thing for a front-end developer!

Anyway, the project I'm working on, as I said, it based within Moodle and writing JS for that is something of a challenge. I can't get my head around the module system they use, and I don't want to learn it enough to get access to jQuery within a theme's script, so I wrote it in Vanilla JS.

I had CSS variables for the colours, so first I clocked I needed those within the script. Still, I also needed them in terms of the RGB values - as an array would work fine, after some research I came up with hexToRGB, which produces a simple collection with the RGB values as three distinct integer values in an array. One possible issue with using JS to get the computed style from a CSS variable is that it returns everything from the colon to the semi-colon as a string. That's good and all, but that left an extraneous space which needed trimming.

const hexToRGB = hex => hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b).substring(1).match(/.{2}/g).map(x => parseInt(x, 16))
const red = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--under-third').trim())
const amber = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--third-to-two-thirds').trim())
const green = hexToRGB(getComputedStyle(document.documentElement).getPropertyValue('--over-two-thirds').trim())

I also needed to mix the colours, which wasn't much of a bother. Though I did discover there were multiple ways of mixing colours, I chose to go the most straightforward route with mixColour (hey, what can I say? I know I'm supposed to use `color` when I code HTML and CSS, but I much prefer `colour`). mixColour uses a further function, imaginatively called mix, where the heavy lifting occurs and takes into account the percentage of each colour needing mixing, we pass this as a float between 0 and 1.

const mixColour = (c1, c2, pc) => RGBToHex(Math.round(mix(c1[0], c2[0], pc)), Math.round(mix(c1[1], c2[1], pc)), Math.round(mix(c1[2], c2[2], pc)))
const mix = (s, e, pc) => s + ((pc) * (e - s))

Once mixed, I needed to produce RGB values again, so I worked up RGBToHex which uses some smart bit-wise operators - I wish I knew what they did. Still, I use bit-wise operators so infrequently that I can't seem to work up the enthusiasm to learn them properly.

const RGBToHex = (r, g, b) => `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`

We had values between 0 and 100 for the progress bar, so working out the float value was the only thing that caused me any head-scratching. If the amount was less than 50, then red and amber needed to be mixed by the value divided by 50; if the value was over 50 then we use amber and green: we need to subtract 50 from the value and then, again, divide the result by 50.

let value = 0;
const container = document.getElementById('bar');
const bar = container.querySelector('.progress-bar-filler');
const interval = setInterval(
  () => {
    value = value === 100 ? 0 : value + 1;
    container.setAttribute('data-value', value);
    bar.style.width = value + "%";
    bar.style.backgroundColor = value < 50
      ? mixColour(red, amber, (value / 50))
      : mixColour(amber, green, ((value - 50) / 50))
  }, 100);

Simples eh? Once it's all broken down, then it's just a collection of function, and it works a treat too. There are again some dodgy colours produced between amber and green - but that's only for a very little time, something like 5% I think.

All that was left was to work up a demo.

Wednesday, 3 June 2020

Free course: The 7-Day JavaScript Challenge

Scrimba are running a week-long JavaScript challenge, so I jumped in.

Day 1

Initial attempt:

function addBorder(array) {
    const l = Array.from({length: array[0].length + 2 }, () => '*').join('');
    array = array.map((x) => x.padStart(x.length + 1, '*').padEnd(x.length + 2, '*'))
    array.push(l)
    array.unshift(l)
    return array;
}

After I'd thought about it:

const addBorder = a => ['*'.repeat(a[0].length), ...a, '*'.repeat(a[0].length)].map(e => `*${e}*`)

Day 2

const addTwoDigits = n => (''+n).split('').reduce((a,c) => ~~a + ~~c)

Day 3

const firstDuplicate = ns => {
  const s = new Set();
  for(n of ns){
    if(s.size === s.add(n).size) return n
  }
  return -1
}

Day 4

const sumAllPrimes = n => Array.from({length: n-1}, (_, k) => k+2).reduce((a, c) => a += !'1'.repeat(c).match(/^1?$|^(11+?)\1+$/) ? c : 0)

Day 5

const evenDigitsOnly = n => (''+n).split('').every(n => !(~~n % 2))

Day 6

const makeArrayConsecutive = n => Math.max(...n) - Math.min(...n) - [...new Set(n)].sort().reduce((a, c) => c > Math.min(...n) && c < Math.max(...n) ? a + 1 : a, 0) - 1

Alternatively:

const makeArrayConsecutive = n => ([...new Set(n)].sort()[[...new Set(n)].length - 1] - [...new Set(n)].sort()[0] + 1) - [...new Set(n)].length

Day 7

const properNounCorrection = s => s === s + '' && s.length ? String.fromCharCode(...[...s].map((_, i) => !i ? s.charCodeAt(i) - (s.charCodeAt(i) >= 97 && s.charCodeAt(i) <= 122 ? 32 : 0) : s.charCodeAt(i) + (s.charCodeAt(i) >= 65 && s.charCodeAt(i) <= 90 ? 32 : 0))) : ''

Think that's all of them now.