translation
ES6 的函数式 js —– 递归模式
原文地址:https://medium.com/dailyjs/functional-js-with-es6-recursive-patterns-b7d0813ef9e3
作者:Casey Morris
这是处理什么问题? 函数式编程正在兴起,这是一个令人非常兴奋的话题。它使我可以编写简洁的声明性代码,易于测试和推理。 什么是函数式编程? 我将把答案回答给对此主题有更多了解的人,Eric Elliot 说:
函数式编程(通常缩写为FP)是通过组合纯函数、避免共享状态、可变数据和副作用来构建软件的过程。函数式编程是声明式的,而不是命令式的,应用的状态通过纯函数传递。与面向对象编程相反,在面向对象编程中,应用的状态通常与对象中的方法共享和同步。
ES6带来了许多特性,让我们可以轻松编写纯函数,而 rest/spread 是最强大的功能之一。使用 rest 参数,我们可以使用递归进行无循环的循环。 在本文中,我们将重写许多支持函数模式的常用 JavaScript 方法/函数。
前言
以下函数用于演示和学习。下面的许多函数都是尾部递归的,应该进一步优化。优化尾部递归不是本文的主题。ES6带来了尾部调用优化,但必须与“严格模式”一起使用。
Head
返回数组中的第一项。当您需要将第一项与数组的其余项分隔开时,非常有用。为此,我们使用了解构赋值。
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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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 解决。
- 返回接口数据报错。
- 服务不稳定导致的接口报错。
- 网络不稳定导致的接口报错。
Share
…
ARTS
- 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
未完待续。。。