Skip to main content

Ivan Teoh

Something personal yet public

Javascript: 101 Week 3 Track 2

On track 2 week 3, we are continuing reading Eloquent Javascript ebook on chapter 6: Functional Programming and JavaScript code style.

Reflection

  1. Why is it helpful to break a programs logic into functions? As programs get bigger, it helps us easy to understand the program logic.
  2. In JavaScript, functions are first class objects. What does it mean to be a first class object? From Wikipedia - First-class object, first class object is an entity that can be passed as a parameter, returned from a subroutine, or assigned into a variable.
  3. What are anonymous functions, and where would you use them? In computing, an anonymous functions is a function (or a subroutine) defined, and possibly called, without being bound to an identifier. They are convenient to pass as an argument to a higher-order function and are ubiquitous in languages with first-class functions.
  4. Why is it important to follow good code style in your code? From this article, good code style can help in reducing the brittleness of programs.

Homework

Week 3 homework for track 2 are the same as track 1. Here will be the answers that is recommended by JSLint for exercises 6.1 to 6.5. Track 1 will be showing the original answer for exercises 6.1 to 6.5.

  • Ex. 6.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.

    171-exercise61a.js (Source)

    function forEach(array, action) {
        var i;
        for (i = 0; i < array.length; i++) {
            action(array[i]);
        }
    }
    
    function reduce(combine, base, array) {
      forEach(array, function (element) {
        base = combine(base, element);
      });
      return base;
    }
    
    function zeroes(a, b) {
        if (b) {
            return a;
        }
        return a + 1;
    }
    
    function countZeroes(numbers) {
        return reduce(zeroes, 0, numbers);
    }
    
    //document.write(countZeroes([1, 3, 0, 6, 0, 0, 0])); // 4
    

    Using count.

    171-exercise61b.js (Source)

    function forEach(array, action) {
        var i;
        for (i = 0; i < array.length; i++) {
            action(array[i]);
        }
    }
    
    function count(array, test) {
        var total = 0;
        forEach(array, function (element) {
            if (test(element)) {
                total += 1;
            }
        });
        return total;
    }
    
    function zeroes(a) {
        return !a;
    }
    
    function countZeroes(numbers) {
        return count(numbers, zeroes);
    }
    
    //document.write(countZeroes([1, 3, 0, 6, 0, 0, 8, 2])); // 3
    
  • Ex. 6.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 of 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.

    171-exercise62.js (Source)

    function processParagraph(paragraph) {
        // check whether this paragraph is header
        var typeString = 'p';
        var header = 0;
        var i;
        var content;
        for (i = 0; i < paragraph.length; i++) {
            if (paragraph.charAt(i) === '%') {
                header += 1;
            } else {
                break;
            }
        }
    
        if (header) {
            typeString = 'h' + header;
            content = paragraph.substr(header + 1);
        } else {
            content = paragraph.substr(header);
        }
    
    
    
        return {'content': content, 'type': typeString};
    }
    // {'content': 'Language', 'type': 'h2'}
    //console.log(processParagraph("%% Language"));
    // {'content': 'A hermit spent ten years writing a program.', 'type': 'p'}
    //console.log(processParagraph("A hermit spent ten years writing a program."));
    // {'content': 'The Book of Programming', 'type': 'h1'}
    //console.log(processParagraph("% The Book of Programming"));
    
  • Ex. 6.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.

    171-exercise63.js (Source)

    function splitParagraph(paragraph) {
        var paragraphs = [];
        var start, end, content;
    
        function spliting(first, last, typeString) {
            while (true)  {
                start = paragraph.indexOf(first, 0);
                if (start === -1) {
                    break;
                }
                end = paragraph.indexOf(last, start + 1);
                if (end === -1) {
                    break;
                }
                // content does not have "*"
                content = paragraph.slice(start, end + 1).slice(1, -1);
                paragraph = paragraph.slice(0,
                                paragraph.charAt(start) === " "? start - 1 :start)
                                .concat(paragraph.slice(end + 1));
                paragraphs.push({'content': content, 'type': typeString});
            }
        }
    
        // emphasised part
        spliting("*", "*", "emphasised");
    
        // footnote
        spliting("{", "}", "footnote");
    
        // normal text
        paragraphs.push({'content': paragraph, 'type': 'normal'});
        return paragraphs;
    }
    
    // [{'content': 'million', 'type': 'emphasised'},
    //  {'content': 'mine is almost a lines', 'type': 'normal'}]
    //console.log(splitParagraph("mine is almost a *million* lines"));
    // [{'content': 'Type something!', 'type': 'emphasised'},
    //  {'content': "and shouted '' The student", 'type': 'normal'}]
    //console.log(splitParagraph("and shouted '*Type something!*' The student"));
    // [{'content': 'square', 'type': 'emphasised'},
    //  {'content': 'result', 'type': 'emphasised'},
    //  {'content': 'two and then it again, the is already inaccurate!',
    //   'type': 'normal'}]
    //console.log(splitParagraph("two and then *square* it again, the *result* is
    //already inaccurate!"));
    // [{'content': 'million', 'type': 'footnote'},
    //  {'content': 'mine is almost a lines', 'type': 'normal'}]
    //console.log(splitParagraph("mine is almost a {million} lines"));
    // [{'content': 'square', 'type': 'emphasised'},
    //  {'content': 'result', 'type': 'footnote'},
    //  {'content': 'two and then it again, the is already inaccurate!',
    //   'type': 'normal'}]
    //console.log(splitParagraph("two and then *square* it again, the {result} is
    //already inaccurate!"));
    
  • Ex. 6.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.

    171-exercise64.js (Source)

    function tag(name, content, attributes) {
      return {name: name, attributes: attributes, content: content};
    }
    
    function image(path) {
      return tag("img", [], {src: path});
    }
    
  • Ex. 6.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:

    171-exercise65a.js (Source)

    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 emphasized fragments with is em, and normal text can be rendered without any extra tags.

    171-exercise65b.js (Source)

    function splitParagraph(paragraph) {
        var paragraphs = [];
        var start, end, content;
    
        function spliting(first, last, typeString) {
            while (true)  {
                start = paragraph.indexOf(first, 0);
                if (start === -1) {
                    break;
                }
                end = paragraph.indexOf(last, start + 1);
                if (end === -1) {
                    break;
                }
                // content does not have "*"
                content = paragraph.slice(start, end + 1).slice(1, -1);
                paragraph = paragraph.slice(0,
                                paragraph.charAt(start) === " "? start - 1 :start)
                                .concat(paragraph.slice(end + 1));
                paragraphs.push({'content': content, 'type': typeString});
            }
        }
    
        // emphasised part
        spliting("*", "*", "emphasised");
    
        // footnote
        spliting("{", "}", "footnote");
    
        // normal text
        paragraphs.push({'content': paragraph, 'type': 'normal'});
        return paragraphs;
    }
    
    function forEach(array, action) {
        var i;
        for (i = 0; i < array.length; i++) {
            action(array[i]);
        }
    }
    
    function map(func, array) {
        var result = [];
        forEach(array, function (element) {
            result.push(func(element));
        });
        return result;
    }
    
    function tag(name, content, attributes) {
        return {name: name, attributes: attributes, content: content};
    }
    
    function link(target, text) {
        return tag("a", [text], {href: target});
    }
    
    function footnote(number) {
        return tag("sup", [link("#footnote" + number,
                              String(number))]);
    }
    
    function renderFragment(fragment) {
        var content;
        switch (fragment.type) {
            case "emphasised":
                content = tag("em", [fragment.content]);
                break;
            case "footnote":
                content = footnote(fragment.content);
                break;
            default: // case 'normal'
                content = fragment.content;
                break;
        }
        return content;
    }
    
    // {'content': 'A hermit spent ten years writing a program.', 'type': 'p'}
    // {'content': 'two and then *square* it again, the {result} is already
    // inaccurate!', 'type': 'p'}
    function renderParagraph(paragraph) {
        // [{'content': 'square', 'type': 'emphasised'},
        //  {'content': 'result', 'type': 'footnote'},
        //  {'content': 'two and then it again, the is already inaccurate!',
        //   'type': 'normal'}]
        var fragments = splitParagraph(paragraph.content);
        return tag(paragraph.type, map(renderFragment, fragments));
    }
    
    //console.log(renderParagraph({'content': 'two and then *square* it again, the
    //{result} is already inaccurate!', 'type': 'p'}));
    

Comments

Comments powered by Disqus