The task involves analyzing a bunch of lines containing lines of text. Each line represents a calibration value that needs to be recovered by extracting the first and last digits and combining them into a two-digit number. The goal is to find the sum of all these calibration values. Note: sometimes there is only 1 digit, it will count both as the first and last digit.
But now the English words for non-zero digits, must be considered as well.
This can not be a simple replace as 'twonetwone'
should be 21
.
On the subreddit, folks were writing lexers etc. which would have taken me forever. Took some advice from Advent of Code 2023 day 1: Trebuchet in JS (function, use strict)
// ==UserScript==
// @name Advent of Code, day 2 54087
// @namespace Violentmonkey Scripts
// @match https://adventofcode.com/2023/day/1/input
// @grant none
// @version 1.0
// @author -
// @description 12/7/2023, 9:26:24 AM
// ==/UserScript==
(function goNuts() {
"use strict";
const lines = document.body.textContent.split('\n');
lines.pop(); //Last 'line' is empty and does not follow the assumptions
let sum = 0;
function considerStart(data, string, number) {
const i = data.line.indexOf(string);
if (i != -1 && i < data.currentBestIndex) {
data.currentBestIndex = i;
data.currentBestValue = number;
}
}
function considerEnd(data, string, number) {
const i = data.line.lastIndexOf(string);
if (i != -1 && i > data.currentBestIndex) {
data.currentBestIndex = i;
data.currentBestValue = number;
}
}
const words = "one,two,three,four,five,six,seven,eight,nine".split(",").map((value, key) => [value, key + 1]);
for (let line of lines) {
const data = {
line: line,
currentBestIndex: line.length,
}
for (const [word, index] of words) {
considerStart(data, index, index);
considerStart(data, word, index);
}
const first = data.currentBestValue * 10;
for (const [word, index] of words) {
considerEnd(data, index, index);
considerEnd(data, word, index);
}
const last = data.currentBestValue
sum = sum + first + last;
}
console.log(sum);
})();
1 Answer 1
Automatic Semicolon Insertion ASI
Generally your use of semicolons is consistent as preferred (use them rather than not) however there is one potential issue.
JavaScript requires semicolon. If you do not include them the compiler / interpreter will add them for you called Automatic Semicolon Insertion ASI. Unless you are well versed in the how and when ASI rules are applied err on the side of adding semicolons.
In your code you define a object literal as
const data = { line: line, currentBestIndex: line.length, } // << missing semicolon!!!!
ASI will add the semicolon most of the time, but that will depend on what is contained in the lines that follow the closing brace }
The following example will throw a "Type Error: {} is not a function"
.
const data = {
line: line,
currentBestIndex: line.length,
}
(() => {})(); // IIFE
Use constants
When a variable will not be changed (mutated) declare it as a constant. Example for (let line of lines) {
is safer as for (const line of lines) {
Code noise
Use shorthand syntax
You can use Shorthand property names to reduce code noise.
const data = { line: line, currentBestIndex: line.length, }
becomes
const data = {
line, // Shorthand property name
currentBestIndex: line.length,
};
Avoid needlessly long names
Names should be roughly proportional to the size of the scope they are used in and should consider context to infer semantic meaning.
The general naming style across most languages is to limit word count to 2 and no more than 20 characters. This is not a hard and fast rule, but long names are harder to read than short names
Example the following code contains a bug (typo) that in JS will not throw an error. Way too easy to overlook.
if (i != -1 && i < data.curentBestIndex) {
data.currentBestIndex = i;
data.currentBestValue = number;
}
In my view current
is redundant in the property names of data
. Also data
is a rather poor name. Could use
const best = {
line: line,
index: line.length, // replaces currentBestIndex
value: 0, // replaces currentBestValue
};
Note that value
(previously known as currentBestValue
) is defined in the declaration as it is slightly (very slightly in this case) more preformat as JS need only create one internal template of the object, rather than 2 when you add the property later in the code.
Assignment operators
Use assignment operator to reduce code noise. Example you have sum = sum + first + last;
can use addition assignment operator sum += first + last;
Avoid single use variable
You have the following
const last = data.currentBestValue sum = sum + first + last;
can be simplified to
sum = sum + first + data.currentBestValue;
Note that if the line gets too long single use variable have value in improving readability.
Unreferenced names.
Don't define names that are never referenced. Example you define goNuts
but never reference it.
Named properties
Use named object properties rather than indexed arrays to organise data. Note that when performance is critical, array indexes are faster.
In your code the array of arrays word
has more meaning if it is an array of objects. See example.
Strict mode
Strict mode directive can also be added as the first line of a script tag or JS script file (Note module scripts are automatically strict mode)
Rewrite
Below is how I would rewrite it. JavaScript is a very expressive language and hence good style is subjective.
I prefer...
- short (compact) and low noise source code. Including short cricut expressions rather than statements.
- granular code. Many simple small single role functions.
- abbreviations, names use only common well used abbreviations when possible.
- declarative style when possible. EG
sum += find(first) * 10 + find(last);
- to show clear intent. Eg hoist
var
declaration for function scope variable. Only uselet
when not function scope.const
for all constants. - object reuse rather than recreating. Eg Object
best
(formerlydata
) created once and reused many times. - local scoped functions as constant arrow function declarations.
- anonymous functions as arrow function declarations
I also think that comments indicate bad code (good code does not require comments). For example you have the line
lines.pop(); //Last 'line' is empty and does not follow the assumptions
I am guessing that without that line the result of the code is undefined. Rather the code should be able to handle the edge case and the pop()
+ comment is needless noise.
"use strict";
(() => {
var sum = 0;
const words = "one,two,three,four,five,six,seven,eight,nine"
.split(",")
.map((word, i) => ({word, idx: i + 1}));
const best = {line: "", idx: 0, val: 0};
const lines = document.body.textContent.split("\n");
const found = (idx, item) => (best.idx = idx, best.val = item.idx);
const first = (str, item) => {
const idx = best.line.indexOf(str);
idx != -1 && idx < best.idx && found(idx, item);
}
const last = (str, item) => {
const idx = best.line.lastIndexOf(str);
idx != -1 && idx > best.idx && found(idx, item);
}
const find = search => {
for (const item of words) {
search(item.idx, item);
search(item.word, item);
}
return best.val;
}
for (const line of lines) {
best.line = line;
best.idx = line.length;
best.val = 0;
sum += find(first) * 10 + find(last);
}
console.log(sum);
})();