What to do with lists of things in JavaScript

krofdrakula

Klemen Slavič

Posted on September 23, 2018

What to do with lists of things in JavaScript

Cover image by Internet Archive Book Image

In JavaScript, as in most languages, we have a data structure that deals with lists of values. It's a very handy object that lets us group values together in an ordered list. But there's much more to arrays in JavaScript than just the string index and length property.

JavaScript has borrowed some of the functions that functional programming languages implement in their standard libraries, and made them a bit more convenient by binding them to the Array prototype. In a follow-up post, we'll see how we can apply functional approaches to writing programs that compose better than standard procedural code.

But first, let's dive into the basics.

Part 1: Search

There are many ways to skin this particular cat, depending on what you want to achieve. Let's take a fun data source that provides a list of things that we can practice our JS-fu on:

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); // ----[main]---- function main(json) { // here's where we deal with the data console.log(json.data.children); }

We'll be using /r/reactiongifs on Reddit. Run the example above to see what we're dealing with.

Hint: Any Reddit page can be fetched in JSON form by appending the .json suffix to the URL. Try it!

Question: does every list item match a particular criteria?

Say that we wanted to check that every post in the list contains the acronym MRW in the title field. For this, we use the every() function on the list.

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const postTitleContainsMRW = post => post.data.title.includes('MRW'); function main(json) { const posts = json.data.children; const eachContainsMRW = posts.every(postTitleContainsMRW); console.log('Every post contains MRW?', eachContainsMRW); }

Note: When the function supplied to every() returns false, it stops iterating over the array and immediately returns false. If all items in the array resolve to true, it returns true.

Question: does the list contain any items matching a criteria?

OK, what about if we just want to check if any value matches? Let's look for the word cat in the title using some().

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const postTitleContainsCat = post => post.data.title.includes('cat'); function main(json) { const posts = json.data.children; const anyContainsCat = posts.some(postTitleContainsCat); console.log('Does any post contain the word cat?', anyContainsCat); }

Note: Since this function is the complement of every(), it will stop iteration as soon as the first item resolves to true. If none of the items resolve to true, it returns false.

Question: what's the first item in the list that matches a criteria?

Assuming the answer above was correct (it is dynamic data, after all!), let's find the first post that had the word cat in it. For this, we can use find().

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const postTitleContainsCat = post => post.data.title.includes('cat'); function main(json) { const posts = json.data.children; const catPost = posts.find(postTitleContainsCat); console.log(catPost); }

If no element is found, it returns undefined.

Question: which position is the first found item in?

Just substitute find() by findIndex() and hey presto!

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const postTitleContainsCat = post => post.data.title.includes('cat') function main(json) { const posts = json.data.children; const catPostIndex = posts.findIndex(postTitleContainsCat); console.log(catPostIndex); }

Part 2: Transformation

So far, the methods described above only scan the contents, but other more useful methods allow us to transform an array into something else. Let's start with the basics, though.

Task: get a list of posts matching a criteria

Previously, we only cared about a single (first) value in the array. What about the rest? filter() allows you to do just that.

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const postTitleContainsCat = post => post.data.title.includes('cat'); function main(json) { const posts = json.data.children; const postsWithCats = posts.filter(postTitleContainsCat); console.log(postsWithCats); }

Task: convert each item in the array

Sometimes we need to take an object and transform it into a different format to be consumed by some other component or function. In this case, we can use the map() function.

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const simplifyPost = post => ({ title: post.data.title, image: post.data.thumbnail, animation: post.data.url }); function main(json) { const posts = json.data.children; const simplerPosts = posts.map(simplifyPost); console.log(simplerPosts); }

Note: map() returns a new array of items without changing the original array.

Task: create a summary of the list of items

If you need to produce any kind of summation, rollup or transformation on a list of items, reduce() is the way to go. The gist of this operation is that you give it an initial value, and the function supplied to it will return the next value after processing each item in turn.

For this example, let's create a Set of all words used in the title. Sets are quite useful as they take care of deduplication of items that are already in the set.

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const addWordsFromTitle = (set, post) => { // we lowercase the title first const title = post.data.title.toLowerCase(); // we split along every word boundary which isn't an apostrophe const words = title.split(/[^\w']+/); // for each non-empty word, we add it to the set words.filter(word => word.length > 0) .forEach(word => set.add(word)); // IMPORTANT: we return the set as the next value return set; }; function main(json) { const posts = json.data.children; // NOTE: here we start with an empty set and add words to it const allWords = posts.reduce(addWordsFromTitle, new Set()); console.log(allWords); }

This is a very powerful transformational method and can express almost any kind of operation you can think of, including all of the ones described above! If you want a quick taste of things you can do with just reduce (or fold, as it's called in functional languages), have a look at Brian Lonsdorf's talk below:

Task: order the items within a list

If we want to sort arbitrary values, we need to provide a comparator so that we can tell the sorting algorithm about ordering. To do this, we need to provide a function that takes two items from the array and returns one of three values:

  • -1: when the first item should be before the second item (any negative number will do)
  • 0: when the two items are equivalent in order
  • 1: when the second item should come before the first item (any positive number will do)

Let's sort the items based on title length in decreasing order (longest first). If two titles have the same length, order them alphabetically.

// we set up the data fetch and hand the data // to our main function const fetch = require('node-fetch'); const SOURCE_URL = 'https://www.reddit.com/r/reactiongifs.json'; fetch(SOURCE_URL) .then(response => response.json()) .then(main) .catch(err => console.error(err)); const comparePosts = (a, b) => { const titleA = a.data.title.toLowerCase(); const titleB = b.data.title.toLowerCase(); if (titleA.length > titleB.length) return -1; if (titleA.length < titleB.length) return 1; return titleA.localeCompare(titleB, 'en', { sensitivity: 'base' }); }; function main(json) { // Array.from() creates a copy of the array so that we don't // modify the original data const posts = Array.from(json.data.children); posts.sort(comparePosts); console.log(posts); }

Note: sort() sorts the array in-place, which means that the original array is modified.

Wrapping up

This post covers just the basics of array methods that we'll need when we start implementing a more functional approach in our examples. Until then, keep in mind that whenever you feel the need to write a for loop over an Array, there's probably a way to write that same thing using the methods described above.

Stay curious!

💖 💪 🙅 🚩
krofdrakula
Klemen Slavič

Posted on September 23, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What to do with lists of things in JavaScript
functional What to do with lists of things in JavaScript

September 23, 2018