有趣的前端程式設計題:攜程 2018 春招

RayJune發表於2018-03-31

認真編寫的題目能測出程式碼水平,而有趣的題目則能激發人的“程式碼慾望”。

前言

前後參加過網易、美團、頭條、滴滴等公司的線上程式設計題。簡單的認為題目大體有以下三種型別:

  1. 認真編寫的題目會促進答題者去認真的回答
  2. 有趣的題目會激起答題者的興趣,以至於能激發一些『靈感』
  3. 沒有意義的題目不值得答,這是一個雙向選擇

筆試題在很多時候確實是個“坑”,能避開就避開,因為畢竟像狀況 2 的是少數但優質的題目確實是一個很好的程式設計能力檢驗,遇到了就不要錯過

2018 年春招的攜程前端筆試題是一個不錯的例子,下面對其中的程式設計題做一番分析。


P.S. 原文顯示效果更好喔:) check:rayjune.me/有趣的前端程式設計題

作者:RayJune轉載請署名,請尊重博主含辛茹苦、遍查資料、一行一行含淚碼出來的成果

常見問題

簡單列舉一下可能被問到的問題:

  • 博主你的程式碼確定 100% AC 了嗎?

非常確定 100% AC。同時這意味著細節複雜度上的處理到位。(實際前端開發中要處理大量細節複雜度的東西,這同樣很有意思)

  • 為啥不用 ES6 來寫呢?

避免有些筆試平臺不支援 ES6 的狀況,你懂的

  • 為啥不用 new Set() 來去重呢?

同上,另外順便回顧一下陣列去重。在面試的時候寫 new Set 來陣列去重會被 diss 的,別問我為什麼知道 :>

  • 為什麼博主你的程式碼沒有雜糅在一起,而是合理的抽出為多個函式呢?

一種程式設計品味

程式設計風格

簡單陳述一下文中程式碼使用的程式設計風格:

  1. 使用 ES5,以避免有些線上程式設計平臺不支援 ES6 的狀況(所以在這裡沒有用 new Set()
  2. Airbnb 程式碼規範,不使用單 var 模式
  3. 變數定義(var),函式體(body),return 值三者用空行隔開,邏輯鮮明
  4. 有意義的變數命名
  5. 適當的函式抽取,一個函式只做一件事情

另外還有 根據不同場合使用合適的型別判斷方式

  • Array.isArray 判斷陣列,
  • Object.prototype.toString.call 來判斷純物件
  • typeof 判斷基本型別和 function
  • instanceof 來判斷自定義的物件

一. 字串擷取

題目

描述

給定一個長度小於 50 且包含字母和數字的任意字串,要求按順序取出當中的數字和英文字母,數字需要去重,重新排列後的字串數字在前,字母在後。

輸入

需要擷取的字串(包含數字和字母)

輸出

按照要求重新排列的字串

樣例輸入

'攜程C2t0r1i8p2020校招'
複製程式碼

樣例輸出:

'2018Ctrip'
複製程式碼

解答

肯定有同學表示第一題不值得分析。但我還是想拋磚引玉一下,思路如下:

  1. 字串轉陣列str.split('')進而使用陣列的各種操作方法,如 arr.forEach
  2. 判斷字串值是否為數字/\d/.test(element) 或者 Number.isNaN(Number(element)
  3. 判斷字串值是否為字母/[a-zA-Z]/.test(element)
  4. 數字字串去重(利用陣列去重)
  5. 輸出數字+字母

由此有了這一版程式碼:

條件 1~3

function handleStr(str) {
  var arr = str.split('');
  var nums = '';
  var words = '';
    
  arr.forEach(function (element) {
    if (/\d/.test(element))) {
      nums += element;
    } else if (/[a-zA-Z]/.test(element) ) {
      words += element;
    }
  });

  return uniqueStr(nums) + words;
}
複製程式碼

去重

作為前端開發超高頻面試題,相信大家早已對陣列去重熟捻於心:

基本型別去重:

function unique(arr) {
  return arr.filter(function (element, index) {
    return arr.indexOf(element) === index;
  });
}
複製程式碼

基本+複雜型別去重:

function unique(arr) {
  var hash = {};

  return arr.filter(function (element) {
    if (hash.hasOwnProperty(element)) {
      return false;
    }
    hash[element] = true;

    return true;
  });
}
複製程式碼

由於數字去重(str,基本型別)基於陣列去重,我們要對原來的陣列去重做一點修改:

function uniqueStr(str) {
  var arr = str.split('');

  return arr.filter(function (element, index) {
    return arr.indexOf(element) === index;
  }).join('');
}
複製程式碼

string.split()array.join() 幫助我們自由的遊走在字串和陣列間。

最終解答 1

function handleStr(str) {
  var arr = str.split('');
  var nums = '';
  var words = '';
    
  arr.forEach(function (element) {
    if (/\d/.test(element)) {
      nums += element;
    } else if (/[a-zA-Z]/.test(element) ) {
      words += element;
    }
  });

  return uniqueStr(nums) + words;
}

function uniqueStr(str) {
  var arr = str.split('');

  return arr.filter(function (element, index) {
    return arr.indexOf(element) === index;
  }).join('');
}

// 測試
console.log(handleStr('攜程C2t0r1i8p2020校招'));
// 2018Ctrip
複製程式碼

最終解答 2

非常感謝評論區 @while大水逼 大神的寶貴建議,博主筆試的時候沒想到 str.match(regex) 的方法。實現更簡潔、邏輯的展示更好,在此補上:

function handleStr(str) {
  var nums = str.match(/\d/g).join('');
  var words = str.match(/[a-zA-Z]/g).join('');

  return uniqueStr(nums) + words;
}

function uniqueStr(str) {
  var arr = str.split('');

  return arr.filter(function (element, index) {
    return arr.indexOf(element) === index;
  }).join('');
}

// 測試
console.log(handleStr('攜程C2t0r1i8p2020校招'));
// 2018Ctrip
複製程式碼

二. 陣列升維

題目

描述

對一維陣列,根據 type 型別分組成二維陣列

輸入

  • 輸入的引數可能是空陣列 [],空物件 nullundefined,數字,字串等異常值;
  • 也可能是結構為 [{ type, content}] 的有效值;
  • 甚至是 [null, null, (type, content)] 等有效和非法值混合的資料。

輸出

  • 當輸入資料不合法時,輸出空陣列 []
  • 當輸入資料有效時(請先過濾陣列裡的異常元素),然後將相同 type 值的元素合併,形成新元素 {"type": "A", "contents": [content1, content2]},其中,contents 為一個陣列,元素為所有 type 值相同的 content 值。
  • 注意,輸出的是一個標準 JSON 格式

樣例輸入

var input = [null, 2, "test", undefined, {
  "type": "product",
  "content": "product1"
}, {
  "type": "product",
  "content": "product2"
}, {
  "type": "tag",
  "content": "tag1"
}, {
  "type": "product",
  "content": "product3"
}, {
  "type": "tag",
  "content": "tag2"
}];
複製程式碼

樣例輸出

[{"type":"product","contents":["product1","product2","product3"]},{"type":"tag","contents":["tag1","tag2"]}]
複製程式碼

解答

乍一看要求頗多,我們一點點來拆解:

條件 1

當輸入資料不合法時,輸出空陣列 []

什麼資料不合法?輸入值不為 JSON 格式(即 array 型別)

還有呢?輸入值為 JSON 格式(即 array 型別),但長度為 0;

由此寫下第一句:

function groupList(list) {
  if (!Array.isArray(list) || list.length === 0) { return []; }
}
複製程式碼

條件 2

當輸入資料有效時(請先過濾陣列裡的異常元素)

過濾掉[],空物件 nullundefined,數字,字串等異常元素:

function groupList(list) {
  if (!Array.isArray(list) || list.length === 0) { return []; }
  
  var validItems = getValidItems(list);
}

function getValidItems(json) {
  return json.filter(function (element) {
    return isPureObject(element)
  });
}

function isPureObject(item) {
  return Object.prototype.toString.call(item).slice(8, -1) === 'Object';
}
複製程式碼

條件 3(隱藏條件)

且慢,結構不為 { "type": "xx", "content": "yy" } 的值,是不是也為異常元素呢?為此在 getValidItems 里加上一句:

function getValidItems(json) {
  return json.filter(function (element) {
    return isPureObject(element) && element.type && element.content;
  });
}
複製程式碼

可能有同學會問,這裡為什麼不用 typeof 判斷 object,而是那麼麻煩擼了一個 isPureObject 呢?

首先明確 typeof 用來判斷基本型別和 function(check: 根據不同場合使用合適的型別判斷方式),舉個例子:

var demo = [1, 2];

demo.type = '我其實是陣列';
demo.content = '但我也有 type 和 content 屬性,判斷不出來了吧';
複製程式碼

如果是

function getValidItems(json) {
  return json.filter(function (element) {
    return typeof element === 'object' && element.type && element.content;
  });
}
複製程式碼

顯然無法過濾 demo 這種情況了,更何況還可以是 regex 等各種非 function 的物件。

所以線上程式設計題請慎重考慮邊緣情況。

條件 4

過濾完成後,將相同 type 值的元素合併,形成新元素

function groupList(list) {
  if (!Array.isArray(list) || list.length === 0) { return []; }
  
  var validItems = getValidItems(list);
  var result = {};

  validItems.forEach(function (item) {
    if (result.hasOwnProperty(item.type)) {
      result[item.type].push(item.content);
    } else {
      result[item.type] = [];
      result[item.type].push(item.content);
    }
  });

  return result;
}
複製程式碼

條件 5

貌似我們已經完成了將相同 type 值合併這一步驟,但是:

當前的結構是 {type1: contentsArr1, type2: contentsArr2} 的結構,與題目要求的:[{type1: contentsArr1}, {type1: contentsArr2}] 不相同。

不難,再加一步便是:

function adjustFormat(obj) {
  var result = [];

  Object.keys(obj).forEach(function (type) {
    result.push({ type: type, contents: obj[type] });
  });

  return result;
}
複製程式碼

且慢,根據一個陣列產生一個新的陣列,用 array.map 是不是更物盡其用呢?(再次感謝評論區 @while大水逼 提出的方案)

更純粹的函數語言程式設計,更直觀的邏輯展示:

function adjustFormat(obj) {
  return Object.keys(obj).map(function (type) {
    return { type: type, contents: obj[type] };
  });
}
複製程式碼

最終解答

完整的程式碼:

function groupList(list) {
  if (!Array.isArray(list) || list.length === 0) { return []; }
  
  var validItems = getValidItems(list);
  var result = {};

  validItems.forEach(function (item) {
    if (result.hasOwnProperty(item.type)) {
      result[item.type].push(item.content);
    } else {
      result[item.type] = [];
      result[item.type].push(item.content);
    }
  });

  return adjustFormat(result);
}

function getValidItems(json) {
  return json.filter(function (element) {
    return isPureObject(element) && element.type && element.content;
  });
}

function isPureObject(item) {
  return Object.prototype.toString.call(item).slice(8, -1) === 'Object';
}

function adjustFormat(obj) {
  return Object.keys(obj).map(function (type) {
    return { type: type, contents: obj[type] };
  });
}

// test
var input = [null, 2, "test", undefined, {
    "type": "product",
    "content": "product1"
},  {
    "type": "product",
    "content": "product2"
},  {
    "type": "tag",
    "content": "tag1"
}, {
    "type": "product",
    "content": "product3"
}, {
    "type": "tag",
    "content": "tag2"
}];

console.log(JSON.stringify(groupList(input)));
// [{"type":"product","contents":["product1","product2","product3"]},{"type":"tag","contents":["tag1","tag2"]}]
複製程式碼

總結

回到文章題目本身上來,什麼算是有趣的前端筆試題

  • 不是固定套路的,不考“背誦能力”的
  • 體現 JS 能力的,考察點廣泛全面
  • 和前端密切相關的和實際開發有聯絡
  • 注重細節複雜度,而不是純粹的“演算法複雜度”(僅針對前端開發,僅為博主一家之言)

如果是現場面試手寫程式設計題,我覺得應該再加上一條:

  • 體現程式設計素養的,注重縱向擴充

如果哪家公司有這樣的程式設計題,請把我一波流內推帶走 :)


更多資訊,check: rayjune.me/about


有同學問為神馬沒有第三題的題解,因為自己在第二題的細節複雜度中消耗了大量時間(盯了一二十分鐘才發現需要進行 Array.isArray(input) 的判斷) - =(相信有不少同學也是這樣),沒有時間把第三題擼出來了。。。

相關文章