Chapter 9 Exercise Set 1

  1. Write a function countZeroes, which takes an array of numbers as its argument and returns the amount of zeroes that occur in it. Use reduce.

    Then, write the higher-order function count, which takes an array and a test function as arguments, and returns the amount of elements in the array for which the test function returned true. Re-implement countZeroes using this function.

    function countZeroes(array) {
        function counter(total, element) {
            return total + (element === 0 ? 1 : 0);
        }
        return reduce(counter, 0, array);
    }
    

    The weird part, with the question mark and the colon, uses a new operator. In values_chapter we have seen unary and binary operators. This one is ternary ― it acts on three values. Its effect resembles that of if/else, except that, where if conditionally executes statements, this one conditionally chooses expressions. The first part, before the question mark, is the condition. If this condition is true, the expression after the question mark is chosen, 1 in this case. If it is false, the part after the colon, 0 in this case, is chosen.

    Use of this operator can make some pieces of code much shorter. When the expressions inside it get very big, or you have to make more decisions inside the conditional parts, just using plain if and else is usually more readable.

    Here is the solution that uses a count function, with a function that produces equality-testers included to make the final countZeroes function even shorter:

    function count(test, array) {
        return reduce(function(total, element) {
            return total + (test(element) ? 1 : 0);
        }, 0, array);
    }
    
    function equals(x) {
        return function(element) {return x === element;};
    }
    
    function countZeroes(array) {
        return count(equals(0), array);
    }
    
  2. Write a function processParagraph that, when given a paragraph string as its argument, checks whether this paragraph is a header. If it is, it strips off the ‘%’ characters and counts their number. Then, it returns an object with two properties, content, which contains the text inside the paragraph, and type, which contains the tag that this paragraph must be wrapped in, "p" for regular paragraphs, "h1" for headers with one ‘%’, and "hX" for headers with X ‘%’ characters.

    Remember that strings have a charAt method that can be used to look at a specific character inside them.

    function processParagraph(paragraph) {
        var header = 0;
        while (paragraph.charAt(0) == "%") {
            paragraph = paragraph.slice(1);
            header++;
        }
    
        return {type: (header == 0 ? "p" : "h" + header),
                       content: paragraph};
    }
    
    alert(processParagraph(paragraphs[0]));
    
  3. Build a function splitParagraph which, given a paragraph string, returns an array of paragraph fragments. Think of a good way to represent the fragments.

    The method indexOf, which searches for a character or sub-string in a string and returns its position, or -1 if not found, will probably be useful in some way here.

    This is a tricky algorithm, and there are many not-quite-correct or way-too-long ways to describe it. If you run into problems, just think about it for a minute. Try to write inner functions that perform the smaller actions that make up the algorithm.

    Here is one possible solution:

    function splitParagraph(text) {
        function indexOrEnd(character) {
            var index = text.indexOf(character);
            return index == -1 ? text.length : index;
        }
    
        function takeNormal() {
            var end = reduce(Math.min, text.length,
                             map(indexOrEnd, ["*", "{"]));
            var part = text.slice(0, end);
            text = text.slice(end);
            return part;
        }
    
        function takeUpTo(character) {
            var end = text.indexOf(character, 1);
            if (end == -1)
                throw new Error("Missing closing '" + character + "'");
            var part = text.slice(1, end);
            text = text.slice(end + 1);
            return part;
        }
    
        var fragments = [];
    
        while (text != "") {
            if (text.charAt(0) == "*")
                fragments.push({type: "emphasised",
                               content: takeUpTo("*")});
            else if (text.charAt(0) == "{")
                fragments.push({type: "footnote",
                               content: takeUpTo("}")});
            else
                fragments.push({type: "normal",
                               content: takeNormal()});
        }
        return fragments;
    }
    

    Note the over-eager use of map and reduce in the takeNormal function. This is a chapter about functional programming, so program functionally we will! Can you see how this works? The map produces an array of positions where the given characters were found, or the end of the string if they were not found, and the reduce takes the minimum of them, which is the next point in the string that we have to look at.

    If you’d write that out without mapping and reducing you’d get something like this:

    var nextAsterisk = text.indexOf("*");
    var nextBrace = text.indexOf("{");
    var end = text.length;
    if (nextAsterisk != -1)
        end = nextAsterisk;
    if (nextBrace != -1 && nextBrace < end)
        end = nextBrace;
    

    Which is even more hideous. Most of the time, when a decision has to be made based on a series of things, even if there are only two of them, writing it as array operations is nicer than handling every value in a separate if statement. (Fortunately, Regular Expressions describes an easier way to ask for the first occurrence of ‘this or that character’ in a string.)

    If you wrote a splitParagraph that stored fragments in a different way than the solution above, you might want to adjust it, because the functions in the rest of the chapter assume that fragments are objects with type and content properties.

  4. Looking back at the example HTML document if necessary, write an image function which, when given the location of an image file, will create an img HTML element.

    function image(src) {
        return tag("img", [], {src: src});
    }
    
  5. Write a function renderFragment, and use that to implement another function renderParagraph, which takes a paragraph object (with the footnotes already filtered out), and produces the correct HTML element (which might be a paragraph or a header, depending on the type property of the paragraph object).

    This function might come in useful for rendering the footnote references:

    function footnote(number) {
        return tag("sup", [link("#footnote" + number,
                                String(number))]);
    }
    

    A sup tag will show its content as “superscript”, which means it will be smaller and a little higher than other text. The target of the link will be something like "#footnote1". Links that contain a “#” character refer to “anchors” within a page, and in this case we will use them to make it so that clicking on the footnote link will take the reader to the bottom of the page, where the footnotes live.

    The tag to render emphasised fragments with is em, and normal text can be rendered without any extra tags.

    function renderParagraph(paragraph) {
        return tag(paragraph.type, map(renderFragment,
                                       paragraph.content));
    }
    
    function renderFragment(fragment) {
        if (fragment.type == "reference")
            return footnote(fragment.number);
        else if (fragment.type == "emphasised")
            return tag("em", [fragment.content]);
        else if (fragment.type == "normal")
             return fragment.content;
    }