题目链接:https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting

 

1. Sum All Numbers in a Range


传入的参数是包含两个数字的数组,要求计算两数之间(包含边界)所有数字的和,较小的数字不一定在数组第一位

function sumAll(arr) {
  var start = arr[0]
        ,end = arr[1];
  return (start + end) * (Math.abs(end - start) + 1) / 2;
}

sumAll([1, 5]); // 15

 

 

2. Diff Two Arrays


传入两个数组,要求求差,即返回一个新数组,新数组里的值只能在数组1或数组2中找到,不能同时出现在两个数组中;

function diffArray(arr1, arr2) {
  arr1 = arr1.slice();    // 为了不改变传入的数组的值,先对数组进行一次浅复制
  arr2 = arr2.slice();
  for (var i = arr1.length - 1; i >= 0; i--) {
    var index = arr2.indexOf(arr1[i]);    // 寻找两个数组同时拥有的项,找到的话把这一项从两个数组中删掉
    if (index > -1) {                    // 必须先把index存起来,因为后面使用splice()会改变数组,这时再动态查询index计算会出错误
      arr1.splice(i, 1);                // arr1去掉了一项,后面一项往前移,此时再用arr2.indexOf(arr1[i])得到的结果就是错的了
      arr2.splice(index, 1);
    }
  }
  var newArr = arr1.concat(arr2);    // 剩下来的就是两个数组中不同的值
  return newArr;
}

 

也可以先把两个数组合并起来,再使用类似数组去重的方法,但是找到的相同元素不是留一个,而是要全删掉;

function diffArray(arr1, arr2) {
  var newArr = arr1.concat(arr2);
  var re = [];
  newArr.forEach(ele => {
    var index = re.indexOf(ele);    // 这里的index不用先保存也不会出错
    if (index === -1) {            // 如果结果数组中没有,就把这一项加进去
      re.push(ele);
    } else {                      // 如果在结果数组中已存在,那就忽略这一项,并把在结果数组中存在的这一项也删掉,这里和数组去重不一样
      re.splice(index, 1);
    }
  });
  return re;
}

 

 

3. Seek and Destroy


传入一个数组和若干个其他参数,要求删除数组中与其他参数相等的项;

function destroyer(arr) {
  var removedVal = Array.prototype.slice.call(arguments, 1);
  var re = arr.filter(ele => removedVal.indexOf(ele) === -1);
  return re;
}

destroyer([1, 2, 3, 1, 2, 3], 2, 3);  // [1, 1]

 

 

4. Wherefore art thou


写一个函数,这个函数接收两个参数,第一个参数是成员都是对象的数组,第二个参数是一个对象;

要求遍历对象数组,如果对象成员包含与第二个参数所有键值对相同的键值对,就返回这个对象,最后返回符合要求的对象数组;

也就是说,如果第一个参数是[{name: 'suki', age: 21, gender: 'female'}, {name: 'cora', age: 22}, {name: 'suki'}],第二个参数是{name: 'suki', age: 21},最后的返回结果是[{name: 'suki', age: 21, gender: 'female'}];

换句话说,返回结果是一个新数组,由第一个参数中符合条件的对象组成,条件是第二个参数是这个对象的一个子集;

function whatIsInAName(collection, source) {
  var arr = [];
  var sourceKeys = Object.keys(source);
  arr = collection.filter(obj => sourceKeys.every(key => source[key] === obj[key]));
  return arr;
}

whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" });
// [{ first: "Tybalt", last: "Capulet" }]

 

 

5. Spinal Tap Case


把字符串变成'aaa-bbb-ccc-ddd'这种格式,作为参数的字符串可能'thisIsSpinalTap' 这样的,也可能是'This Is<abbr>Spinal_Tap'这样的;虽然我的解法通过了测试,但自己觉得有点繁琐二球难看,如果你知道更简单的方法的话欢迎留言~

function spinalCase(str) {
  var reg1 = /[^\w\s'].*[^\w\s']/g;    // 匹配像<abbr>这种分隔符
  str = str.replace(reg1, '-');       // 用短横线先做个记号分隔单词
  var reg2 = /([A-Z])/g;             // 有些单词之间没有分隔符,只靠后一个单词首字母大写来区分两个单词
  str = str.replace(reg2, ($1) => '-' + $1.toLowerCase());    // 用短横线做个记号分隔单词并把大写字母转成小写字母
  var reg3 = /[^a-zA-Z0-9']+/g;
  str = str.replace(reg3, '-');
  return str.replace(/^-/, '');
}

spinalCase('This Is Spinal Tap'); // 'this-is-spinal-tap'

 

 

6. Pig Latin


把英语单词转换成pig latin,也就是把单词开头的辅音字母(一个或连续多个)移到单词尾部再加上后缀'ay',如果单词不以辅音字母开头,直接在词尾加'way';

传进来的参数都是小写的英文单词;

function translatePigLatin(str) {
  var consonants = /^[^aeiou]+/;
  if (str.match(consonants) !== null) {
    return RegExp.rightContext + RegExp.lastMatch + 'ay';
  } else {
    return str + 'way';
  }
}

translatePigLatin("consonant");    // 'onsonantcay'

 

 

7. Search and Replace


函数接收三个字符串参数(str, before, after),要求在str中找到before,然后用after替换掉它;

after的大小写要与before一致,如果before是'Dog',after是'cat',那么替换结果是'Cat';

function myReplace(str, before, after) {
  var reg = new RegExp(before);
  return str.replace(reg, match => {
    if (/^[A-Z]/.test(match)) {
      return after.replace(after[0], after[0].toUpperCase());
    }
    return after;
  });
}

myReplace("A quick brown fox jumped over the lazy dog", "jumped", "leaped");
// "A quick brown fox leaped over the lazy dog"

 

 

8. DNA Pairing


传入一个DNA序列字符串,如'GCG',要求给DNA序列配对后,返回一个二维数组;

配对规则是'A'配'T','C'配'G';

返回的数组中的每一项是一对配对的DNA,如[["G", "C"], ["C","G"],["G", "C"]],传入的DNA字符串中的元素放在前面;

function pairElement(str) {
  var re = [];
  var strand = str.split('');
  var pairing = new Map([['G', 'C'], ['C', 'G'], ['A', 'T'], ['T', 'A']]);
  strand.forEach(ele => {
    re.push([ele, pairing.get(ele)]);
  });
  return re;
}

pairElement("GCG");    // [["G", "C"], ["C","G"],["G", "C"]]

 

 

 

9. Missing lette


接收一个字符串参数,要求找出字符串中缺失的字母并返回,如传入'abce',需要返回'd';如果传入的字符串没有缺失的字母,返回undefined;

function fearNotLetter(str) {
  var arr = str.split('').map(char => char.charCodeAt(0));
  var missing;

  for (var i = arr.length - 1; i >= 0; i--) {
    if (arr[i] - arr[i - 1] > 1) {
      missing = arr[i] - 1;
      break;
    }
    if (i === 0) {
      return undefined;
    }
  }
  return String.fromCharCode(missing);
}

fearNotLetter("abce");    // 'd'

 

 

 

10. Sorted Union


要求写一个函数,函数接收两个或以上数组作为参数,对这些数组进行去重后返回一个新数组,举个例子,传入参数为([1, 3, 2], [5, 2, 1, 4], [2, 1]),要求返回[1, 3, 2, 5, 4]数组里的数字的排列顺序跟它们在原数组的顺序一样;我的思路就是把所有传进来的数组合成一个新数组再去重;

function uniteUnique(...arrs) {
  var arrUnion = arrs.reduce((a, b) => a.concat(b));
  var re = new Set(arrUnion);
  return [...re];
}

uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]);  // [1, 3, 2, 5, 4]

 

 

 

11. Convert HTML Entities


把传入的字符串中不符合HTML语法的字符进行转义,即对&, <, >, ", ' 进行转义;

function convertHTML(str) {
  var convertRules = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    '\'': '&apos;'
  };
  return str.replace(/[&<>"']/g, match => convertRules[match]);
}

convertHTML("Dolce & Gabbana");    //  "Dolce &​amp; Gabbana"

 

 

12. Sum All Odd Fibonacci Numbers


传入一个正整数,返回一个斐波那契数列中小于等于这个正整数的奇数的和;

斐波那契数列开始两项分别是1和1,之后每一项都是前面两项的和;

我的思路是先把这个斐波那契数列中小于参数的项存在一个数组中,再遍历这个数组,求所有奇数的和;

function sumFibs(num) {
  var fibNums = [1, 1];
  for (var i = 0; i <= fibNums.length; i++) {
    fibNums[i] = fibNums[i] ? fibNums[i] : (fibNums[i-1] + fibNums[i-2]);
    if (fibNums[i] > num) {
      fibNums.pop();
      break;
    } 
  }
  var re = fibNums.reduce((a, b) => (b % 2 === 0) ? a : a + b, 0);
  return re;
}

sumFibs(4);  // 5

 

 

 

13. Sum All Primes


传入一个正整数,返回小于等于这个正整数的所有质数的和;

function sumPrimes(num) {
  function isPrime(n) {
    for (let i = 2; i <= n / 2; i++) {
      if (n % i === 0) {
        return false;
      }
    }
    return true;
  }

  var re = 0;
  for (let i = 2; i <= num; i++) {
    if (isPrime(i)) {
      re += i;
    }
  }
  return re;
}

sumPrimes(10);    // 17

 

 

 

14. Smallest Common Multiple


传入一个数组,数组中有两个数字,要求求这两个个数字范围内的所有数字的最小公倍数;

例如,传入的数组是[1, 5],那么需要求得1, 2, 3, 4, 5这几个数字的最小公倍数,即60;

传入的数组中的数字不一定是升序排列的,也可能传入[5, 1];

我的思路是先填充一个新数组,得到参数数组范围内的所有数字;

要求得若干个数字的最小公倍数,先求两个数的最小公倍数,再求这个最小公倍数与第三个数的最小公倍数,重复操作即可得到结果;

求两个数的最小公倍数时,我先使用更相减损法求得两数的最大公约数,再用公式求两数的最小公倍数;

function smallestCommons(arr) {
  // 使用arr范围内的数字填充新数组
  var newArr = [];
  var start, end;
  if (arr[0] - arr[1] < 0) {
    start = arr[0];
    end = arr[1];
  } else {
    start = arr[1];
    end = arr[0];
  }

  for (let i = start; i <= end; i++) {
    newArr.push(i);
  }

  // 求数组所有项的最小公倍数
  var re = newArr.reduce((a, b) => {
    var num1 = a
      ,num2 = b;
    // 先求两个数的最大公约数
    // 第一步:如果两个数都是偶数,先约简
    // 否则直接开始第二步
    var multiply2 = 1;
    while ((num1 % 2 === 0) & (num2 % 2 === 0)) {
      num1 /= 2;
      num2 /= 2;
      multiply2 *= 2;
    }
    // 第二步:更相减损法求最大公约数
    // 以大数减小数,得到的等数与减数比较,再用较大数减较小数
    // 重复上面的操作,直到得到的等数与减数相等
    var bigNum, smallNum, diff;
    if (num1 > num2) {
      bigNum = num1;
      smallNum = num2;
    } else {
      bigNum = num2;
      smallNum = num1;
    }
    diff = bigNum - smallNum;

    while (diff !== smallNum) {
      if (smallNum > diff) {
        bigNum = smallNum;
        smallNum = diff;
      } else {
        bigNum = diff;
      }
      diff = bigNum - smallNum;
    }
    // 第一步中约简掉的'2'与第二步中得到的等数的乘积就是两个数的最大公约数
    var biggestCommonDivisor = diff * multiply2;

    // 求最小公倍数
    // 两个数的乘积等于两数最大公约数与最小公倍数的乘积
    var smallestCommonMultiple = a * b / biggestCommonDivisor;
    // 得到的最小公倍数再与下一个数求最小公倍数
    // 重复操作即可得到若干个数的最小公倍数
    return smallestCommonMultiple;
  });
  return re;
}

smallestCommons([1,5]);    // 60

 

 

 

15. Drop it


函数接收两个参数,一个数组(arr),一个函数(func);

要求从索引0开始遍历数组(arr),把数组项作为参数传给函数(func),如果函数(func)返回false,则去掉这一项,直到数组项满足函数(func)的要求返回true,停止遍历,返回余下的数组部分;

function dropElements(arr, func) {
  while (!func(arr[0])) {
    arr.shift();
  }
  return arr;
}

dropElements([1, 2, 3], function(n) {return n < 3; });  // [3, 4]

 

 

 

16. Steamroller


传入一个多维数组,要求把它完全展开;

我的思路是,给函数增加一个参数re用于存储展开后的数组项,它的默认值是一个空数组;

在函数中,遍历多维数组,对每一个数组项进行判断,如果数组项不是数组,把这一项推到re中,如果数组项是数组,那就再调用steamrollArray函数,把这一项和re作为参数传进函数;遍历完成后返回结果;

function steamrollArray(arr, re=[]) {
  arr.forEach(ele => {
    if (!Array.isArray(ele)) {
      re.push(ele);
    } else {
      steamrollArray(ele, re);
    }
  });
  return re;
}
steamrollArray([1, [2], [3, [[4]]]]);   // [1, 2, 3, 4]

 

 

17. Binary Agents


传入一个二进制数字构成的字符串,要求把这个字符串翻译成英文,二进制数字之间以空格隔开;

function binaryAgent(str) {
  var arr = str.split(' ').map(ele => {
    // 把每个二进制数字字符串转换成十进制数字,再转成英文字母
    return String.fromCharCode(parseInt(ele, 2));
  });
  return arr.join('');
}

binaryAgent("01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111");
// "Aren't bonfires fun!?"

 

 

18. Everything Be True


传入两个参数,第一个是由对象组成的数组,第二个是一个对象属性字符串,如果在数组的每一个对象中,与第二个参数同名的属性都为真值,函数返回true,否则返回false;

function truthCheck(collection, pre) {
  return collection.every(obj => !!obj[pre]);  // 我在这里使用了!!是因为觉得这样比较好理解,去掉结果也是一样的
}

truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex");
// true

 

 

19. Arguments Optional


写一个函数,这个函数接收一个或两个参数,如果传入两个参数(a, b),返回两个参数的和;

如果传入一个参数(a),返回一个接收一个参数(b)的函数,该函数返回外部函数参数a和自身参数b的和;

如果任意一个参数不是数字,函数返回undefined;

function addTogether(...arg) {
  if (arg.every(ele => (typeof ele) == 'number')) {
    return arg.length === 2 ? arg[0] + arg[1] : function(n) {
      return (typeof n) == 'number' ? arg[0] + n : undefined;
    };
  } else {
    return undefined
  }
}

var sumTwoAnd = addTogether(2);
sumTwoAnd(3);  // 5

 

 

20. Make a Person


有一个Person构造函数,需要在这个构造函数中增加以下方法:

getFirstName(),getLastName(),getFullName(),setFirstName(first),setLastName(last),setFullName(firstAndLast)

其中那些接收一个参数的方法必须接收一个字符串参数;要求这些方法是唯一能与对象交互的方法;

var Person = function(firstAndLast) {
  // 定义私有变量
  var firstName = firstAndLast.split(' ')[0]
      ,lastName = firstAndLast.split(' ')[1];

  this.getFirstName = function() {
    return firstName;
  };
  this.getLastName = function() {
    return lastName;
  };
  this.getFullName = function() {
    return this.getFirstName() + ' ' + this.getLastName();
  };

  this.setFirstName = function(first) {
    firstName = first;
  };
  this.setLastName = function(last) {
    lastName = last;
  };
  this.setFullName = function(firstAndLast) {
    firstName = firstAndLast.split(' ')[0]
    ,lastName = firstAndLast.split(' ')[1]
  }
};

var bob = new Person('Bob Ross');
bob.getFullName();  // 'Bob Ross'
bob.setFirstName('Haskell');
bob.getFullName();  // 'Haskell Ross'