数组快速前缀和
const accumulate = (...nums) =>
nums.reduce((acc, n) => [...acc, n + (acc.slice(-1)[0] || 0)], [])
reduce()
reduce函数,这个函数作用于一个数组,最大的特点就是数组中的每一个元素都会被作为接收的函数的参数调用一次:
reduce((accumulate, current) => {...}, initial)
接收两个参数:
- 第一个是一个函数,它的第一个参数accumulate是累加器,代表当前处理值,第二个参数是数组当前的处理值;
- 第二个是accumulate的初始值,默认为数组的第一个元素。
使用到reduce()函数的场景
reduce()专用于Array,功能强大,有时候会发挥巧妙的作用,这就要求我们对reduce()有足够的掌握能力了。但是一般说来,如果对于某一个数组,我们需要遍历其每一个元素进行处理,并且还需要先前的处理结果时,此时可能适合使用reduce()。另外,有些问题可能并不是以数组形式出现,需要首先将其转化为数组(Object.keys()等方法)。
例题
Pete likes to bake some cakes. He has some recipes and ingredients. Unfortunately he is not good in maths. Can you help him to find out, how many cakes he could bake considering his recipes?
Write a function
cakes()
, which takes the recipe (object) and the available ingredients (also an object) and returns the maximum number of cakes Pete can bake (integer). For simplicity there are no units for the amounts (e.g. 1 lb of flour or 200 g of sugar are simply 1 or 200). Ingredients that are not present in the objects, can be considered as 0.Examples:
// must return 2 cakes({flour: 500, sugar: 200, eggs: 1}, {flour: 1200, sugar: 1200, eggs: 5, milk: 200}); // must return 0 cakes({apples: 3, flour: 300, sugar: 150, milk: 100, oil: 100}, {sugar: 500, flour: 2000, milk: 2000});
分享一个很简洁的代码思路:
首先肯定要遍历配料对象的每一个属性,然后在原料对象中查询之,两者作整数除法,取出最小值即为答案了。这其实符合了reduce()函数的使用场景:每一个元素都需要被处理;还需要比较先前的处理结果(取最小值)。
于是,对于reduce的接收的回调函数的累加器参数,我们设置为min{当前原料与配料量之比,之前的值}(也即回调函数的返回值),第二个参数设为Infinity,表示无穷大。
function cakes(recipe, available) {
return Object.keys(recipe).reduce((pre, ingre) => {
return Math.min(Math.floor(available[ingre] / recipe[ingre]), pre)
}, Infinity)
}
slice()
Array.slice(),用于JavaScript的数组切片操作。此处,acc.slice(-1)表示取出acc的最后一个元素(返回数组)。
解释
- 审视这段代码,[]是初始值,也就是第一次调用回调函数时,acc累加器是[],参数n是首元素,箭头函数返回值是一个数组,acc是数组类型,经扩展运算符,与当前值和累加器的最后一个元素相加组合成新数组。
- 这样一来,数组就是前缀和形式。
快速修改ISO格式日期
const addDaysToDate = (date, n) => {
const d = new Date(date);
d.setDate(d.getDate() + n);
return d.toISOString().split('T')[0];
};
toISOString()
Date.toISOString(),可以将一个Date类型的数据转化为ISO格式的字符串。ISO格式,形如*2022-10-15T00:00:00.000Z*
解释
调用Date类型数据d的setDate()方法,设置其天数,在当前天数(getDate())基础上加n,重新转为ISO类型字符串。
数组快速分割
const aperture = (n, arr) =>
// map((currentValue, currentIndex) => {})
n > arr.length
? []
: arr.slice(n - 1).map((v, i) => arr.slice(i, i + n));
map()
Array.map((currentValue, currentIndex) => {…})
这个函数的特点也是会对数组中的每一个元素都使用一次回调函数参数。
解释
对于最外层的aperture函数,参数n是步长,arr是原数组;先从原数组的第n个元素开始取,对取出的子数组的每一个元素都使用map()函数(共arr.length-n+1次),每一次调用map函数都会得到一个新的子数组。
aperture(2, [1, 2, 3, 4]); // [[1, 2], [2, 3], [3, 4]]
aperture(3, [1, 2, 3, 4]); // [[1, 2, 3], [2, 3, 4]]
aperture(5, [1, 2, 3, 4]); // []
算术迭代
const arithmeticProgression = (n, lim) =>
Array.from({
length: Math.ceil(lim / n)
}, (_, i) => (i + 1) * n)
let re = arithmeticProgression(5, 25)
console.log(re);
// [ 5, 10, 15, 20, 25 ]
from()
Array.from(obj, mapFn(currentValue, currentIndex) => {…}, thisArg)
其中,obj是一个可迭代对象(具有length属性),它将被转化为一个数组;而mapFn相当于map函数,它会在obj转化时对其进一步转化(联系map函数特点,每一个元素都会被处理一次)。
解释
此处的from函数,第一个参数是一个对象,length属性用于迭代生成数组,第二个参数是mapFn,它接收了两个参数,第一个是占位符,第二个是下标,表示数组中的每一个元素的得到方式:下标加1再乘以n。
练习
The goal of this exercise is to convert a string to a new string where each character in the new string is
"("
if that character appears only once in the original string, or")"
if that character appears more than once in the original string. Ignore capitalization when determining if a character is a duplicate.Examples
"din" => "(((" "recede" => "()()()" "Success" => ")())())" "(( @" => "))(("
参考代码如下,思路是将字符串转化为数组,同时利用from函数的第二个参数——处理函数,对结果进行二次处理,最后重新拼接成满足条件的字符串。
function duplicateEncode(word){
word = word.toUpperCase()
return Array.from(word, (v, i) => word.indexOf(v)===word.lastIndexOf(v) ? '(' : ')').join('')
}
Write a function,
persistence
, that takes in a positive parameternum
and returns its multiplicative persistence, which is the number of times you must multiply the digits innum
until you reach a single digit.For example (Input —> Output):
39 --> 3 (because 3*9 = 27, 2*7 = 14, 1*4 = 4 and 4 has only one digit) 999 --> 4 (because 9*9*9 = 729, 7*2*9 = 126, 1*2*6 = 12, and finally 1*2 = 2) 4 --> 0 (because 4 is already a one-digit number)
function persistence(num, time=0) {
return num < 10 ? time : persistence((Array.from(num.toString()).reduce((acc, i) => acc * i, 1)), ++time)
}
多维数组求最大值
const ary = (fn, n) => (...args) => fn(...args.slice(0, n));
const firstTwoMax = ary(Math.max, 2);
[
[2, 6, 'a'],
[6, 4, 8],
[10]
].map(x => firstTwoMax(...x)); // [6, 6, 10]
解释
此处的ary函数的写法有些奇怪。其实,这是箭头函数简略写法的嵌套,有点类似闭包——也就是说,ary的返回结果是一个函数对象,这个函数对象接收一个数组,并取其子数组作为参数调用fn函数。
firstTwoMax()得到一个求前两个元素较大值的函数对象,经map函数作用于每一个子数组,得到新值。
对象是否包含指定键
const assertValidKeys = (obj, keys) =>
Object.keys(obj).every(key => keys.includes(key));
Object.keys()
这个方法是Object对象上的,它接收一个object实例对象作为参数,返回其所有的键(数组形式)。
Array.every(callback)
对数组的每一个元素检查,是否满足回调函数 callback 中提供的条件。
数组元素分组
const bifurcate = (arr, filter) =>
arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []])
这里关键就是reduce函数的使用:对于arr中的每一个元素,都需要经过处理,累加器acc是一个二维数组,通过判断在filter数组中的值来确定加入acc中的哪一个子数组。
绑定对象的键
const bindKey = (context, fn, ...boundArgs) => (...args) =>
context[fn].apply(context, [...boundArgs, ...args]);
const freddy = {
user: 'fred',
greet: function(greeting, punctuation) {
return greeting + ' ' + this.user + punctuation;
}
};
const freddyBound = bindKey(freddy, 'greet');
console.log(freddyBound('hi', '!')); // 'hi fred!'
bind()
function.bind(thisArg[, arg1[, arg2[, ...]]])
这个bind()函数将会创建一个新的函数,同时指定该函数的this参数thisArg,以及传递给function的参数arg1等。
解释
此处的bindKey()将返回一个函数对象:
// freddyBound变量的内容:
function(...args) {
return context[fn].apply(context, [...boundArgs, ...args])
}
该函数中,context为freddy对象,fn为键名greet,freddyBound()再接收的参数(即…args),会被作为apply方法的第二个参数:
freddyBound('hi', '!')
,args即为’hi’, ‘!’,它们将作为greeting、punction出现在freddy.greet的参数中。
bottomVisible判定页面底部是否可见
const bottomVisible = () =>
document.documentElement.clientHeight + window.scrollY >=
(document.documentElement.scrollHeight ||
document.documentElement.clientHeight);
三大易混属性
- element.clientHeight
- window.scrollX / window.scrollY
clientHeight/clientWidth
这是HTMLElement的一个只读属性。比如clientHeight,它是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
HTMLElement'clientHeight = CSS'height + CSS'padding - scroll'height
使用语法一般就是
var h = element.clientHeight;
其中,element是一个HTML元素,它会返回一个整数(单位px像素)。
offsetHeight/offsetWidth
这也是HTMLElement的一个只读属性。比如元素的offsetHeight是一种元素CSS高度的衡量标准,包括元素的边框、内边距和元素的水平滚动条。
scrollX/scrollY
返回文档/页面水平方向滚动的像素值。
使用语法一般就是
var x = window.scrollX;
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.demo {
background-color: rgb(207, 114, 114);
/* 宽度和高度都属于盒子的内容区,offset和client都包含 */
width: 400px;
height: 600px;
/* border属于边框,offset包含,client不包含 */
border: 10px #bfc solid;
/* margin属于外边距,offset、client都不包含 */
margin: 30px;
/* padding属于内边距,offset、client都包含 */
padding: 15px;
/* 滚动条的宽度,offset包含,client不包含 */
overflow: scroll;
}
</style>
</head>
<body>
<div class="demo">
中和癸卯春三月,洛阳城外花如雪。
东西南北路人绝,绿杨悄悄香尘灭。
路旁忽见如花人,独向绿杨阴下歇。
凤侧鸾欹鬓脚斜,红攒黛敛眉心折。
借问女郎何处来?含颦欲语声先咽。
回头敛袂谢行人,丧乱漂沦何堪说!
三年陷贼留秦地,依稀记得秦中事。
君能为妾解金鞍,妾亦与君停玉趾。
前年庚子腊月五,正闭金笼教鹦鹉。
斜开鸾镜懒梳头,闲凭雕栏慵不语。
忽看门外起红尘,已见街中擂金鼓。
居人走出半仓惶,朝士归来尚疑误。
是时西面官军入,拟向潼关为警急。
皆言博野自相持,尽道贼军来未及。
须臾主父乘奔至,下马入门痴似醉。
适逢紫盖去蒙尘,已见白旗来匝地。
扶羸携幼竞相呼,上屋缘墙不知次。
南邻走入北邻藏,东邻走向西邻避。
北邻诸妇咸相凑,户外崩腾如走兽。
轰轰昆昆乾坤动,万马雷声从地涌。
火迸金星上九天,十二官街烟烘烔。
日轮西下寒光白,上帝无言空脉脉。
阴云晕气若重围,宦者流星如血色。
紫气潜随帝座移,妖光暗射台星拆。
...
</div>
</body>
<script>
const bottomVisible = () =>
document.documentElement.clientHeight + window.scrollY >=
(document.documentElement.scrollHeight ||
document.documentElement.clientHeight);
let el = document.querySelector('.demo')
console.log('clientHeight', el.clientHeight);
console.log('clientWidth', el.clientWidth);
console.log('offsetHeight', el.offsetHeight);
console.log('offsetWidth', el.offsetWidth);
console.log(window.scrollY);
</script>
</html>
<!--以下是浏览器控制台输出结果-->
clientHeight 613
09-bottomVisible.html:206 clientWidth 414
09-bottomVisible.html:207 offsetHeight 650
09-bottomVisible.html:208 offsetWidth 450
09-bottomVisible.html:209 0
下面是获取各种浏览器可见窗口大小的函数:
function getInfo()
{
var s = "";
s += " 网页可见区域宽:"+ document.body.clientWidth;
s += " 网页可见区域高:"+ document.body.clientHeight;
s += " 网页可见区域宽:"+ document.body.offsetWidth + " (包括边线和滚动条的宽)";
s += " 网页可见区域高:"+ document.body.offsetHeight + " (包括边线的宽)";
s += " 网页正文全文宽:"+ document.body.scrollWidth;
s += " 网页正文全文高:"+ document.body.scrollHeight;
s += " 网页被卷去的高(ff):"+ document.body.scrollTop;
s += " 网页被卷去的高(ie):"+ document.documentElement.scrollTop;
s += " 网页被卷去的左:"+ document.body.scrollLeft;
s += " 网页正文部分上:"+ window.screenTop;
s += " 网页正文部分左:"+ window.screenLeft;
s += " 屏幕分辨率的高:"+ window.screen.height;
s += " 屏幕分辨率的宽:"+ window.screen.width;
s += " 屏幕可用工作区高度:"+ window.screen.availHeight;
s += " 屏幕可用工作区宽度:"+ window.screen.availWidth;
s += " 你的屏幕设置是 "+ window.screen.colorDepth +" 位彩色";
s += " 你的屏幕设置 "+ window.screen.deviceXDPI +" 像素/英寸";
}
数组元素判定相同
Given two arrays
a
andb
write a functioncomp(a, b)
(orcompSame(a, b)
) that checks whether the two arrays have the “same” elements, with the same multiplicities (the multiplicity of a member is the number of times it appears). “Same” means, here, that the elements inb
are the elements ina
squared, regardless of the order.Examples
Valid arrays
a = [121, 144, 19, 161, 19, 144, 19, 11] b = [121, 14641, 20736, 361, 25921, 361, 20736, 361]
comp(a, b)
returns true because inb
121 is the square of 11, 14641 is the square of 121, 20736 the square of 144, 361 the square of 19, 25921 the square of 161, and so on. It gets obvious if we writeb
‘s elements in terms of squares:a = [121, 144, 19, 161, 19, 144, 19, 11] b = [11*11, 121*121, 144*144, 19*19, 161*161, 19*19, 144*144, 19*19]
function comp(array1, array2) {
return array1 != null && array2 != null && array1.sort((a, b) => b - a).map(v => v * v).join() == array2.sort((a, b) => b - a).join()
}
解释
题目要求的是当a数组中元素的平方都在b数组中时(注意a、b元素个数相同),称a与b“相同”,正常的思路是对a数组使用Array.every()
,every()函数中接收参数为对a中每一个元素,它的平方是否在b中出现:b.indexOf(ele*ele) !== -1
。
而新颖的思路是,既然两个数组元素数量一致,那么如果a、b相同,将a中元素均平方,得到的新数组和b就会完全一致,只是顺序可能有差异。既然顺序不同,我们可以sort排序将它们顺序转为一致,然后使用Array.join()
方法转为字符串,进而直接进行==
判定。
单词首字母大写
Complete the method/function so that it converts dash/underscore delimited words into camel casing. The first word within the output should be capitalized only if the original word was capitalized (known as Upper Camel Case, also often referred to as Pascal case).
Examples
"the-stealth-warrior"` gets converted to `"theStealthWarrior"` `"The_Stealth_Warrior"` gets converted to `"TheStealthWarrior"
resolution
function toCamelCase(str) {
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase())
}
String.replace()方法
str.replace(substr | regexp, newSubStr | function)
- subStr,是str的子串内容
- regexp,是正则表达式,对str按照该正则进行匹配
- newSubStr,是要替换的新内容
- function,
判断幂的个位数字
给定两个参数str1,str2,要求出str1^str2的个位数字。
var lastDigit = function(str1, str2) {
return +str2 === 0 ? 1 : (+str1.slice(-1))**(+str2.slice(-2) % 4 + 4) % 10
}
思路
在敲代码编程之前,我们先动笔算算0~9这十个数字的幂的情况,期望从中找出幂的个位数的规律。
经过简单的计算之后,我们会发现其实0~9每一个数字的幂以4为周期(事实上,0、1、5、6以1为周期,4、8以2为周期),所以取最小公倍数统一处理。此处需要指出:string.slice()方法用于截取字符串,slice(-1)截取最后一个字符,slice(-2)截取最后两个字符,以此类推。
大数相加问题
传统的a plus b问题,要求它们的和。其中,a、b以字符串给出,返回的结果亦为字符串类型。
The code as below is like an art.
function add(a, b) {
let c = 0, res = '' // res存储运算结果,以字符串形式;c存储每一位的进位
a = a.split('')
b = b.split('') // 字符串分割,转为数组
while(a.length || b.length || c) {
c += ~~a.pop() + ~~b.pop() // 二次取反,专门用于处理数组长度为0时取值undefined,而~~undefined值为0
res = c % 10 + res // 结果拼接
c = c > 9 // 考虑进位
}
return res
}
单词首字母大写
const capitalize = ([first, …rest], lowerRest = false) => first.toUpperCase() + (lowerRest ? rest.join(‘’).toLowerCase() : rest.join(‘’))
解构赋值
解构赋值是ES6新推出的语法,有着强大的功能。
在上面的代码示例中,我们调用capitalize('helloWorld', true);
‘helloWorld’会传入参数作为第一个参数,而函数第一个参数是以解构赋值形式接收的:
- first保存的是字符串的首字符,即h;
- …rest保存的是从第二个字符开始的全部子字符串的数组形式,即[‘e’, ‘l’, ‘l’, ‘o’, ‘W’, ‘o’, ‘r’, ‘l’, ‘d’]
解释
运用解构赋值、toUppercase()、toLowerCase()等,可以很方便地实现这种功能。