Skip to content

Instantly share code, notes, and snippets.

@rpgraham84
Last active March 21, 2018 00:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rpgraham84/89085a0496c77178ad5584a31913f8b3 to your computer and use it in GitHub Desktop.
Save rpgraham84/89085a0496c77178ad5584a31913f8b3 to your computer and use it in GitHub Desktop.
Hacker News sort by best
'use strict';
// This let statement selects elements from the HTML document using a CSS selector.
// Specifically, we're making an Array of all table rows (each news listing is
// a table row in an html table). The css selector here says, find the 'tr' elements
// that are nested at any depth below a tbody which is nested at any depth below any
// element bearing the class 'itemlist' -- the dot in front of itemlist indicates
// we're talking about a CSS class, not an element.
// Look up css selectors and the document.querySelectorAll
// function for more about that.
// When you do a querySelectorAll, you get a NodeList in return, not quite the same
// thing as an array, because you can't slice it. Well, maybe you could using css selectors
// but lets not get into that. The 'Array.from' here converts our nodelist into and array
// we can slice on the very next line.
let elements = Array.from(document.querySelectorAll(".itemlist tbody tr"));
// Since we'll be taking each news entry as a group of 3 tr's, we want to take the slice of
// the tr's that is divisible by 3, the remaining 0-2 elements we'll call 'leftovers'
// and append them back to the table after after everything else is done.
let leftoverCount = elements.length - elements.length % 3;
// Here, we're making a new iterator of the 'elements' array from the line above.
// entries() is a built-in function that iterates over each element in the array and
// pairs it with it's numeric index. So ['a', 'b'] becomes [[0, 'a'], [1, 'b']].
let ielements = elements.slice(0, leftoverCount).entries();
// Here we save the leftovers to be appended at the end.
let leftovers = elements.slice(leftoverCount);
// Just initializing some variables that will be used in the upcoming lines.
// 'i' will be the index variable and 'e' will be the HTMLElement as we iterate through.
// ielements from the line above. 'score' is the score of the line
let i, e, score, rows = [], tbody = document.createElement("tbody");
// This is some hot new es6 magic right here. Remember how ielements turned our elements
// array into an array of 2-element arrays, each containing the index and element
// from the original array? Well, now we're unpacking that shit. You could implement
// the same behavior using a simple counter variable starting at 0 and ticking up by
// one with each iteration. I just find this approach more elegant and closer to the
// way I would do it in Python. At the start of each iteration in this for loop, 'i' will
// be the index and 'e' will be the table row element we want to examine. See
// Destructuring assignment https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
for ([i, e] of ielements) {
// Each news entry is represented as 3 consecutive 'tr' elements and the middle tr contains the sweet, juicy
// score value we want to sort by. So, as we iterate over all the elements, we'll skip the 0th, take
// the 1st, skip the 2nd, skip the 3rd, take the 4th. But what function represents the selection we
// want to make? Mod division n % d gives you the remainder of n / d. Watch what happens:
// 0 % 3 = 0
// 1 % 3 = 1
// 2 % 3 = 2
// 3 % 3 = 0
// 4 % 3 = 1
// 5 % 3 = 2
// 6 % 3 = 0
// Doing a mod division by 3 for all the natural numbers creates a repeating cycle of 0, 1, 2 for all the
// numbers going on forever. In fact, replace 3 for n and the cycle will always be 0, 1, 2... up to n - 1.
// Since we always want the middle element out of a set of 3, we want to take the element each time
// the counter mod 3 equals 1.
if (i % 3 === 1) {
// So we're inside this if statement, so 'e' here must be one of those middle elements we were
// looking for. We find the element with the css class of 'score' within our element, 'e' and
// assign it to the variable scoreTag. Remember that querySelectorAll returns an Array, so
// we index into it at its zeroth position to take what we assume is to be the only element in
// the array. Which we can safely assume it will be, unless HN changes their layout.
let scoreTag = e.querySelectorAll(".score")[0];
// Now that we have the score element, we need to extract the raw integer value of the score so that
// we can sort the rows based on it. There are 2 big concepts introduced on this line. Ternary
// Expressions and Regular Expressions (regexes). This line is dense.
// A ternary expression is shorthand for doing a simple variable assignment based on an
// if statement. It follows the format:
// result = (condition)?expr1:expr2
// where condition is evaluated as a boolean true/false value and if it is true, then result will get
// the value of expr1, otherwise result will be expr2.
// We know that scoreTag will either be an HTMLElement or undefined. HTMLElement will evaluate to true,
// undefined to false. If true, we're saying here we want to parse an integer out of whatever our regex
// matched when executed on our scoreTag's innerText (the visible score text, ie. "27 points").
// Otherwise, we assume a score of 0. This places any abnormal content we may encounter at the bottom
// of the list. But the 'abnormal content' is hypothetical and should never actully exist.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
// Okay now regexes are cool as shit. It's a whole language (or more broadly outside of js, a collection
// of languages) that describe how to parse text and extract information. Like, grab all the digits
// or find all words that start with a certain prefix within some text. Can't type it all here,
// if you read any of these links, read the shit out of this one. It's insanely powerful. You will
// be capable of things that mere mortals will worship you for.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
// Putting it together now, the parseInt function converts a string to an integer given some base.
// In this case, being that we're normal humans, we want to use base 10. Yes it is mandatory to specify that.
// So, we're defining the regex, /\d+/ which says grab the first sequential set of digits. If we executed that
// on scoreTag.innerText, and the text was "27 points", we'd have the string "27". So, parseInt would
// process that string and return the number 27 and that's what would be our score. Phew.
let score = scoreTag ? parseInt(/\d+/.exec(scoreTag.innerText)[0], 10) : 0;
// Now that we have a score with a numeric value, we could sort a list of rows on that value.
// Lets begin by building that collection of rows with their score.
// We defined a 'rows' variable above as an empty array []. Now, we'll push arrays to it that contain
// the score and an array of the 3 table rows that make up this news headline.
// Note, we're indexing into the original elements (not ielements).
rows.push([score, [elements[i - 1], elements[i], elements[i + 1]]]);
}
}
// Now we delete the table body with all the table rows in it.
document.querySelectorAll(".itemlist tbody")[0].remove();
// The variable we created, tbody, is an HTMLElement living in memory in JS,
// it needs to be actually appended to the HTML document so we can see it.
// That whole living in memory thing really means its not attached to the
// Document Object Model, or DOM. This line brings it into the DOM
// by appending it as a child to the element with the class itemlist.
// (The same place the last tbody lived before we removed it)
document.querySelectorAll(".itemlist")[0].appendChild(tbody);
// In some languages, sorting is not a huge pain in the ass. In JS, it just is...
// rows.sort((a, b) => {b[0] - a[0]})
// This line is sorting the rows we built on line 92 based on their score values.
// Sorting in JS is lexicographic by default and isn't smart enough to figure out
// you're giving it numbers, so it will interpret your numbers as strings and
// sort them as such by default. So instead we pass a custom compare function in as
// an argument to rows.sort. This function is written out here as an es6 arrow function.
// These compare functions are supposed to take to elements from the array being
// sorted and return a boolean that determines sort order for the elements, or something.
// Good one to read up on.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
for ([score, elements] of rows.sort((a, b) => { return b[0] - a[0]; })) {
// Okay so we're walking through the sorted news headlines now, each one is a collection of 3
// HTMLElements, so we iterate each of those and append them to our table body.
for (let ele of elements) {
if (ele) tbody.appendChild(ele);
}
}
// Finally, we iterate over the leftover elements and append them to the tbody.
for (let ele of leftovers) {
if (ele) tbody.appendChild(ele);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment