translation

2020-06-20 00:00:00 +0000

ES6 的函数式 js —– 递归模式

原文地址:https://medium.com/dailyjs/functional-js-with-es6-recursive-patterns-b7d0813ef9e3

作者:Casey Morris

这是处理什么问题? 函数式编程正在兴起,这是一个令人非常兴奋的话题。它使我可以编写简洁的声明性代码,易于测试和推理。 什么是函数式编程? 我将把答案回答给对此主题有更多了解的人,Eric Elliot 说:

函数式编程(通常缩写为FP)是通过组合纯函数、避免共享状态、可变数据和副作用来构建软件的过程。函数式编程是声明式的,而不是命令式的,应用的状态通过纯函数传递。与面向对象编程相反,在面向对象编程中,应用的状态通常与对象中的方法共享和同步。

ES6带来了许多特性,让我们可以轻松编写纯函数,而 rest/spread 是最强大的功能之一。使用 rest 参数,我们可以使用递归进行无循环的循环。 在本文中,我们将重写许多支持函数模式的常用 JavaScript 方法/函数。

前言

以下函数用于演示和学习。下面的许多函数都是尾部递归的,应该进一步优化。优化尾部递归不是本文的主题。ES6带来了尾部调用优化,但必须与“严格模式”一起使用。

返回数组中的第一项。当您需要将第一项与数组的其余项分隔开时,非常有用。为此,我们使用了解构赋值。

const head = ([x]) => x

示例:

const array = [1,2,3,4,5]
head(array) // 1

Tail

返回数组中除第一项以外的所有项。

const tail = ([, ...xs]) => xs

上面的示例与如下所示本质上是一样的

const tail = ([x, ...xs]) => xs

因为我们不需要在结果中使用 x,所以我们可以删除它,但是保留逗号以获取数组中的其他项。

示例:

const array = [1,2,3,4,5]
tail(array) // [2,3,4,5]

Def

返回参数是否已定义。

const def = x => typeof x !== 'undefined'

示例:

const defined = 'this is defined'
def(defined) // true
def(doesntExist) // false

Undef

返回参数是否未定义。

const undef = x => !def(x)

示例:

const defined = 'this is defined'
undef(defined) // false
undef(doesntExist) // true

Copy

不使用 array .slice() 返回数组的拷贝。利用 spread。

const copy = array => [...array]

示例:

let array = [1,2,3,4,5]
let copied = copy(array)
copied.push(6)

array // [1,2,3,4,5]
copied // [1,2,3,4,5,6]

Length

返回数组的长度。这是使用递归循环遍历数组的一种非常简单的形式,尽管在本例中数组的值无关紧要(数组中的每一项都从 1 开始递增)。我们使用 len 参数来避免尾部递归。

const length = ([x, ...xs], len = 0) => def(x) ? length(xs, len + 1) : len

如果我们不关心尾部递归,我们可以把它写成

const length = ([x, ...xs]) => def(x) ? 1 + length(xs) : 0

这将为数组中的每个项添加一个堆栈帧,而避免尾部递归的版本将替换单个堆栈帧。如果传入的数组足够大,它将抛错 “Maximum call stack size exceeded”。

示例:

const array = [1,2,3,4,5]
length(array) // 5

Reverse

返回一个反向数组。

const reverse = ([x, ...xs]) => def(x) ? [...reverse(xs), x] : []

示例:

const array = [1,2,3,4,5]
reverse(array) // [5,4,3,2,1]

Array.reverse() 可以使用,但是它有一个副作用,它会把原来的数组改掉。以下所示:

const array = [1,2,3,4,5]

const newArray = array.reverse() // [5,4,3,2,1]
array // [5,4,3,2,1]

// using the reverse method we just created
const array2 = [1,2,3,4,5]

const newArray2 = reverse(array2) // [5,4,3,2,1]
array2 // [1,2,3,4,5]

First

返回一个新数组,其中包含给定数组的前 n 项。

const first = ([x, ...xs], n = 1) => def(x) && n ? [x, ...first(xs, n - 1)] : []

示例:

const array = [1,2,3,4,5]
first(array, 3) // [1,2,3]

Last

返回一个新数组,其中包含给定数组的最后 n 项。

const last = (xs, n = 1) => reverse(first(reverse(xs), n))

示例:

const array = [1,2,3,4,5]
last(array, 3) // [3,4,5]

slice

返回在给定索引处插入值的新数组。

const slice = ([x, ...xs], i, y, curr = 0) => def(x)
  ? curr === i
    ? [y, x, ...slice(xs, i, y, curr + 1)]
    : [x, ...slice(xs, i, y, curr + 1)]
  : []

示例:

const array = [1,2,4,5]
slice(array, 2, 3) // [1,2,3,4,5]

isArray

返回是否是数组。允许我们以更函数化的方式编写 Array.isArray()。

const isArray = x => Array.isArray(x)

示例:

const array = [1,2,3,4,5]
isArray(array) // true

Flatten

将多维数组变成一维数组。

const flatten = ([x, ...xs]) => def(x)
    ? isArray(x) ? [...flatten(x), ...flatten(xs)] : [x, ...flatten(xs)]
    : []

示例:

const array1 = [1,2,3]
const array2 = [4,[5,[6]]]
flatten([array1, array2]) // [1,2,3,4,5,6]

Swap

交换两项的值返回一个新数组。

const swap = (a, i, j) => (
  map(a, (x,y) => {
    if(y === i) return a[j]
    if(y === j) return a[i]
    return x
  })
)

示例:

const array = [1,2,3,4,5]
swap(array, 0, 4) // [5,2,3,4,1]

Map

MDN: 数组中元素执行传入函数生成一个新的数组。

const map = ([x, ...xs], fn) => {
  if (undef(x)) return []
  return [fn(x), ...map(xs, fn)]
}

可以简化为

const map = ([x, ...xs], fn) => def(x) ? [fn(x), ...map(xs, fn)] : []

示例:

const double = x => x * 2
map([1,2,3], double) // [2,4,6]

Filter

MDN: 数组中元素执行传入函数通过验证执行生成一个新的数组。

const filter = ([x, ...xs], fn) => {
  if (undef(x)) return []
  if (fn(x)) {
    return [x, ...filter(xs, fn)]
  } else {
    return [...filter(xs, fn)]
  }
}

可以简化为

const filter = ([x, ...xs], fn) => def(x)
    ? fn(x)
        ? [x, ...filter(xs, fn)] : [...filter(xs, fn)]
    : []

示例:

const even = x => x % 2 === 0
const odd = x = !even(x)
const array = [1,2,3,4,5]

filter(array, even) // [2,4]
filter(array, odd) // [1,3,5]

Reject

与筛选器相反,返回一个不通过筛选器函数的数组。

const reject = ([x, ...xs], fn) => {
  if (undef(x)) return []
  if (!fn(x)) {
    return [x, ...reject(xs, fn)]
  } else {
    return [...reject(xs, fn)]
  }
}

示例:

const even = x => x % 2 === 0
const array = [1,2,3,4,5]

reject(array, even) // [1,3,5]

Partition

将一个数组分成两个数组。一个是其项通过筛选函数的,另一个是其项失败的。

const partition = (xs, fn) => [filter(xs, fn), reject(xs, fn)]

示例:

const even = x => x % 2 === 0
const array = [0,1,2,3,4,5]

partition(array, even) // [[0,2,4], [1,3,5]]

Reduce

MDN: 对累加器和数组中的每个元素(从左到右)应用的一个函数,将其结果汇总为单个返回值。

const reduce = ([x, ...xs], fn, memo, i) => {
  if (undef(x)) return memo
  return reduce(xs, fn, fn(memo, x, i), i + 1)
}

Which can be simplified as:

const reduce = ([x, ...xs], fn, memo, i = 0) => def(x)
    ? reduce(xs, fn, fn(memo, x, i), i + 1) : memo

示例:

const sum = (memo, x) => memo + x
reduce([1,2,3], sum, 0) // 6

const flatten = (memo, x) => memo.concat(x)
reduce([4,5,6], flatten, [1,2,3]) // [1,2,3,4,5,6]

ReduceRight

与reduce相似,但是从右到左应用函数。

const reduceRight = (xs, fn, memo) => reduce(reverse(xs), fn, memo)

示例:

const flatten = (memo, x) => memo.concat(x)

reduceRight([[0,1], [2,3], [4,5]], flatten, []) // [4, 5, 2, 3, 0, 1]

Partial

通过填充任意数量的参数部分应用函数。

const partial = (fn, ...args) => (...newArgs) => fn(...args, ...newArgs)

示例:

const add = (x,y) => x + y
const add5to = partial(add, 5)

add5to(10) // 15

SpreadArg

将接受数组的函数转换为接受多个参数的函数。这在部分应用时非常有用。

const spreadArg = (fn) => (...args) => fn(args)

示例:

const add = ([x, ...xs]) => def(x) ? parseInt(x + add(xs)) : []
add([1,2,3,4,5]) // 15

const spreadAdd = spreadArg(add)
spreadAdd(1,2,3,4,5) // 15

如果你只想定义一个函数,你可以把它写成

const add = spreadArg(([x, ...xs]) => def(x) ? parseInt(x + add(...xs)) : [])
add(1,2,3,4,5) // 15

在上面的示例中,需要记住传递给递归函数的展开数组是使用展开的参数实现的。

ReverseArgs

反转函数参数的顺序。

const reverseArgs = (fn) => (...args) => fn(...reverse(args))

示例:

const divide = (x,y) => x / y
divide(100,10) // 10

const reverseDivide = reverseArgs(divide)
reverseDivide(100,10) // 0.1

对于部分应用参数来说,反向参数可能很有用。有时您希望使用部分列表末尾的参数,而不是列表开头的参数。翻转参数将允许我们做到这一点。

const percentToDec = partial(reverseDivide, 100)

percentToDec(25) // 0.25

Pluck

提取数组中的属性值。与 map 函数结合使用时非常有用。

const pluck = (key, object) => object[key]

示例:

const product = {price: 15}
pluck('price', product) // 15

const getPrices = partial(pluck, 'price')
const products = [
  {price: 10},
  {price: 5},
  {price: 1}
]
map(products, getPrices) // [10,5,1]

Flow

每个函数使用之前的函数的返回值。

const flow = (...args) => init => reduce(args, (memo, fn) => fn(memo), init)

示例:


const getPrice = partial(pluck, 'price')
const discount = x => x * 0.9
const tax = x => x + (x * 0.075)
const getFinalPrice = flow(getPrice, discount, tax)

// looks like: tax(discount(getPrice(x)))
// -> get price
// -> apply discount
// -> apply taxes to discounted price

const products = [
  {price: 10},
  {price: 5},
  {price: 1}
]

map(products, getFinalPrice) // [9.675, 4.8375, 0.9675]

Compose

与 flow 相同,但是参数使用时的顺序相反。Compose 与函数的编写方式更自然更匹配。使用与 flow 函数定义相同的数据:

const compose = (...args) => flow(...reverse(args))

示例:

const getFinalPrice = compose(tax, discount, getPrice)

// looks like: tax(discount(getPrice(x)))

map(products, getFinalPrice) // [9.675, 4.8375, 0.9675]

Min

返回数组中最小的数字。如果提供的数组为空,则返回 Infinity。

const min = ([x, ...xs], result = Infinity) => def(x)
    ? x < result
        ? min(xs, x)
        : result
    : result

示例:

const array = [0,1,2,3,4,5]

min(array) // 0

Max

返回数组中最大的数字。如果数组是空的,返回 Infinity。

const max = ([x, ...xs], result = -Infinity) => def(x)
    ? x > result
        ? max(xs, x)
        : max(xs, result)
    : result

示例:

const array = [0,1,2,3,4,5]

max(array) // 5

Factorial

返回一个数的阶乘。使用一个累加器来允许替换堆栈帧以允许返回更大的阶乘。

const factorial = (x, acum = 1) => x ? factorial(x - 1, x * acum) : acum

示例:

factorial(5) // 120

Fibonacci

返回给定位置的斐波那契数。

const fib = x => x > 2 ? fib(x - 1) + fib(x - 2) : 1

示例:

fib(15) // 610

Quicksort

对数组从小到大排序。通过重新排序数组来实现的,使其包含两个子数组,一个值较小,另一个值较大。使用递归应用于每个子数组,直到没有数组剩下,然后使用 flatten 函数返回排序后的数组。

const quicksort = (xs) => length(xs)
  ? flatten([
    quicksort(filter(tail(xs), x => x <= head(xs))),
    head(xs),
    quicksort(filter(tail(xs), x => x > head(xs)))
  ])
  : []

这也可以通过 partition 函数来实现,但需要赋值给一个新的变量。

const quicksort = (array) => {
  if (!length(array)) return []
  const [less, more] = partition(tail(array), x => x < head(array))
  return flatten([quicksort(less), head(array), quicksort(more)])
}

示例:

const array = [8,2,6,4,1]

quicksort(array) // [1,2,4,6,8]

所有函数都是 Reduce

上面的许多功能都可以转换为 reduce 函数,这在大多数情况下也会提高性能。这也彰显了reduce 函数的灵活性。

const reduce = ([x, ...xs], f, memo, i = 0) => def(x)
    ? reduce(xs, f, f(memo, x, i), i + 1) : memo

const reverse = xs => reduce(xs, (memo, x) => [x, ...memo], [])

const length = xs => reduce(xs, (memo, x) => memo + 1, 0)

const map = (xs, fn) => reduce(xs, (memo, x) => [...memo, fn(x)], [])

const filter = (xs, fn) => reduce(xs, (memo, x) => fn(x)
    ? [...memo, x] : [...memo], [])

const reject = (xs, fn) => reduce(xs, (memo, x) => fn(x)
    ? [...memo] : [...memo, x], [])

const first = (xs, n) => reduce(xs, (memo, x, i) => i < n
    ? [...memo, x] : [...memo], [])

const last = (xs, n) => reduce(xs, (memo, x, i) => i >= (length(xs) - n)
    ? [...memo, x] : [...memo], [])

const merge = spreadArg(xs => reduce(xs, (memo, x) => [...memo, ...x], []))

const flatten = xs => reduce(xs, (memo, x) => x
    ? isArray(x) ? [...memo, ...flatten(x)] : [...memo, x] : [], [])

const add = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo + y, x))

const divide = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo / y, x))

const multiply = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo * y, x))

示例:

reverse([1,2,3]) // [3,2,1]
length([1,2,3]) // 3
map([1,2,3], double) // [2,3,4]
filter([1,2,3,4], even) // [2,4]
reject([1,2,3,4], even) // [1,3]
first([1,2,3,4], 3) // [1,2,3]
last([1,2,3,4], 2) // [3,4]
merge([1,2,3],[4,5,6]) // [1,2,3,4,5,6]
flatten([1,[2,3,[4,[5,[[6]]]]]]) // [1,2,3,4,5,6]
add(1,2,3,4,5) // 15
multiply(2,5,10) // 100
divide(100,2,5) // 10

圆满完成

我希望这篇文章能帮助我们深入了解 JavaScript 和 ES6 提供的一些设计模式。很多问题可以用迭代或者循环来解决,当然也可以使用递归函数。我希望本文还能够向您展示 reduce 函数的灵活性。

ARTS

2019-12-23 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Jewels and Stones

Solution

// 	1 ms	34.9 MB
// 暴力破解,理论上是最慢的,但是这里最快
class Solution {
    public int numJewelsInStones(String J, String S) {
        int stones = 0;
        for (char s: S.toCharArray()) {
            for (char j: J.toCharArray()) {
                if (s == j) {
                    stones++;
                    break;
                }
            }
        }

        return stones;
    }
}

Review

Tip

Share

ARTS

2019-12-16 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Best Time to Buy and Sell Stock II

Solution

class Solution {
    public int maxProfit(int[] prices) {
        int sum = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                sum += prices[i] - prices[i - 1];
            }
        }
        return sum;
    }
}

Review

Tip

Share

ARTS

2019-12-08 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Two City Scheduling

Solution

/**
 * @param {number[][]} costs
 * @return {number}
 */
var twoCitySchedCost = function(costs) {
    var newArray = [{
        arr: costs[0],
        diffValue: costs[0][0] - costs[0][1]
    }];
    for (var i = 1; i < costs.length; i++) {
        var item = costs[i];
        var diffValue = item[0] - item[1];
        for (var j = 0; j < newArray.length; j++) {
            if (newArray[j].diffValue > diffValue) {
                newArray.splice(j, 0, {
                    arr: item,
                    diffValue: diffValue
                });
                break;
            }
            if (j === newArray.length - 1) {
                newArray.push({
                    arr: item,
                    diffValue: diffValue
                });
                break;
            }
        }
    }
    var result = 0;
    for (i = 0; i < newArray.length; i++) {
        if (i < (newArray.length - 1) / 2) {
            result += newArray[i].arr[0];
        } else {
            result += newArray[i].arr[1];
        }
    }
    return result;
};

// var costs = [[10,20],[30,200],[400,50],[30,20]];
// var costs1 = [[10,20],[30,200],[400,50],[30,30]];
// var costs1 = [[10,20],[30,200],[400,50],[30,30], [30, 20]];
// console.log('twoCitySchedCost', twoCitySchedCost(costs))
// console.log('twoCitySchedCost 1', twoCitySchedCost(costs1))

Review

Tip

N/A (Not applicable) 不适用,不可用,不知道,不适用的,不限。

Share

别让自己“墙”了自己

文中讲了几个小故事,大体其实就是某人很有天赋,但是因为自身狭隘的想法,固步自封。写前端的认为自己只能写前端,写客户端的认为自己只能写客户端;还有写某种特定语言鄙视别的语言的,诸如此类的情况。还有待在小城市没有去大公司历练过的。

不要限制自己。做有价值的事情,扩大自己的视野,开放自己的内心,站在更高的维度上,精于计算自己的得失,勇于跳出传统的束缚。

ARTS

2019-11-30 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Letter Combinations of a Phone Number

Solution

/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function(digits) {
    digits = String(digits);
    if (digits.length === 1) {
        return digitMaps[digits].split('');
    }
    if (digits === '') {
        return [];
    }
    var firstDigit = digits[0];
    var lastDigits = digits.slice(1);
    var firstStr = digitMaps[firstDigit];
    var result = [];
    firstStr.split('').forEach(firstItem => {
        letterCombinations(lastDigits).forEach(lastItem => {
            result.push(firstItem + lastItem);
        });
    });
    return result;
};

const digitMaps = {
    2: 'abc',
    3: 'def',
    4: 'ghi',
    5: 'jkl',
    6: 'mno',
    7: 'pqrs',
    8: 'tuv',
    9: 'wxyz',
};


// letterCombinations(233);

Review

Tip

Share

ARTS

2019-11-24 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Jewels and Stones

Solution

/**
 * @param {string} J
 * @param {string} S
 * @return {number}
 */
/**
 * @param {string} J
 * @param {string} S
 * @return {number}
 */
// method 1
// var numJewelsInStones = function(J, S) {
//     var count = 0;
//     for (var i of S) {
//         for (var j of J) {
//             if (j === i) {
//                 count++;
//             }
//         }
//     }
//     return count;
// };

// method 2
var numJewelsInStones = function(J, S) {
    return S.split('').filter(x => J.indexOf(x) !== -1).length
};

// numJewelsInStones('aA', 'aAAbbbb')

Review

Tip

“当谈论面向对象的时候,我们到底在谈论什么”,收获什么是面向对象,面向对象狭义上是指包含四个特性,封装、抽象、继承、多态的代码语言;广义上是指面向支持类,有对象语法机制就能认为是面向对象语言。

Share

ARTS

2019-11-11 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description 1108. Defanging an IP Address

Solution

/**
 * @param {string} address
 * @return {string}
 */
// method 1
// var defangIPaddr = function(address) {
//     return address.replace(/\./g, "[.]");
// };

// method 2
var defangIPaddr = function(address) {
    var res = '';
    for (var i = 0; i < address.length; i++) {
        if (address[i] === '.') {
            res += '[.]'
        } else {
            res += address[i];
        }
    }
    return res;
};

Review

Tip

Share

今天学习了,收获

ARTS

2019-01-07 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Valid Parentheses

Solution

/**
 * https://leetcode.com/problems/valid-parentheses/
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    var stack = [];

    var arrs = s.split('');
    var firstItem = arrs.shift();
    if (s.length % 2 !== 0) {
        return false;
    }
    while (arrs.length) {
        var secondItem = arrs.shift();
        if (parenthesesObject[firstItem] === secondItem) {
            firstItem = stack.pop();
            continue;
        }
        stack.push(firstItem);
        firstItem = secondItem;
    }
    if (stack.length) {
        return false;
    }
    return true;
};

var parenthesesObject = {
    '{': '}',
    '(': ')',
    '[': ']',
};

var s = '()';
console.log(isValid(s));

var s = "[";
console.log(isValid(s));

Review

How to write a good README for your GitHub project? https://bulldogjob.com/news/449-how-to-write-a-good-readme-for-your-github-project

如何写一篇好的 Readme。Readme 是我们在开始一个新项目的第一个文件。 Readme 指在是其他人更容易理解我们的代码。 使用英语可以让更多受众了解这个项目。 Readme 用 markdown 格式,使用 markdown 语法。 一篇好的 readme 包含:标题、介绍、技术细节、如何启动几部分,也可包含目录、插图、示例等。 好的示例如下:

Tip

Js 原生的 onchange 就是 input 失去焦点的时候触发的。 oninput 会在 input 输入框值改变的时候实时触发。

Share

最近也在思考如何开发一个 js 库。并不单单只是考虑 js 的逻辑怎么书写,也包含了代码之外的考虑。

代码之外的考虑包含:

  • 脚手架
  • 构建
    • $ npm run build
  • 单元测试
    • $ npm run test
  • 发布
    • $ npm publish
  • 目录结构
    • lib/
    • src/
    • package.json
    • .gitignore
    • .npmrc
    • README.md
    • CHANGELOG.md // 更新日志
    • example // 示例代码。

ARTS

2019-01-01 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Roman to Integer

Solution

/**
 * https://leetcode.com/problems/roman-to-integer/
 * @param {string} s
 * @return {number}
 */
var romanToInt = function(s) {
    var arr = s;
    var result = 0;
    // arr = [ 'I1', 'I2', 'I3' ];
    // console.log('arr', arr);

    // arr.reduce(function(x, y) {
    //     // console.log('x, y', x, y)
    //     if (romanValue[x] >= romanValue[y]) {
    //         result += (romanValue[x]);
    //     } else {
    //         result -= (romanValue[x]);
    //     }
    //     return y;
    // });

    // for 循环比 reduce 应该要快一些。
    for(var arrIndex = 1; arrIndex < arr.length; arrIndex++) {
        // console.log('arr[arrIndex - 1]', arr[arrIndex - 1], arr[arrIndex]);
        var last = romanValue[arr[arrIndex - 1]];
        // console.log('...last', last, romanValue[arr[arrIndex]]);
        if (last >= romanValue[arr[arrIndex]]) {
            result += last;
        } else {
            result -= last;
        }
    }

    result += romanValue[arr[arr.length - 1]];
    return result;
};

var romanValue = {
    'I': 1,
    'V': 5,
    'X': 10,
    'L': 50,
    'C': 100,
    'D': 500,
    'M': 1000,
};

// console.log('romanToInt(\'IV\')', romanToInt('IV'), 4);


console.log('romanToInt(\'I\')', romanToInt('I'), 1);
console.log('romanToInt(\'II\')', romanToInt('II'), 2);
console.log('romanToInt(\'III\')', romanToInt('III'), 3);
console.log('romanToInt(\'IV\')', romanToInt('IV'), 4);
console.log('romanToInt(\'V\')', romanToInt('V'), 5);
console.log('romanToInt(\'VI\')', romanToInt('VI'), 6);
console.log('romanToInt(\'VII\')', romanToInt('VII'), 7);
console.log('romanToInt(\'VIII\')', romanToInt('VIII'), 8);
console.log('romanToInt(\'IX\')', romanToInt('IX'), 9);
console.log('romanToInt(\'XIX\')', romanToInt('XIX'), 19);
console.log('romanToInt(\'XL\')', romanToInt('XL'), 40);
console.log('romanToInt(\'CXLIV\')', romanToInt('CXLIV'), 144);
console.log('romanToInt(\'CD\')', romanToInt('CD'), 400);

console.log('romanToInt(\'LVIII\')', romanToInt('LVIII'), 58);
console.log('romanToInt(\'MCMXCIV\')', romanToInt('MCMXCIV'), 1994);

console.log('romanToInt(\'XC\')', romanToInt('XC'), 90);
console.log('romanToInt(\'XCI\')', romanToInt('XCI'), 91);
console.log('romanToInt(\'XCIX\')', romanToInt('XCIX'), 99);
console.log('romanToInt(\'CM\')', romanToInt('CM'), 900);
console.log('romanToInt(\'CMXCIX\')', romanToInt('CMXCIX'), 999);
console.log('romanToInt(\'MCMXCIX\')', romanToInt('MCMXCIX'), 1999);
console.log('romanToInt(\'MM\')', romanToInt('MM'), 2000);

Review

Things I Don’t Know as of 2018 https://overreacted.io/things-i-dont-know-as-of-2018/?from=timeline&isappinstalled=0

该篇文章是作者阐述自己至 2018 年还未了解的相关技术。

作者表示很多你崇拜的大神也会有你会但是大神不会的技术。

作者承认自己也会有知识缺陷但是也有宝贵的需要多年才能达到的知识。

Tip

99%的程序都没有考虑的网络异常 https://mp.weixin.qq.com/s/S2Eu9coPeikkfIbc3KeusA 这篇文章我理解的就是处理一些平时常见但前端不怎么处理掉错误。用 try catch 解决。

  1. 返回接口数据报错。
  2. 服务不稳定导致的接口报错。
  3. 网络不稳定导致的接口报错。

Share

ARTS

2018-12-23 00:00:00 +0000

  • A (Algotithm) 至少做一个leetcode的算法题
  • R (Review) 阅读并点评一篇英文的技术文章
  • T (Tip) 学习一个技术技巧
  • S (Share) 分享一篇有观点和思考的技术文章

每周一次,坚持一年

Algorithm

Description Integer to Roman

Solution

/**
 * https://leetcode.com/problems/integer-to-roman/
 * @param {number} num
 * @return {string}
 */
var intToRoman = function(num) {
    if (num >= 4000) {
        new Error('无法处理');
        return null;
    }

    var result = '';
    var count = 0;
    var currentIndex = currentRomanValue.length - 1;
    var currentSymbol = null;
    var lastSymbol = null;
    // var nextSymbol = '';
    var minuend;

    while(num) {
        currentSymbol = currentRomanValue[currentIndex];
        if (currentIndex + 1 < currentRomanValue.length) {
            lastSymbol = currentRomanValue[currentIndex + 1];
        } else {
            // 处理 lastSymbol 为 1000 的时候
            lastSymbol = currentSymbol;
        }

        if (currentIndex >= 2) {
            minuend = currentRomanValue[currentIndex - 1];
            if (!minuend.isMinuend) {
                minuend = currentRomanValue[currentIndex - 2];
            }
        }
        if (currentSymbol.value === minuend.value) {
            // 当前的不能为被减数
            lastSymbol = null;
        }
        count = Math.floor(num / currentSymbol.value);
        num = num % currentSymbol.value;

        // console.log(
        //     'count', count, 'num', num,
        //     'lastSymbol', lastSymbol && lastSymbol.value,
        //     'currentSymbol', currentSymbol.symbol,
        //     'minuend', minuend.value,
        // );

        if (count >= 1) {
            result += repeat(count, currentSymbol.symbol);
        }
        if (lastSymbol && (currentSymbol.value - minuend.value <= num)) {
            result += minuend.symbol + currentSymbol.symbol;
            num = num - (currentSymbol.value - minuend.value);
        }

        if (num < 0) {
            break;
        }
        currentIndex--;
    }
    return result;
};

var repeat = function( num, str ) {
    return new Array(num + 1).join(str);
};

var currentRomanValue = [
    { symbol: 'I', value: 1, isMinuend: true },
    { symbol: 'V', value: 5, isMinuend: false },
    { symbol: 'X', value: 10, isMinuend: true },
    { symbol: 'L', value: 50, isMinuend: false },
    { symbol: 'C', value: 100, isMinuend: true },
    { symbol: 'D', value: 500, isMinuend: false },
    { symbol: 'M', value: 1000, isMinuend: true },
];

// 方法二
var intToRoman = function(num) {
    var M = ['', 'M', 'MM', 'MMM'];
    var C = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'];
    var X = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'];
    var I = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
    return M[Math.floor(num/1000)] + C[Math.floor((num%1000)/100)] + X[Math.floor((num%100)/10)] + I[num%10];
};

// 方法三
var intToRoman = function(num) {
	if (num >= 1000) {
		return "M" + intToRoman(num - 1000)
	}

	if (num >= 900) {
		return "CM" + intToRoman(num - 900)
	}

	if (num >= 500) {
		return "D" + intToRoman(num - 500)
	}

	if (num >= 400) {
		return "CD" + intToRoman(num - 400)
	}

	if (num >= 100) {
		return "C" + intToRoman(num - 100)
	}

	if (num >= 90) {
		return "XC" + intToRoman(num - 90)
	}

	if (num >= 50) {
		return "L" + intToRoman(num - 50)
	}

	if (num >= 40) {
		return "XL" + intToRoman(num - 40)
	}

	if (num >= 10) {
		return "X" + intToRoman(num - 10)
	}

	if (num >= 9) {
		return "IX" + intToRoman(num - 9)
	}

	if (num >= 5) {
		return "V" + intToRoman(num - 5)
	}

	if (num >= 4) {
		return "IV" + intToRoman(num - 4)
	}

	if (num >= 1) {
		return "I" + intToRoman(num - 1)
	}
	return '';
};

// Symbol       Value
// I             1
// V             5
// X             10
// L             50
// C             100
// D             500
// M             1000

console.log('intToRoman(1)', intToRoman(1), 'I');
console.log('intToRoman(2)', intToRoman(2), 'II');
console.log('intToRoman(3)', intToRoman(3), 'III');
console.log('intToRoman(4)', intToRoman(4), 'IV');
console.log('intToRoman(5)', intToRoman(5), 'V');
console.log('intToRoman(6)', intToRoman(6), 'VI');
console.log('intToRoman(7)', intToRoman(7), 'VII');
console.log('intToRoman(8)', intToRoman(8), 'VIII');
console.log('intToRoman(9)', intToRoman(9), 'IX');
console.log('intToRoman(19)', intToRoman(19), 'XIX');
console.log('intToRoman(40)', intToRoman(40), 'XL');
console.log('intToRoman(144)', intToRoman(144), 'CXLIV');
console.log('intToRoman(400)', intToRoman(400), 'CD');

console.log('intToRoman(58)', intToRoman(58), 'LVIII');
console.log('intToRoman(1994)', intToRoman(1994), 'MCMXCIV');

console.log('intToRoman(90)', intToRoman(90), 'XC');
console.log('intToRoman(91)', intToRoman(91), 'XCI');
console.log('intToRoman(99)', intToRoman(99), 'XCIX');
console.log('intToRoman(900)', intToRoman(900), 'CM');
console.log('intToRoman(999)', intToRoman(999), 'CMXCIX');
console.log('intToRoman(1999)', intToRoman(1999), 'MCMXCIX');
console.log('intToRoman(2000)', intToRoman(2000), 'MM');

Review

Create and Test Decorators in JavaScript https://netbasal.com/create-and-test-decorators-in-javascript-85e8d5cf879c

该文章指导我们如何使用 Decorator 去修饰类、方法和属性。并且如何去测试 Decorator。测试 Decorator 与测试普通函数几无二致。

Tip

tabindex:元素使用Tab键进行focus时候的顺序值

div 要加这个属性,才能用 js 去 focus 该 div。 https://www.zhangxinxu.com/wordpress/2017/05/html-tabindex/

Share

未完待续。。。