JavaScript脚本算法编程实战课程
目录
1、判断电话号码算法挑战
如果传入字符串是一个有效的美国电话号码,则返回 true
.用户可以在表单中填入一个任意有效美国电话号码. 下面是一些有效号码的例子(还有下面测试时用到的一些变体写法):
555-555-5555
(555)555-5555
(555) 555-5555
555 555 5555
5555555555
1 555 555 5555
在本节中你会看见如 800-692-7753
or 8oo-six427676;laskdjf
这样的字符串. 你的任务就是验证前面给出的字符串是否是有效的美国电话号码. 区号是必须有的. 如果字符串中给出了国家代码, 你必须验证其是 1
.如果号码有效就返回 true
; 否则返回 false
.
function telephoneCheck(str) {
// Good luck!
var regExp1 = /^(1\s?)?\(\d{3}\)\s?\d{3}(\s|-)?\d{4}$/;
var regExp2 = /^(1\s)?\d{3}(\s|-)?\d{3}(\s|-)?\d{4}$/;
return (regExp1.test(str) || regExp2.test(str));
}
telephoneCheck("555-555-5555");
运行结果:true
2、集合交集算法挑战
创建一个函数,接受两个或多个数组,返回所给数组的 对等差分(symmetric difference) (△
or ⊕
)数组。
给出两个集合 (如集合 A = {1, 2, 3}
和集合 B = {2, 3, 4}
), 而数学术语 "对等差分" 的集合就是指由所有只在两个集合其中之一的元素组成的集合(A △ B = C = {1, 4}
). 对于传入的额外集合 (如 D = {2, 3}
), 你应该安装前面原则求前两个集合的结果与新集合的对等差分集合 (C △ D = {1, 4} △ {2, 3} = {1, 2, 3, 4}
)。
function sym(args) {
var arg = arguments;
var newArgs = [];
for(var i = 0; i < arguments.length; i++){
var newArg = [];
for(var j = 0; j < arguments[i].length; j++){
if(arguments[i].indexOf(arguments[i][j]) == j){
newArg.push(arguments[i][j]);
}
}
newArgs.push(newArg);
}
var returnArg = [];
for(var k in newArgs){
for(var m in newArgs[k]){
if(returnArg.indexOf(newArgs[k][m]) == -1){
returnArg.push(newArgs[k][m]);
}else{
returnArg.splice(returnArg.indexOf(newArgs[k][m]),1);
}
}
}
return returnArg;
}
sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]);
运行结果:[1,4,5]
3、 收银系统算法挑战
设计一个收银程序 checkCashRegister()
,其把购买价格(price
)作为第一个参数 , 付款金额 (cash
)作为第二个参数, 和收银机中零钱 (cid
) 作为第三个参数。
cid
是一个二维数组,存着当前可用的找零。
当收银机中的钱不够找零时返回字符串 "Insufficient Funds"
. 如果正好则返回字符串 "Closed"。
否者, 返回应找回的零钱列表,且由大到小存在二维数组中
function checkCashRegister(price, cash, cid) {
var change = cash - price,
// Here is your change, ma'am.
totalCash = 0,//收银机中的现金总数
smallChange = 0,//某单位面额的现金总和
rArr = [];//返回的找零数组
//定义一个面额由大到小的单位面额的json格式的对象
var cidDic = {
"ONE HUNDRED":100.00,
"TWENTY":20.00,
"TEN":10.00,
"FIVE":5.00,
"ONE":1.00,
"QUARTER":0.25,
"DIME":0.10,
"NICKEL":0.05,
"PENNY":0.01
};
// 计算收银机中零钱总数
cid.forEach(function(obj){
totalCash += obj[1];
});
if(totalCash < change){//零钱总数不够找零
return "Insufficient Funds";
}else if(totalCash == change){//恰好足够找零
return "Closed";
}else{
for(var key in cidDic){
smallChange = 0;
while(cidDic[key] <= change && getValue(cid,key) > 0){
smallChange += cidDic[key];
change -= cidDic[key];
change = parseInt(Math.round(change*100))*0.01;
cid = changeCashIngDrawer(cid, key, cidDic[key]);
}
if(smallChange > 0.00){
rArr.push([key,smallChange]);
}
}
}
if(change > 0.00){//收银机中的零钱总数比要找零的数额大,但是面额凑不对
return "Insufficient Funds";
}
return rArr;
}
// 根据key返回arr中的对应的元素的值
function getValue(arr, key){
for(var i in arr){
if(arr[i][0] === key){
return arr[i][1];
}
}
}
//修改收银机中的钱数
function changeCashIngDrawer(arr, key, val){
for(var i in arr){
if(arr[i][0] == key){
arr[i][1] =(arr[i][1]*100 - val*100)*0.01;
}
}
return arr;
}
// Example cash-in-drawer array:
// [["PENNY", 1.01],
// ["NICKEL", 2.05],
// ["DIME", 3.10],
// ["QUARTER", 4.25],
// ["ONE", 90.00],
// ["FIVE", 55.00],
// ["TEN", 20.00],
// ["TWENTY", 60.00],
// ["ONE HUNDRED", 100.00]]
checkCashRegister(19.50, 20.00, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.10], ["QUARTER", 4.25], ["ONE", 90.00], ["FIVE", 55.00], ["TEN", 20.00], ["TWENTY", 60.00], ["ONE HUNDRED", 100.00]]);
运行结果:[["QUARTER",0.5]]
以下是官方答案,然而,我并不觉得合理。原因是:1、如果抽屉里面的现金没有按由大到小顺序排列的时候,最后返回的结果就会报错;2、代码过于冗长。
function checkCashRegister(price, cash, cid) {
var change = cash - price,
totalCash = 0,
rArr = [],
isDone = false;
// Here is your change, ma'am.
cid.forEach(function (arr) {
totalCash += arr[1];
});
if (change > totalCash) {
return 'Insufficient Funds';
} else if (change == totalCash) {
return 'Closed';
} else {
for (var i = cid.length - 1; i >= 0; i--) {
if (cid[i][1] < change) {
if (cid[i][1] !== 0) {
rArr.push(cid[i]);
change -= cid[i][1];
}
if (cid[i][0] === 'PENNY') {
return 'Insufficient Funds';
}
} else {
var t = 0;
switch (cid[i][0]) {
case 'PENNY':
t = Math.round(change * 100) * 0.01;
rArr.push(['PENNY', t]);
change -= t;
if (change >= 0.01) {
return 'Insufficient Funds';
}
break;
case 'NICKEL':
t = parseInt(change / 0.05) * 0.05;
if (t > 0) {
rArr.push(['NICKEL', t]);
change -= t;
}
break;
case 'DIME':
t = parseInt(change / 0.1) * 0.1;
if (t > 0) {
rArr.push(['DIME', t]);
change -= t;
}
break;
case 'QUARTER':
t = parseInt(change / 0.25) * 0.25;
if (t > 0) {
rArr.push(['QUARTER', t]);
change -= t;
}
break;
case 'ONE':
t = parseInt(change / 1) * 1;
if (t > 0) {
rArr.push(['ONE', t]);
change -= t;
}
break;
case 'FIVE':
t = parseInt(change / 5) * 5;
if (t > 0) {
rArr.push(['FIVE', t]);
change -= t;
}
break;
case 'TEN':
t = parseInt(change / 10) * 10;
if (t > 0) {
rArr.push(['TEN', t]);
change -= t;
}
break;
case 'TWENTY':
t = parseInt(change / 20) * 20;
if (t > 0) {
rArr.push(['TWENTY', t]);
change -= t;
}
break;
case 'ONE HUNDRED':
t = parseInt(change / 100) * 100;
if (t > 0) {
rArr.push(['ONE HUNDRED', t]);
change -= t;
}
break;
}
if (change === 0) {
break;
}
}
}
return rArr;
}
}
// Example cash-in-drawer array:
// [["PENNY", 1.01],
// ["NICKEL", 2.05],
// ["DIME", 3.10],
// ["QUARTER", 4.25],
// ["ONE", 90.00],
// ["FIVE", 55.00],
// ["TEN", 20.00],
// ["TWENTY", 60.00],
// ["ONE HUNDRED", 100.00]]
checkCashRegister([19.50, 20.00], ["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.10], ["QUARTER", 4.25], ["ONE", 90.00], ["FIVE", 55.00], ["TEN", 20.00], ["TWENTY"]);
4、库存更新算法挑战
依照一个存着新进货物的二维数组,更新存着现有库存(在 arr1
中)的二维数组. 如果货物已存在则更新数量 . 如果没有对应货物则把其加入到数组中,更新最新的数量. 返回当前的库存数组,且按货物名称的字母顺序排列.
function updateInventory(arr1, arr2) {
// All inventory must be accounted for or you're fired!
var flag;
for(var i in arr2){
flag = false;
for(var j in arr1){
if (arr2[i][1] == arr1[j][1]) {
flag = true;
break;
}
}
if(flag){
for(var k in arr1){
if(arr1[k][1] == arr2[i][1]){
arr1[k][0] += arr2[i][0];
}
}
}else{
arr1.push(arr2[i]);
}
}
var temp = [];
for(var m in arr1){
temp.push(arr1[m][1]);
}
temp.sort();
for(var n in temp){
for(var t in arr1){
if(temp[n] == arr1[t][1])
temp.splice(n,1,arr1[t]);
}
}
arr1 = temp;
return arr1;
}
// Example inventory lists
var curInv = [
[21, "Bowling Ball"],
[2, "Dirty Sock"],
[1, "Hair Pin"],
[5, "Microphone"]
];
var newInv = [
[2, "Hair Pin"],
[3, "Half-Eaten Apple"],
[67, "Bowling Ball"],
[7, "Toothpaste"]
];
updateInventory(curInv, newInv);
运行结果:[[88,"Bowling Ball"],[2,"Dirty Sock"],[3,"Hair Pin"],[3,"Half-Eaten Apple"],[5,"Microphone"],[7,"Toothpaste"]]
5、排列组合去重算法挑战
把一个字符串中的字符重新排列生成新的字符串,返回新生成的字符串里没有连续重复字符的字符串个数.连续重复只以单个字符为准
例如, aab
应该返回 2 因为它总共有6中排列 (aab
, aab
, aba
, aba
, baa
, baa
), 但是只有两个 (aba
and aba
)没有连续重复的字符 (在本例中是 a
).
评论区5楼的代码是本题的一种方法,可以借鉴。
6、日期改写算法挑战
让日期区间更友好!
把常见的日期格式如:YYYY-MM-DD
转换成一种更易读的格式。
易读格式应该是用月份名称代替月份数字,用序数词代替数字来表示天 (1st
代替 1
).
记住不要显示那些可以被推测出来的信息: 如果一个日期区间里结束日期与开始日期相差小于一年,则结束日期就不用写年份了。月份开始和结束日期如果在同一个月,则结束日期月份就不用写了。
另外, 如果开始日期年份是当前年份,且结束日期与开始日期小于一年,则开始日期的年份也不用写。
例如:
makeFriendlyDates(["2016-07-01", "2016-07-04"])
应该返回 ["July 1st, 2016","4th"]
makeFriendlyDates(["2016-07-01", "2018-07-04"])
应该返回 ["July 1st, 2016", "July 4th, 2018"]
.
function makeFriendlyDates(arr) {
var startDate;
var endDate;
var times = new Date(arr[1]).getTime() - new Date(arr[0]).getTime();
// console.log(formatMonth(arr[0]));
//开始日期
if(new Date().getFullYear() == formatYear(arr[0]) && times > 0 && times < 365*24*60*60*1000){
startDate = formatMonth(arr[0]) + " " + formatDay(arr[0]);
}else{
startDate = formatMonth(arr[0]) + " " + formatDay(arr[0]) + ", " + formatYear(arr[0]);
}
//结束日期
if(formatMonth(arr[1]) == formatMonth(arr[0]) && formatYear(arr[0]) == formatYear(arr[1])){
endDate = "" + formatDay(arr[1]);
}else if((formatMonth(arr[1]) != formatMonth(arr[0]) || formatYear(arr[0]) != formatYear(arr[1])) && times > 0 && times < 365*24*60*60*1000){
endDate = formatMonth(arr[1]) + " " + formatDay(arr[1]);
}else{
endDate = formatMonth(arr[1]) + " " + formatDay(arr[1]) + ", " + formatYear(arr[1]);
}
arr[0] = startDate;
if(times === 0){
arr.splice(1,1);
}else{
arr[1] = endDate;
}
return arr;
}
function formatYear(_date){
return (new Date(_date)).getFullYear();
}
function formatDay(_date){
var _day = new Date(_date).getDate();
if (_day % 10 == 1 && _day != 11) {
_day = _day + "st";
} else if (_day % 10 == 2 && _day != 12) {
_day = _day + "nd";
} else if (_day % 10 == 3 && _day != 13) {
_day = _day + "rd";
} else {
_day = _day + "th";
}
return _day;
}
function formatMonth(_date){
var _month;
switch (new Date(_date).getMonth() + 1) {
case 1:
_month = "January";
break;
case 2:
_month = "February";
break;
case 3:
_month = "March";
break;
case 4:
_month = "April";
break;
case 5:
_month = "May";
break;
case 6:
_month = "June";
break;
case 7:
_month = "July";
break;
case 8:
_month = "August";
break;
case 9:
_month = "September";
break;
case 10:
_month = "October";
break;
case 11:
_month = "November";
break;
case 12:
_month = "December";
break;
}
return _month;
}
makeFriendlyDates(["2017-03-01", "2017-05-05"]);
运行结果:["March 1st, 2017","May 5th"]
7、类及对象构建算法挑战
用下面给定的方法构造一个对象.
方法有getFirstName(), getLastName(), getFullName(), setFirstName(first), setLastName(last), and setFullName(firstAndLast).
所有有参数的方法只接受一个字符串参数。
所有的方法只与实体对象交互。
var Person = function(firstAndLast) {
this.getFirstName = function(){return firstAndLast.split(" ")[0]};
this.getLastName = function(){return firstAndLast.split(" ")[1]};
this.getFullName = function(){return firstAndLast};
this.setFirstName = function(first){firstAndLast = first + " " + firstAndLast.split(" ")[1]};
this.setLastName = function(last){firstAndLast = firstAndLast.split(" ")[0] + " " + last};
this.setFullName = function(fullName){firstAndLast = fullName};
};
var bob = new Person('Bob Ross');
bob.getFullName();
运行结果:Bob Ross
8、 轨道周期算法挑战
返回一个数组,其内容是把原数组中对应元素的平均海拔转换成其对应的轨道周期.
原数组中会包含格式化的对象内容,像这样 {name: 'name', avgAlt: avgAlt}
.
至于轨道周期怎么求,戳这里 on wikipedia (不想看英文的话可以自行搜索以轨道高度计算轨道周期的公式).
求得的值应该是一个与其最接近的整数,轨道是以地球为基准的.
地球半径是 6367.4447 kilometers, 地球的GM值是 398600.4418, 圆周率为Math.PI
function orbitalPeriod(arr) {
var GM = 398600.4418;
var earthRadius = 6367.4447;
var newArr = [];
for(var i in arr){
period = Math.round(2 * Math.PI * Math.pow(Math.pow((earthRadius + arr[i].avgAlt), 3)/GM , 0.5));
newArr.push({name : arr[i].name , orbitalPeriod : period});
}
return newArr;
}
orbitalPeriod([{name: "iss", avgAlt: 413.6}, {name: "hubble", avgAlt: 556.7}, {name: "moon", avgAlt: 378632.553}]);
运行结果:
[{"name":"iss","orbitalPeriod":48815},{"name":"hubble","orbitalPeriod":50368},{"name":"moon","orbitalPeriod":20883321}]
9、数据组合求值算法挑战
找到你的另一半
都说优秀的程序员擅长面向对象编程,但却经常找不到另一半,这是为什么呢?因为你总是把自己局限成为一个程序员,没有打开自己的思维。
这是一个社群的时代啊,在这里你应该找到与你有相同价值观但又互补的另一半。
譬如:你编程能力强,估值11分,如果以20分为最佳情侣来计算,你应该找一个设计能力强,估值为9分的女生。
那么当你遇到一个设计能力为9分的女生,千万别犹豫,大胆去表白。千万别以为后面的瓜比前面的甜哦。
举个例子:有一个能力数组[7,9,11,13,15]
,按照最佳组合值为20来计算,只有7+13和9+11两种组合。而7在数组的索引为0,13在数组的索引为3,9在数组的索引为1,11在数组的索引为2。
所以我们说函数:pairwise([7,9,11,13,15],20)
的返回值应该是0+3+1+2的和,即6。
我们可以通过表格来更直观地查看数组中索引和值的关系:
Index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
Value | 7 | 9 | 11 | 13 | 15 |
任务:帮右边的pairwise函数实现上面的功能。
function pairwise(arr, arg) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
var arrObj = {};
arrObj.num = i;
arrObj.value = arr[i];
newArr.push(arrObj);
}
var count = 0;
var arrLength = newArr.length;
for (var j = 0; j < arrLength; j++) {
for (var k = j + 1; k < arrLength; k++) {
if (Number(newArr[j].value) + Number(newArr[k].value) == arg) {
count = count + newArr[j].num + newArr[k].num;
// console.log(count);
newArr.splice(k,1);
arrLength--;
newArr.splice(j,1);
arrLength--;
j--;
k-=2;
break;
}
}
}
return count;
}
pairwise([1, 3, 2, 4], 4);
运行结果:1