• 开源镜像
  • 开源沙龙
  • 媛宝
  • 猿帅
  • 注册
  • 登录
  • 息壤开源生活方式平台
  • 加入我们

开源日报

  • 2018年9月1日:开源日报第177期

    1 9 月, 2018

    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg


    今日推荐开源项目:《强化 Node.js ndb》传送门:GitHub链接

    推荐理由:ndb 是谷歌推出的一款 Node.js 的调试工具,毕竟是 Node.js,很多时候你都会看到它。这个 Node.js 的调试工具可以帮助你进行诸如增加断点这样的调试用操作,兴许使用 Node 的朋友什么时候就用的上这个。


    今日推荐英文原文:《A Bit about Binary Tree》作者:Hitesh Kumar

    原文链接:https://medium.com/smelly-code/a-bit-about-binary-tree-111e2d600974

    推荐理由:二叉树,学数据结构的时候,迟早会碰上这个的,而且兴许还会一起碰上左旋转右旋转之类的,不过总而言之,二叉树是迟早都要学的

    A Bit about Binary Tree

    In the previous post, A Gentle Introduction to Trees ?, we acquainted ourselves with trees and their properties. We saw that the naturally hierarchical data are well represented using trees. We learned that the topmost element in the hierarchy/tree which does not have any parent is called the root of a tree. Today’s post talks about a special type of tree called Binary Tree.

    Binary tree is a tree where each node can have maximum two children. Since there are only two children so it’s quite intuitive to call them left and right child. Let’s get our hands dirty and code a BinaryTreeNode class in JavaScript as per the definition above.

    /**
     * Represent a BinaryTreeNode
     */
    class BinaryTreeNode {
        constructor(value) {
            // Value which our beloved node represents.
            this.value = value;
            // Reference of the left child.
            this.left = null;
            // Reference of the right child.
            this.right = null;
        }
    }

    Here’s our first binary tree created using the BinaryTreeNode class.

    const root = new BinaryTreeNode(10);
    const left = new BinaryTreeNode(20);
    const right = new BinaryTreeNode(30);
    root.left = left;
    root.right = right;
    console.log(root);
    // Our first binary tree.
    // 
    //    10
    //   /  \
    //  20   30

    This is a naive way to create a binary tree. There are many far better ways to create a binary tree. We will discuss some of them as we move on.

    There are four basic operations Create, Read, Update and Delete(CRUD), which are performed on every data structure. Let’s perform them on Binary Tree as well. At first, we’ll start with the “Read” operation cause it’s relatively easy(a million thanks to recursion). Also, it will strengthen our bond with the binary tree.

    Traversal

    Reading data from a data structure is also known as traversal. As we know Binary Tree is a non-linear data structure. So its data can be traversed in many ways. In general, data traversal for trees is categorized as Breadth First Traversal(BFS) aka Level Order Traversal and Depth First Traversal(DFS).

    Note. These traversal methods are not specific to binary trees. They can be used for any tree. But, for brevity, we’ll only discuss them for binary trees.

    Depth First Traversal

    In depth-first traversal, we start with the root node and go into the depth of it. We dive deep as much as possible until we reach the end. Once we reach the bottom i.e. the leaf node, we backtrack to traverse other nodes. It’s really important to notice that while diving or backtracking, how are we dealing with the root node or the node acting as root node? Are we traversing root node before diving? Are we traversing root node after backtracking? Answer of these questions leads to 3 new categories of depth-first traversal in-order, pre-order and post-order. I hope I my poor articulation is not confusing you.

    Inorder Traversal

    Inorder Traversal (Source)

    For in-order traversal, we start with the root node and go to the left of the tree as far as possible until we reach the end or leaf node. If there’s any leaf node, we read its data and backtrack to the immediate parent node or root node. Otherwise, we simply read data from the root node and move the right child, if there’s any, and repeat the same for the right node. The traversal sequence for in-order is left-root-right. Here are some sample binary trees which we will use for traversing.

           Sample binary trees
           ====================
        1)                      2)
      -----                   ------
        a                       j
      /   \                   /   \
     b     c                 f     k
                           /  \     \
                          a    h     z

    For tree 1) and 2), in-order traversals will be b-a-c and a-f-h-j-k-z. Let me explain how we got them. For tree 1), we start with its root node i.e. node a and move to the left that is node b. Then we again move to the left of node b. Since there’s no node to visit which means we have reached to the farthest left. We read/print its data and move back(backtrack) to the root node a. We read data from it and move to its right child that is node c. and repeat the same for it. Similarly, we can traverse tree 2).

    Let’s see how does it look in the code.

    /**
     * Prints the values of binary tree in-order.
     * 
     */
    const traverseInorder = (root) => {
      
      if (root === null) {
        return;
      }
      
      // Traverse the left node.
      traverseInorder(root.left);
      
      // Print root node's value.
      console.log(root.value);
      
      //Traverse the right node.
      traverseInorder(root.right);
    };

    We learned from our previous post that trees are recursive in nature. So we leveraged their recursiveness and wrote a recursive function traverseInorder which traverses a binary tree in order. As we know that for every recursive solution there’s an equivalent iterative solution. And we can go with the iterative solution as well but to keep it simple we will continue with recursion and will discuss the iterative solution on some other day.

    In-order traversal is commonly used for binary search tree cause for BST it retrieves data in sorted order.

    Preorder Traversal

    Preorder Traversal (Source)

    Pre-order traversal is similar to in-order. The only difference is we traverse the root node first then, we move to the left and the right child. The traversal sequence is root-left-right. For tree 1) and 2) in our boring example pre-order traversals are a-b-c and j-f-a-h-k-z. Here’s the Js code.

    /**
     * Print the values of binary tree in pre-order.
     * 
     */
    const traversePreOrder = (root) => {
      if (root === null) {
        return;
      }
      // Print root node's value.
      console.log(root.value);
      // Traverse the left node.
      traversePreOrder(root.left);
      //Traverse the right node.
      traversePreOrder(root.right);
    };

    If you look at the code, you’ll realize that we have only changed the order of statement compared to in-order traversal. And that’s all is required.

    Postorder Traversal

    Post Traversal (Source)

    In post-order traversal, we traverse the root node in the end. The traversal sequence is left-right-root. For tree 1) and 2) post-order traversal will be b-c-a and a-h-f-z-k-j. Js code:

    /**
     * Print the values of binary tree in post-order.
     * 
     */
    const traversePostOrder = (root) => {
      if (root === null) {
        return;
      }
      // Traverse the left node.
      traversePostOrder(root.left);
      //Traverse the right node.
      traversePostOrder(root.right);
      // Print root node's value.
      console.log(root.value);
    };

    Breadth First Traversal

    In the breadth-first traversal, we traverse tree horizontally i.e. we traverse tree by levels. We start with the root node (or first level), explore it and move to the next level. Then traverse all the nodes present on that level and move to the next level. We do the same for all remaining levels. Since nodes are traversed by levels, so breadth-first-traversal is also referred as Level Order Traversal. Here’s an illustration.

    Depth-First vs Breadth-First (Source)

    We accomplish level order traversal with the help of a queue. We create an empty queue and enqueue the root node. Then we do the followings, while the queue is not empty.
    1. Dequeue a node.
    2. Explore the node.
    3. If the node has a left child, enqueue it.
    4. If the node has a right child, enqueue it.

    /**
     * Traverses  binary tree in level order. 
     * 
     */
    const levelOrder = (root) => {
      if (root === null) {
        return;
      }
      
      const queue = [root];
      while (queue.length) {
        const node = queue.shift();
        // pirnt/explore node.
        console.log(node.value);
        // enqueue left
        if (node.left !== null) {
          queue.push(node.left);
        }
        // enqueue right.
        if (node.right !== null) {
          queue.push(node.right);
        }
      }
    };

    Create

    There’s no restriction on the data stored in a binary tree which gives us the flexibility to create it. We will discuss following two popular methods to create a binary tree:
    1. From array representation.
    2. Level Order Insertion.

    From array representation

    A binary tree can also be represented using an array. For child i, in the array representation, the left child will be at 2*i + 1 and the right child will be at 2*i + 2. The index of the root node is 0. Here’s the JS code to create binary tree from array representation.

    /**
     * Creates a binary tree from it's array representation.
     * 
     * @param {any[]} array
     */
    const createTree = (array) => {
      if (!array.length) {
        return null;
      }
      const nodes = array.map(
        (value) => (value !== null ? new BinaryTreeNode(value) : null)
      );
      nodes.forEach((node, index) => {
        if (node === null) {
          return;
        }
        const doubleOfIndex = 2 * index;
        // Left node -> (2 * i) + 1.
        const lIndex = doubleOfIndex + 1;
        if (lIndex < array.length) {
          node.left = nodes[lIndex];
        }
        // Right node -> (2 * i) + 2.
        const rIndex = doubleOfIndex + 2;
        if (rIndex < array.length) {
          node.right = nodes[rIndex];
        }
      });
      return nodes[0];
    }

    Let’s consider an array and use above function to create a binary tree.

    const array = [1, 2, 3, 4, 5, 6];
    const tree = createTree(array);
    console.log(tree);
    // Tree represented by array.
    //                1
    //             /     \
    //            2       3
    //          /  \     /
    //         4    5   6

    Level Order Insertion

    In this approach, we insert an item at the first available position in the level order.

    Level Order Insertion (Source)

    Since we are inserting values in level order, so we can use iterative level order traversal. If we find a node whose left child is null then we assign a new node to its left child. Else if we find a node whose right child is null then we assign a new node to its right child. We keep traversing until we find a left or a right null/empty child. Here’s the JS code for level order insertion.

    /**
     * Insert the value into the binary tree at
     * first position available in level order.
     * 
     */
    const insertInLevelOrder = (root, value) => {
      const node = new BinaryTreeNode(value);
      if (root === null) {
        return node;
      }
      const queue = [root];
      while (queue.length){
        const _node = queue.shift();
        if (_node.left === null) {
          _node.left = node;
          break;
        }
        queue.push(_node.left);
        if (_node.right === null) {
          _node.right = node;
          break;
        }
        queue.push(_node.right);
      }
      return root;
    }

    Update

    Updating a value of a node in a binary tree is relatively easy, cause values stored in a binary tree doesn’t have to maintain any specific tree property. To update the value, we simply find the node, whose value is being updated, using one of the traversals discussed above and replace its value.

    Delete

    Deleting a node form a binary tree is not as easy as it seems. It’s because there are so many scenarios which we need to deal with while deleting. Scenarios like:
    1. Is targeted node a leaf node?
    2. Does targeted node only have a left child?
    3. Does targeted node only have a right child?
    4. Does targeted node have both the left and the right child?
    5. Are we only deleting the node and not its subtree?
    6. Are we deleting both the node and its subtree?
    To avoid intricacies, we’ll only consider case 6 here i.e. we delete both the node and its subtrees. We’ll discuss other scenarios someday with Binary Search Tree.

    To delete/remove a node from its subtree we need the reference of its parent. If the target is on the left of parent then we set left of the parent to null else we set right of the parent to null. We let the garbage collector take care of the reset i.e. removing subtrees and freeing the memory. We are simply setting the reference to null, so it doesn’t matter which traversal we use. We’ll end up with the same result, but time and space complexities may vary. Let’s use Breadth-First Traversal for now.

    /**
     * Removes a node and it's subtrees from a 
     * binary tree.
     * @param root
     * @value 
     * 
     * @return updated root.
     */
    const remove = (root, value) => {
      if (root === null) {
        return null;
      }
      if (root.value === value) {
        return null;
      }
      const queue = [root];
      while (queue.length) {
        const node = queue.shift();
        
        // If target is on left of parent.
        if (node.left && node.left.value === value) {
          node.left = null;
          return root;
        }
        queue.push(node.left);
        
        // If target is on right of parent.
        if (node.right && node.right.value === value) {
          node.right = null;
          return root;
        }
        queue.push(node.right);
        
      }
      return root;
    };
    let tree = fromArrayRepresentation([1, 2, 3, 4]);
    tree = remove(tree, 2); // Remove 2 and 4(its child of 2).
    console.log(tree);

    Note: If a language(like C or C++) doesn’t have the garbage collector, then we may need to traverse the tree in post-order remove the children first before deleting the targeted node. For more detail please refer here.

    Summary

    Binary Tree is a special tree in which each node can at most contain two children called left and right. To read/traverse data from a binary tree we have two major approaches Depth-First Traversal and Breadth-First Traversal. Depth-First traversal is further classified in pre-order, in-order, post-order traversal. In depth-first traversal, we visit the node and its children first, then we visit siblings of the node. In breadth-first traversal, we visit the node and its siblings(nodes which are at the same level) first, then we move to the next level. Depth-First traversal is also referred as Level Order Traversal. There are many ways to create a binary tree. One can create a binary tree from its array representation. In array representation value at ith, index will have children at 2*i + 1 and 2*i + 2. A binary tree can also be created using level order insertion where any new value is inserted at first available position in the level order. Updating a value in a binary tree is relatively easy because values in a binary tree don’t maintain any specific property with respect to each other. Among all operations, deleting a node is a bit complicated cause a node may carry children. So we have to take care of its children as well.


    I have created a repository on Github which contains the implementation of the binary tree along with many other data structures in JavaScript. Please feel free to fork/clone it. If you have any suggestion or correction, please post them in the comment section. Thank you for reading.


    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg

  • 2018年8月31日:开源日报第176期

    31 8 月, 2018

    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg


    今日推荐开源项目:《React 应用管理器 Guppy》传送门:GitHub链接

    推荐理由:这是个面向初心者的 React 应用管理器,对于新手来说命令行可能神秘又易碎,稍微输错一点点就会瞬间爆炸,但是图形界面能够让他们安心下来,而 Guppy 则是提供了一个图形界面,可以用它来创建新任务,然后构建运行测试。而且对于初心者来说,中文可能比英文看起来更好,它也提供了中文的文档。虽然这么说,但是实际上这个项目还算是一个早期项目,可能还有些地方不够完善,使用的时候需要小心谨慎。


    今日推荐英文原文:《The Top 10 Things Wrong with JavaScript》作者:Richard Kenneth Eng

    原文链接:https://medium.com/javascript-non-grata/the-top-10-things-wrong-with-javascript-58f440d6b3d8

    推荐理由:在 JS 中你最可能中的十大误区,使用 JS 的初学者可以考虑看看这个……想当年我也是写出过 if(i=1) 这种判断然后纠结了半天的新手,从一开始就避免误区可不是个坏事

    The Top 10 Things Wrong with JavaScript

    avaScript has a reputation for being one of the worst programming languages in existence, and for good reasons! JavaScript is easy to learn and easy to use, except when it’s not. There are many “gotchas” that can trip you up. Below, I glean some of the best from various online sources…

    1) There is no integer type! JavaScript has only one numerical type and that’s (double precision) floating point. This is the only language I’ve ever used that doesn’t have an integer type (and I’ve used many). Most of the time, you can get away with it, but there will come a day when it will bite you in the ass. Especially if you need 64-bit integers.

    0.1 + 0.2 → 0.30000000000000004
    0.1 + 0.2 === 0.3 → false // ???
    x = 1.0000000000000001
    x === 1 → true // ???
    typeof NaN → "number" // NaN is a number??? But...
    NaN != NaN → true
    NaN !== NaN → true

    2) JavaScript’s loose typing and aggressive coercions exhibit odd behaviour.

    [] + [] → "" // Empty string? These are arrays!
    [] + {} → [object object]
    {} + [] → 0 // Why isn't the operation commutative???
    {} + {} → NaN // ???
    16 == [16] → true // Array converted into string, then into number
    16 == [1,6] → false // But what is array converted into?
    "1,6" == [1,6] → true
    var arr = [];
    arr.length → 0
    arr[3] → "undefined" // No array bounds exception???
    arr[3] = "hi";
    arr.length → 4 // 4??? Only one element has been added!
    arr["3"] → "hi" // Apparently "3" is coerced into a number
    delete(arr[3]);
    arr.length → 4 // 4??? There are no elements in the array!
    arr[3] → "undefined" // 7 lines above, length was "0"!
    var i = 1;
    i = i + ""; // Oops!
    i + 1 → "11"
    i - 1 → 0
    var j = "1";
    ++j → 2 // Okay, but...
     
    var k = "1";
    k += 1 → "11" // What???
     
    [1,5,20,10].sort() → [1, 10, 20, 5] // Why is it sorting strings???
    xs = ["10", "10", "10"];
    xs.map(parseInt) → [10, NaN, 2] // ???
    y = {};
    y[[]] = 1;
    Object.keys(y) → [""]

    This is nonsense. If a language possesses insane semantics, then it is dangerous to use. If it’s difficult to reason about the language, then it is dangerous to use.

    Incidentally, ECMA should remove “==” and force everyone to use “===”. Having both is damn confusing.

    3) Automatic semicolon insertion. This can cause subtle bugs and unexpected behaviour. Why does this “feature” even exist??? It’s super weird and unnecessary. Get rid of it, ECMA, please!

    4) JavaScript is seriously abused. Much of the code in the wild, especially those in commonly used libraries, are very badly written. The authors have abused the language every which way, and we pay for it in terms of performance and/or ability to reason about the code.

    Programmers often have to write workarounds for various problems in the language and these workarounds are immensely complex and cumbersome to understand. For example, the module pattern and other attempts to create private scopes with localized state and logic that use closures and functions wrapped in functions, wrapped in functions, are beyond mad. Thankfully, this is fixed in ES6, but I present it as one of many examples that are still widely prevalent.

    This is a language issue because JavaScript’s nature makes it easy, and often necessary, to write convoluted, difficult-to-understand code. Peter DiSalvo goes into more details here, and he is quite eloquent!

    5) JavaScript is highly dependent on global variables. Implied global variables are especially problematic (“use strict” to avoid). Global variables seriously complicate your programs.

    JavaScript also has horrible scoping rules.

    6) JavaScript code can fail silently due to syntactical slip-ups. It has happened to me several times, and tracking down the reason can be most exasperating.

    7) Object prototypes do not scale well to large applications; it’s a rather primitive and sloppy way to do object-oriented programming (but it’s flexible!). Further, there are multiple ways to handle object inheritance, making it difficult to decide which way to go. JavaScript is the only popular OOP language that uses object prototypes. The preference for class-based OOP is clear, such that ES6 and TypeScript employ classes. Nevertheless, their classes are not as completely fleshed out as you would find in Smalltalk and C++, for example.

    And because the use of object prototypes is so poorly understood by most JavaScript developers, they abuse the language and write horrible code as a result.

    8) Asynchronous programming in JavaScript is very messy. Callback hell is a frequent complaint. (Promises mitigate this to some extent, but are not a perfect solution.)

    9) Douglas Crockford says that JavaScript is “Lisp in C’s Clothing.” Lisp is a wonderful language and by extension, so is JavaScript.

    But JavaScript is nothing like Lisp!

    • JavaScript’s C-like syntax robs it of Lisp’s clean and elegant syntax.
    • Lisp’s central data structure is the list. JavaScript doesn’t have a list data type. JavaScript’s central data structure is the associative array, which often masquerades as some other data type.
    • Lisp is homoiconic, i.e., its code and its data have the same primary representation. JavaScript is not. Not even a little bit.
    • Lisp’s homoiconicity provides for a powerful macro system. JavaScript does not have macros.
    • Lambdas do not make JavaScript Lisp-like, any more than C++, Java, Python, Haskell, and Smalltalk are Lisp-like.

    If you like Lisp, then use Lisp. Use Clojure or ClojureScript. Don’t use JavaScript, which has little reason to exist other than its ubiquity in web browsers.

    10) The main draw of JavaScript is actually in frameworks like Node.js and AngularJS. If not for them, why would you use JavaScript??? Prior to 2009, when Node.js was released, people generally avoided using JavaScript; its use was limited to some jQuery and webpage enhancements. Everyone understood that JavaScript was a terrible language, and so it was used only sparingly. But then, someone thought it was a good idea to put this awful language on the server.

    The front-end JavaScript landscape is highly fragmented and unstable. Frameworks come and go, depending on current trends and whims. AngularJS was the darling of front-end development for a long while, but suddenly ReactJS came out of nowhere to vie for top spot. This makes it dicey to invest a great deal of time and effort in any one particular framework. (The transition from Angular 1.x to 2.0 has been especially disruptive.)

    But why use any of these frameworks, only to inflict JavaScript on yourself? Accept JavaScript as the “assembly language” of the Web and select better tools that “transpile” to it. Use Amber. Use ClojureScript. Use Ceylon. Use GopherJS. Hell, use Dart! Use anything but JavaScript.

    And guess what? There’s already a de facto standard web framework that’s been around for ages and is rock-solid, well-supported, and widely used. It’s called jQuery and it’s all you really need to create a terrific UX. It’s supported by every transpiled language. Forget Angular, React, Backbone, Ember, Meteor, Polymer, Mithril, Aurelia, etc., etc. etc. Are you kidding me?!!


    JavaScript apologists frequently tout using JSLint as a universal solution to JavaScript’s problems. However, this tool has major limitations. It’s also highly opinionated. Moreover, it’s difficult to incorporate into your workflow, which is why many JavaScript developers do not use it. And finally, JSLint’s output results are subject to interpretation and you need to decide what to do about them; sometimes, you actually want to break the “rules!” This additional cognitive burden only makes your job harder, as if programming wasn’t already hard enough. Is it too much to ask for a programming language that doesn’t have such horrendous problems that I need a tool to help me avoid them?


    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg

  • 2018年8月30日:开源日报第175期

    30 8 月, 2018

    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg


    今日推荐开源项目:《Go 语言处理 prose》传送门:GitHub链接

    推荐理由:这是一个用于语言处理的 Golang 库,在新的版本中它成为了 Go 的自然语言处理库,它可以做到诸如把语言符号化之后分割,命名实体识别(这名字是个人还是个地名等等)这样的工作,这个库接下来将会向着简单实用的方向发展,有兴趣的话可以关注一下。


    今日推荐英文原文:《The code I’m still ashamed of》作者:Bill Sourour

    原文链接:https://medium.freecodecamp.org/the-code-im-still-ashamed-of-e4c021dff55e

    推荐理由:今天的文章大家就当个故事看吧,不过要知道,每个人都应该为他们做过的所有事情负起责任

    The code I’m still ashamed of

    If you write code for a living, there’s a chance that at some point in your career, someone will ask you to code something a little deceitful – if not outright unethical.

    This happened to me back in the year 2000. And it’s something I’ll never be able to forget.

    I wrote my first line of code at 6 years old. I’m no prodigy though. I had a lot of help from my dad at the time. But I was hooked. I loved it.

    By the time I was 15, I was working part-time for my dad’s consulting firm. I built websites and coded small components for business apps on weekends and in the summer.

    I was woefully underpaid. But as my dad still likes to point out, I got free room and board, and some pretty valuable work experience.

    Later, I managed to help fund a part of my education through a few freelance coding gigs. I built a couple of early e-commerce sites for some local small businesses.

    By age 21, I managed to land a full-time coding job with an interactive marketing firm in Toronto, Canada.

    The firm had been founded by a medical doctor and many of its clients were large pharmaceutical companies.

    In Canada, there are strict limits on how pharmaceutical companies can advertise prescription drugs directly to consumers.

    As a result, these companies would create websites that present general information about whatever symptoms their drugs were meant to address. Then, if a visitor could prove they had a prescription, they were given access to a patient portal with more specific info about the drug.

    The home page of edfactscanada.com circa 2001, via The Internet Archive

    One of the projects I was assigned to involved a drug that was targeted at women. The graphics and general style of the website made it clear that the client wanted to specifically target teenage girls.

    One of the features of this website was a quiz that asked girls a series of questions and recommended a type of drug based on their answers.

    Remember, this website was posing as a general information site. It was not clearly an advertisement for any particular drug.

    When I received the requirements, they contained the questions for the quiz, along with multiple choice answers for each question.

    Missing from the requirements was any indication of what I should do with the answers at the end of the quiz. So what rules determined what treatment the quiz would recommend?

    I spoke to the Account Manager about this. She emailed the client and got me the requirements. With those, I proceeded to code up the quiz.

    Before submitting the website to the client, my project manager decided to give it a quick test. She tried the quiz, then came over to my desk:

    “The quiz doesn’t work,” she said.

    “Oh. What’s broken?” I asked.

    “Well, it seems that no matter what I do, the quiz recommends the client’s drug as the best possible treatment. The only exception is if I say I’m allergic. Or if I say I am already taking it.”

    “Yes. That’s what the requirements say to do. Everything leads to the client’s drug.”

    “Oh. Okay. Cool.”

    And she was off.

    I wish I could tell you that when I first saw those requirements they bothered me. I wish I could tell you that it felt wrong to code something that was basically designed to trick young girls. But the truth is, I didn’t think much of it at the time. I had a job to do, and I did it.

    Nothing that we were doing was illegal. As the youngest developer on my team, I was making good money for my age. And in the end, I understood that the real purpose of the site was to push a particular drug. So, I chalked this tactic up to “marketing.”

    The client was extremely pleased with the site. So much so that their rep invited me and the entire team out to a fancy steak dinner.

    The day of the dinner, shortly before leaving the office, a colleague emailed me a link to a news report online. It was about a young girl who had taken the drug I’d built the website for.

    She had killed herself.

    It turned out that among the main side effects of that drug were severe depression and suicidal thoughts.

    The colleague who had emailed me didn’t show up to dinner.

    I still went. It was difficult and awkward. I never mentioned the news report. I just ate my steak quietly and tried to force a smile when I could.

    The next day, I called my sister. She was 19 at the time. We had discovered while working on the project that she had actually been prescribed the very drug I was building a site for.

    When we first talked about it, we thought the whole thing was a neat coincidence. Now, the tone of our conversation was very different. I advised her to get off the drug ASAP. Thankfully, she listened.

    There are a million and one ways for me to rationalize my part in later suicides and severe depression. Even today, there is ongoing litigation with former patients.

    It’s easy to make an argument that I had no part in it at all. Still, I’ve never felt okay about writing that code.

    Not long after that dinner, I resigned.

    As developers, we are often one of the last lines of defense against potentially dangerous and unethical practices.

    We’re approaching a time where software will drive the vehicle that transports your family to soccer practice. There are already AI programs that help doctors diagnose disease. It’s not hard to imagine them recommending prescription drugs soon, too.

    The more software continues to take over every aspect of our lives, the more important it will be for us to take a stand and ensure that our ethics are ever-present in our code.

    Since that day, I always try to think twice about the effects of my code before I write it. I hope that you will too.


    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg

  • 2018年8月29日:开源日报第174期

    29 8 月, 2018

    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg


    今日推荐开源项目:《JS 验证库 v8n》传送门:GitHub链接

    推荐理由:这是一个可以用来做各种各样细的验证的库,它里面的语法看起来和普通的语法很像所以很容易就能理解,而且它的确提供了各种各样方面的验证方式,从检查所有的字母是不是都是小写到字符串的开头结尾,有兴趣的朋友可以尝试一下。


    今日推荐英文原文:《Algorithms: Common edge cases in JS》作者:Jeremy Gottfried

    原文链接:https://medium.com/jeremy-gottfrieds-tech-blog/algorithms-common-edge-cases-in-js-ce35a2d47674

    推荐理由:极端情况,有的情况兴许你的程序一百年也见不到一次,但是为了预防可能出现的各种情况,我们还是应当改进程序以应对极端情况

    Algorithms: Common edge cases in JS

    Algorithms are the bedrock of computer programming. Algorithms are built to handle specific data structures with a range of accepted parameters. An edge case is a problem or situation that occurs only at an extreme (maximum or minimum) operating parameter. As programmers, how can we predict these cases in order to protect our app from breaking?

    I will demonstrate some common edge cases in a basic bubble sort algorithm:

    // bubbleSort.js
    function bubbleSort(array) {
      var swapped;
      do {
        swapped = false;
        for(var i = 0; i < array.length; i++) {
          if(array[i] > array[i + 1]) {
            swap(array, i, i + 1);
            swapped = true;
          }
        }
      } while(swapped);
      return array;
    }
    function swap(array, i, j) {
      var temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }

    1. Accepted data structure

    The first major edge case is the unaccepted data structure. My bubble sort algorithm only works predictably for arrays. Javascript will not prevent you from inputting other data structures in the function, such as normal objects. For example, what happens if I input this object:

    var obj = {0: 0, 
               1: 1, 
               2: 2, 
               3: 3}

    This will do some very strange things inside the for loop, but ultimately, our algorithm will return the original object.

    --> obj

    That may not be what we want! If the algorithm is going to be truly fool proof, we should test that our data structure is an array, and handle it somehow. Here I will return false so that we can handle unwanted data structures as an error in our program:

    if (!Array.isArray(arr)) {
     
        return false
        }

    This will also return false for falsey values like null or undefined.

    2. Emptiness

    An empty array would not break this specific sorting algorithm, but empty values are something important to look out for:

    bubbleSort([]) 
    --> []

    In the context of many programs, we want to handle an empty array differently so that it doesn’t break something in our program further down the line:

    if (!arr[0]) {
        return false
    }
    or
    if (arr.length === 0) {
        return false 
    }

    For algorithms that accept different data structures and types, we should also look out for emptiness. Here are some common ones:

    var str = ''
    var obj = {}
    var arr = []
    var num = 0

    If our algorithm iterates through nested data structures, we may also want to check for nested emptiness like this:

    [[],[],[],[[]],[[[]]]]

    3. Accepted types

    The next big case to test for is accepted types inside our array. Our bubbleSort algorithm sorts strings based on their Unicode values. That means that capital letters will go before lowercase:

    bubbleSort([a,A,b,B])
    ---> [A,B,a,b]

    If we want the algorithm to sort strings independent of capitalization, we may want to handle that:

    if (arr[j-1].toLowerCase > arr[j].toLowerCase) {            
                    var temp = arr[j-1];            
                    arr[j-1] = arr[j];            
                    arr[j] = temp;         
               }

    Another case you may want to handle are string numbers vs normal numerical values. Here is one strange example of this:

    2 > '2' ---> false 
    2 < '2' ---> false 
    2 === '2' ---> false 
    2 == '2' ---> true

    In the case of two equal numbers, where one is a string, the only operator that evaluates to true is == . It gets even more tricky if there is a letter in the numerical string.

    1 > '1a' ---> false 
    1 < '1a' ---> false 
    1 === '1a' ---> false 
    1 == '1a' ---> false 
    2 > '1a' ---> false 
    2 < '1a' ---> false

    This could lead to some strange results in our sorting algorithm. Therefore, we may want to only accept numbers and not strings, or handle the different types separately.

    Another type to be aware of is the boolean value. true is loosely equal to 1 while false is loosely equal to 0 .

    true == 1 ---> true 
    false == 0 ---> true 
    true > false ---> true 
    true < 2 ---> true 
    true < 'a' ---> false
    false < 'a' ---> false

    We may not want to allow boolean values in our algorithm, especially if we are sorting an array of strings. This includes falsey values like null, undefined and NaN , which will all act strange when passed into a comparison operator.

    The falsey data types all act differently in the comparison operators:

    null < 2 ---> true
    null == 1 ---> false 
    null > 0 ---> false 
    null === null ---> true
    undefined < 2 ---> false 
    undefined > 0 ---> false 
    undefined == 1 --> false
    undefined === undefined ---> true 
    NaN === NaN ---> false 
    NaN > 0 ---> false 
    NaN < 2 ---> false 
    NaN == 1 ---> false

    You may want to exclude these from your sorting algorithm.

    4. Large Values

    In our case, we should be looking out for extremely large arrays. What happens if our function is given an array with length greater than 1 x 10¹⁰⁰?

    Here is where Big O notation comes in.

    Big O complexity

    Big O complexity is a measure of algorithm efficiency. It makes a big difference when we are dealing with large data sets. Big O has two variables — time and memory space. Memory is important because it can be break our algorithm if large amounts of data are passed as parameters. Every computer program stores data in stack and heap memory. Stack memory is much faster to access but there’s less of it. How does this relate to algorithms?

    Let’s answer this with two versions of bubble sort, recursive and iterative —

    // bubbleSort.js
    function swap(array, i, j) {
      var temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
    /// Bubble Sort using for loops
    function bubbleSort(array) {
      var swapped;
      do {
        swapped = false;
        for(var i = 0; i < array.length; i++) {
          if(array[i] > array[i + 1]) {
            swap(array, i, i + 1);
            swapped = true;
          }
        }
      } while(swapped);
      return array;
    }
    // Bubble sort using recursion
    var swapped = true
    function recursiveSwapping(arr, i = 0) {
      var len = arr.length;
      if (i >= len) {
        return arr
      }
        if (arr[i] > arr[i + 1]) {
          swap(arr, i, i + 1);
          swapped = true
        }
        return recursiveSwapping(arr, i + 1)
    }
    function recursiveBubbleSort(arr) {
        if (!swapped) {
          return arr
        }
            swapped = false
            recursiveSwapping(arr, i = 0)
            return recursiveBubbleSort(arr)
    }

    These two examples use the same exact algorithm, but the memory is handled differently. The recursive version of the algorithm uses stack memory, and will therefore break with a stack overflow error if the length of the array is very large. This is because the stack keeps track of every call of recursiveSwappinguntil the original call resolves. The iterative version of the function doesn’t face stack overflow issues.

    In theory, the recursive version shouldn’t face memory issues either. Many lower level languages allow something called tail-call optimization, which makes it possible to to write recursive functions with 0(1) memory complexity. Until recently, JS did not allow tail call optimization, but in ECMAScript 6 you can enable it in strict mode. Simply type 'use strict’ at the top of the file, and you will be able to write recursively without running into stack overflow errors.

    'use strict'

    Just make sure that your recursive function calls are written in the last line, or tail, of your function like in the function below.

    function recursiveSwapping(arr, i = 0) {
      var len = arr.length;
      if (i >= len) {
        return arr
      }
        if (arr[i] > arr[i + 1]) {
          swap(arr, i, i + 1);
          swapped = true
        }
    // tail call below 
        return recursiveSwapping(arr, i + 1)
    }

    5. Some other edge cases to keep in mind

    1. Odd/even — numbers and string length
    2. Negative numbers
    3. Special characters
    4. Duplicate elements
    5. Length of 1
    6. Long string or large number
    7. Null or Falsey values

    每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,欢迎关注开源日报。交流QQ群:202790710;微博:https://weibo.com/openingsource;电报群 https://t.me/OpeningSourceOrg

←上一页
1 … 215 216 217 218 219 … 262
下一页→

Proudly powered by WordPress