歡迎光臨
每天分享高質量文章

如何在一場面試中展現你對Python的coding能力?

(點選上方快速關註並設定為星標,一起學Python)

來源:Python資料科學    連結:

https://mp.weixin.qq.com/s/KwjLpeRT4l-pOc_iRCLWzg

 

如果你已經透過了招聘人員的電話面試,那麼下麵正是該展現你程式碼能力的時候了。無論是練習,作業,還是現場白板面試,這都是你證明自己的程式碼技巧的時刻。

 

我們知道面試官常常會出一些題讓你來解決,作為一名程式員,除了需要具備解決問題的思路以外,程式碼的質量和簡潔性也很關鍵。因為從一個人的程式碼可以直接看出你的基本功。對於Python而言,這就意味著你需要對Python的內建功能和庫有很深入的瞭解。

 

本篇給大家介紹一些很強大的功能,它們能讓面試官眼前一亮,覺得你很高階,這可以很大程度上給你加分。對於這些功能,我們從Python內建函式開始,然後是Python對資料結構的天然支援,最後是Python強大的標準庫。

 

選擇正確的內建功能

Python有一個大型標準庫,但只有一個內建函式的小型庫,這些函式總是可用的,不需要匯入。它們每一個都值得我們仔細研究,但是在研究前,我還是給大家一些小的提示,尤其是在其中一些函式的情況下,可以用什麼替代更好。

 

1. 使用enumerate()而不是range()進行迭代

 

在面試中,這種情況可能比任何其他情況都要多:您有一個元素串列,您需要遍歷串列,同時訪問索引和值。

 

有一個名為FizzBuzz的經典編碼面試問題可以透過迭代索引和值來解決。在FizzBuzz中,你將獲得一個整數串列,任務是執行以下操作:

 

  • 用“fizz”替換所有可被3整除的整數
  • 用“buzz”替換所有可被5整除的整數
  • 將所有可被3和5整除的整數替換為“fizzbuzz”

 

通常,開發人員將使用range()解決此問題:

>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i in range(len(numbers)):
...     if numbers[i] % 3 == 0 and numbers[i] % 5 == 0:
...         numbers[i] = 'fizzbuzz'
...     elif numbers[i] % 3 == 0:
...         numbers[i] = 'fizz'
...     elif numbers[i] % 5 == 0:
...         numbers[i] = 'buzz'
...
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']

Range允許你透過索引訪問數字元素,並且對於某些特殊情況也是一個很有用的工具。但在這種情況下,我們希望同時獲取每個元素的索引和值,更優雅的解決方案使用enumerate():

>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i, num in enumerate(numbers):
...     if num % 3 == 0 and num % 5 == 0:
...         numbers[i] = 'fizzbuzz'
...     elif num % 3 == 0:
...         numbers[i] = 'fizz'
...     elif num % 5 == 0:
...         numbers[i] = 'buzz'
...
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']

對於每個元素,enumerate()傳回一個計數器和元素值。計數器預設為0,也是元素的索引。不想在0開始你的計數?只需使用可選的start引數來設定偏移量:

>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i, num in enumerate(numbers, start=52):
...     print(i, num)
...
52 45
53 22
54 14
55 65
56 97
57 72

透過使用start引數,我們訪問所有相同的元素,從第一個索引開始,但現在我們的計數從指定的整數值開始。

 

2. 使用遞推式構造串列而不是map()和filter() 

“我認為刪除filter()和map()是非常有爭議的。”

– Guido van Rossum,Python的創造者 

一般使用者可能錯誤地認為它沒有爭議,但Guido有充分的理由想要從Python中刪除map()和filter()。一個原因是Python支援遞推式構造串列,它通常更容易閱讀並支援與map()和filter()相同的功能。

 

讓我們首先看看我們如何構造對map()的呼叫以及等效的遞推構造串列:

>>> numbers = [4, 2, 1, 6, 9, 7]
>>> def square(x):
...     return x*x
...
>>> list(map(square, numbers))
[16, 4, 1, 36, 81, 49]

>>> [square(x) for x in numbers]
[16, 4, 1, 36, 81, 49]

使用map()和串列推導的兩種方法都傳回相同的值,但串列推導更容易閱讀和理解。下麵我們可以對filter()及其等效的串列推導做同樣的事情:

>>> def is_odd(x):
...    return bool(x % 2)
...
>>> list(filter(is_odd, numbers))
[1, 9, 7]

>>> [x for x in numbers if is_odd(x)]
[1, 9, 7]

就像我們在map中看到的那樣,filter和串列推導方法傳回相同的值,但串列推導更容易理解。

 

來自其他語言的開發人員可能不同意構造串列比map和filter更容易閱讀,但根據我的經驗,初學者能夠更直觀地寫出串列推導。但無論哪種方式,在編碼面試中使用串列推導很少會出錯,因為它會讓你知道Python中最常見的是什麼。

 

3. 使用斷點breakpoint()除錯而不是print()

 

你可能透過在程式碼中新增print並檢視打印出的內容來除錯一個小問題。這種方法起初效果很好,但很快變得很麻煩。另外,在編碼面試設定中,你幾乎不希望在整個程式碼中呼叫print()。

 

相反,你應該使用除錯器。對於不是很瑣碎的錯誤,它幾乎總是比使用print()更快,並且鑒於除錯是編寫軟體的重要部分,它表明你知道如何使用可以在工作中快速開發的工具。

 

如果你使用的是Python 3.7,則無需匯入任何內容,只需在程式碼中要放入除錯器的位置呼叫breakpoint():

# Some complicated code with bugs

breakpoint()

呼叫breakpoint()會將你帶入pdb,這是預設的Python除錯器。在Python 3.6及更早版本中,你可以透過顯式匯入pdb來執行相同的操作:

import pdb; pdb.set_trace()

像breakpoint()一樣,pdb.set_trace()會將你帶入pdb除錯器。它不是那麼簡潔,而且需要記住的多一點。你可能想要嘗試其他除錯器,但pdb是標準庫的一部分,因此它始終可用。無論你喜歡哪種除錯器,在進行編碼面試設定之前,都值得嘗試使用它們來適應工作流程。

 

4. 使用f-Strings格式化字串 

 

Python有很多不同的方法來處理字串格式化,有時候不知道使用哪個。在coding的面試中,如果使用Python 3.6+,建議的格式化方法是Python的f-strings。

 

f-strings支援使用字串格式化迷你語言,以及強大的字串插值。這些功能允許你新增變數甚至有效的Python運算式,併在新增到字串之前在執行時對它們進行評估:

>>> def get_name_and_decades(name, age):
...     return f"My name is {name} and I'm {age / 10:.5f} decades old."
...
>>> get_name_and_decades("Maria", 31)
My name is Maria and I'm 3.10000 decades old.

f-string允許你將Maria放入字串中,併在一個簡潔的操作中新增具有所需格式的年齡。需要註意的一個風險是,如果你輸出使用者生成的值,那麼可能會帶來安全風險,在這種情況下,模板字串可能是更安全的選擇。

 

5. 使用sorted()對複雜串列進行排序

 

大量的編碼面試問題需要進行某種排序,並且有多種有效的方法可以進行排序。除非面試官希望你實現自己的排序演演算法,否則通常最好使用sorted()。你可能已經看到了排序的最簡單用法,例如按升序或降序排序數字或字串串列:

>>> sorted([6,5,3,7,2,4,1])
[1, 2, 3, 4, 5, 6, 7]

>>> sorted(['cat', 'dog', 'cheetah', 'rhino', 'bear'], reverse=True)
['rhino', 'dog', 'cheetah', 'cat', 'bear]

預設情況下,sorted()已按升序對輸入進行排序,而reverse關鍵字引數則按降序排序。

 

值得瞭解的是可選關鍵字key,它允許你在排序之前指定將在每個元素上呼叫的函式。新增函式允許自定義排序規則,如果要對更複雜的資料型別進行排序,這些規則特別有用:

>>> animals = [
...     {'type': 'penguin', 'name': 'Stephanie', 'age': 8},
...     {'type': 'elephant', 'name': 'Devon', 'age': 3},
...     {'type': 'puma', 'name': 'Moe', 'age': 5},
... ]
>>> sorted(animals, key=lambda animal: animal['age'])
[
    {'type': 'elephant', 'name': 'Devon', 'age': 3},
    {'type': 'puma', 'name': 'Moe', 'age': 5},
    {'type': 'penguin', 'name': 'Stephanie, 'age': 8},
]

透過傳入一個傳回每個元素年齡的lambda函式,可以輕鬆地按每個字典的單個值對字典串列進行排序。在這種情況下,字典現在按年齡按升序排序。

 

有效利用資料結構

 

演演算法在面試中得到了很多關註,但資料結構可能更為重要。在coding面試環境中,選擇正確的資料結構會對效能產生重大影響。除了理論資料結構之外,Python還在其標準資料結構實現中內建了強大而方便的功能。這些資料結構在面試中非常有用,因為它們預設為你提供了許多功能,讓你可以將時間集中在問題的其他部分。

 

1. 使用set儲存唯一值

 

我們通常需要從現有資料集中刪除重覆元素。新的開發人員有時會在串列應該使用集合時執行此操作,這會強制執行所有元素的唯一性。

 

假裝你有一個名為get_random_word()的函式。它將始終從一小組單詞中傳回一個隨機選擇:

>>> import random
>>> all_words = "all the words in the world".split()
>>> def get_random_word():
...    return random.choice(all_words)

你應該重覆呼叫get_random_word()以獲取1000個隨機單詞,然後傳回包含每個唯一單詞的資料結構。以下是兩種常見的次優方法和一種好的方法。

 

糟糕的方法

 

get_unique_words()將值儲存在串列中,然後將串列轉換為集合:

>>> def get_unique_words():
...     words = []
...     for _ in range(1000):
...         words.append(get_random_word())
...     return set(words)
>>> get_unique_words()
{'world', 'all', 'the', 'words'}

這種方法並不可怕,但它不必要地建立了一個串列,然後將其轉換為集合。面試官幾乎總是註意到(並詢問)這種型別的設計選擇。

 

更糟糕的做法

 

為避免從串列轉換為集合,你現在可以在不使用任何其他資料結構的情況下將值儲存在串列中。然後,透過將新值與串列中當前的所有元素進行比較來測試唯一性:

>>> def get_unique_words():
...     words = []
...     for _ in range(1000):
...         word = get_random_word()
...         if word not in words:
...             words.append(word)
...     return words
>>> get_unique_words()
['world', 'all', 'the', 'words']

這比第一種方法更糟糕,因為你必須將每個新單詞與串列中已有的每個單詞進行比較。這意味著隨著單詞數量的增加,查詢次數呈二次方式增長。換句話說,時間複雜度在O(N^2)的量級上增長。

 

優秀的方法 

 

現在,你完全跳過使用串列,而是從頭開始使用一組:

>>> def get_unique_words():
...     words = set()
...     for _ in range(1000):
...         words.add(get_random_word())
...     return words
>>> get_unique_words()
{'world', 'all', 'the', 'words'}

除了從頭開始使用集合之外,這可能與其他方法沒有太大的不同。如果你考慮.add()中發生了什麼,它甚至聽起來像第二種方法:得到單詞,檢查它是否已經在集合中,如果沒有,則將其新增到資料結構中。

 

那麼為什麼使用與第二種方法不同的集合呢?

 

它們是不同的,因為集合儲存元素的方式允許接近恆定時間檢查值是否在集合中,而不像需要線性時間查詢的串列。查詢時間的差異意味著新增到集合的時間複雜度以O(N)的速率增長,這在大多數情況下比第二種方法的O(N^2)好得多。

 

2. 使用生成器節省記憶體

 

前面提到,串列推導是方便的工具,但有時會導致不必要的記憶體使用。想象一下,你被要求找到前1000個完美正方形的總和,從1開始。你知道串列推導,所以你快速編寫一個有效的解決方案:

>>> sum([i * i for i in range(1, 1001)])
333833500

解決方案會列出1到1,000,000之間的每個完美平方,並對值進行求和。你的程式碼會傳回正確的答案,但隨後您的面試官會開始增加您需要總和的完美正方形的數量。

 

起初,你的功能不斷彈出正確的答案,但很快就開始放慢速度,直到最後這個過程似乎永遠持續下去。這不是你想要在面試中發生的一件事。

 

這裡發生了什麼?

 

它正在列出你要求的每個完美的方塊,並將它們全部加起來。具有1000個完美正方形的串列在計算機術語中可能不會很大,但是1億或10億是相當多的資訊,並且很容易佔用計算機的可用記憶體資源。這就是這裡發生的事情。

 

值得慶幸的是,有一種解決記憶體問題的快捷方法。你只需用括號替換方括號:

>>> sum((i * i for i in range(1, 1001)))
333833500

換出括號會將串列推導更改為生成器運算式。當你知道要從序列中檢索資料,但不需要同時訪問所有資料的時候,生成器運算式非常適合

 

生成器運算式傳回生成器物件,而不是建立串列。該物件知道它在當前狀態中的位置(例如,i = 49)並且僅在被要求時計算下一個值。

 

因此,當sum透過重覆呼叫.__ next __()來迭代生成器物件時,生成器檢查i

等於多少,計算i * i,在內部遞增i,並將正確的值傳回到sum。該設計允許生成器用於大量資料序列,因為一次只有一個元素存在於記憶體中。

 

3. 使用.get()和.setdefault()在字典中定義預設值 

 

最常見的程式設計任務之一涉及新增,修改或檢索可能在字典中或可能不在字典中的項。Python字典具有優雅的功能,可以使這些任務簡潔明瞭,但開發人員通常會在不需要時檢查值。

 

想象一下,你有一個名為cowboy的字典,你想得到那個cowboy的名字。一種方法是使用條件顯式檢查key:

>>> cowboy = {'age'32, 'horse''mustang', 'hat_size''large'}
>>> if 'name' in cowboy:
...     name = cowboy['name']
... else:
...     name = 'The Man with No Name'
...
>>> name
'The Man with No Name'

此方法首先檢查字典中是否存在name鍵,如果存在,則傳回相應的值。否則,它傳回預設值。

 

雖然清楚地檢查key確實有效,但如果使用.get(),它可以很容易地用一行代替:

>>> name = cowboy.get('name', 'The Man with No Name')

get()執行與第一種方法相同的操作,但現在它們會自動處理。如果key存在,則傳回適當的值。否則,將傳回預設值。

 

但是,如果你想在仍然訪問name的key時使用預設值更新字典呢?.get()在這裡沒有真正幫助你,所以你只需要再次顯式檢查這個值:

>>> if 'name' not in cowboy:
...     cowboy['name'] = 'The Man with No Name'
...
>>> name = cowboy['name']

檢查values並設定預設值是一種有效的方法,並且易於閱讀,但Python再次使用.setdefault()提供了更優雅的方法:

>>> name = cowboy.setdefault('name', 'The Man with No Name')

.setdefault()完成與上面程式碼片段完全相同的操作。它檢查cowboy中是否存在名稱,如果是,則傳回該值。否則,它將cowboy [‘name’]設定為The Man with No Name並傳回新值。

 

利用Python的標準庫

 

預設情況下,Python提供了許多功能,這些功能只是一個匯入陳述句。它本身就很強大,但知道如何利用標準庫可以增強你的編碼面試技巧。

 

從所有可用模組中挑選最有用的部分很困難,因此本節將僅關註其實用功能的一小部分。希望這些對您在編碼訪談中有用,並且您希望瞭解更多有關這些和其他模組的高階功能的資訊。

 

1. 使用collections.defaultdict()處理缺少的字典鍵 

 

當你為單個鍵設定預設值時,.get()和.setdefault()可以正常工作,但通常需要為所有可能的未設定鍵設定預設值,尤其是在面試環境中進行程式設計時。

 

假裝你有一群學生,你需要記錄他們在家庭作業上的成績。輸入值是具有格式(student_name,grade)的元組串列,但是你希望輕鬆查詢單個學生的所有成績而無需迭代串列。

 

儲存成績資料的一種方法是使用將學生姓名對映到成績串列的字典:

>>> student_grades = {}
>>> grades = [
...     ('elliot', 91),
...     ('neelam', 98),
...     ('bianca', 81),
...     ('elliot', 88),
... ]
>>> for name, grade in grades:
...     if name not in student_grades:
...         student_grades[name] = []
...     student_grades[name].append(grade)
...
>>> student_grades
{'elliot': [91, 88], 'neelam': [98], 'bianca': [81]}

在這種方法中,你迭代學生並檢查他們的名字是否已經是字典中的屬性。如果沒有,則將它們新增到字典中,並將空串列作為預設值。然後將實際成績附加到該學生的成績串列中。

 

但是有一個更簡潔的方法,可以使用defaultdict,它擴充套件了標準的dict功能,允許你設定一個預設值,如果key不存在,它將按預設值操作:

>>> from collections import defaultdict
>>> student_grades = defaultdict(list)
>>> for name, grade in grades:
...     student_grades[name].append(grade)

在這種情況下,你將建立一個defaultdict,它使用不帶引數的list建構式作為預設方法。沒有引數的list傳回一個空串列,因此如果名稱不存在則defaultdict呼叫list(),然後再把學生成績新增上。如果你想更炫一點,你也可以使用lambda函式作為值來傳回任意常量。

 

利用defaultdict可以使程式碼更簡潔,因為你不必擔心key的預設值。相反,你可以在defaultdict裡處理它們一次,然後key就終存在了。

 

2. 使用collections.Counter計算Hashable物件 

 

假如你有一長串沒有標點符號或大寫字母的單詞,你想要計算每個單詞出現的次數。

 

你可以使用字典或defaultdict增加計數,但collections.Counter提供了一種更清晰,更方便的方法。Counter是dict的子類,它使用0作為任何缺失元素的預設值,並且更容易計算物件的出現次數:

>>> from collections import Counter
>>> words = "if there was there was but if
... there was not there was not".split()
>>> counts = Counter(words)
>>> counts
Counter({'if': 2, 'there': 4, 'was': 4, 'not': 2, 'but': 1})

當你將單詞串列傳遞給Counter時,它會儲存每個單詞以及該單詞在串列中出現的次數。

如果你好奇兩個最常見的詞是什麼?只需使用.most_common():

>>> counts.most_common(2)
[('there', 4), ('was', 4)]

 

.most-common()是一個方便的方法,只需按計數傳回n個最頻繁的輸入。

 

3. 使用字串常量訪問公共字串組

 

現在有一個瑣事需要判斷!‘A’>‘a’是真是假?

這是假的,因為A的ASCII程式碼是65,但a是97,65不大於97。為什麼答案很重要?因為如果你想檢查一個字元是否是英語字母表的一部分,一種流行的方法是看它是否在A和Z之間(在ASCII圖表上是65和122)。

 

檢查ascii程式碼是可行的,但是在面試時卻很笨拙,很容易弄亂,特別是當你記不清是小寫還是大寫的ascii字元排在第一位的時候。這時候,使用定義在字串模組中的常量要容易得多。

 

你可以使用is_upper(),它傳回字串中的所有字元是否都是大寫字母:

 

>>> import string
>>> def is_upper(word):
...     for letter in word:
...         if letter not in string.ascii_uppercase:
...             return False
...     return True
...
>>> is_upper('Thanks Geir')
False
>>> is_upper('LOL')
True

 

is_upper()迭代word中的字母,並檢查字母是否為string.ascii_大寫字母的一部分。如果你打印出string.ascii_大寫,你會發現它只是一個字串,該值設定為文字“ABCDEFGHIJKLMNOPQRSTUVWXYZ”。

 

所有字串常量都只是經常取用的字串值的字串。其中包括以下內容:

 

  • string.ascii_letters
  • string.ascii_uppercase
  • string.ascii_lowercase
  • string.digits
  • string.hexdigits
  • string.octdigits
  • string.punctuation
  • string.printable
  • string.whitespace

 

這些更容易使用,更重要的是,更容易閱讀。

 

4. 使用Itertools生成排列和組合

 

面試官喜歡給出真實生活的場景,讓面試看起來不那麼嚇人,所以這裡有一個人為的例子:你去遊樂園,決定找出每一對可能坐在過山車上的朋友。

 

除非生成這些配對是面試問題的主要目的,否則很可能生成所有可能的配對只是朝著工作演演算法前進的一個乏味的步驟。你可以自己用巢狀for迴圈計算它們,也可以使用強大的itertools庫。

 

itertools有多個工具來生成可重覆輸入資料序列,但現在我們只關註兩個常見函式:itertools.permutations()和itertools.combinations()。

 

itertools.permutations()構建所有排列的串列,這意味著它是輸入值的每個可能分組的串列,其長度與count引數匹配。r關鍵字引數允許我們指定每個分組中有多少值:

 

>>> import itertools
>>> friends = ['Monique', 'Ashish', 'Devon', 'Bernie']
>>> list(itertools.permutations(friends, r=2))
[('Monique', 'Ashish'), ('Monique', 'Devon'), ('Monique', 'Bernie'),
('Ashish', 'Monique'), ('Ashish', 'Devon'), ('Ashish', 'Bernie'),
('Devon', 'Monique'), ('Devon', 'Ashish'), ('Devon', 'Bernie'),
('Bernie', 'Monique'), ('Bernie', 'Ashish'), ('Bernie', 'Devon')]

 

對於排列,元素的順序很重要,因此(“sam”、“devon”)表示與(“devon”、“sam”)不同的配對,這意味著它們都將包含在串列中。

 

itertools.combinations()生成組合。這些也是輸入值的可能分組,但現在值的順序無關緊要。因為(‘sam’、‘devon’)和(‘devon’、‘sam’)代表同一對,所以輸出串列中只會包含它們中的一個:

 

>>> list(itertools.combinations(friends, r=2))
[('Monique', 'Ashish'), ('Monique', 'Devon'), ('Monique', 'Bernie'),
('Ashish', 'Devon'), ('Ashish', 'Bernie'), ('Devon', 'Bernie')]

 

由於值的順序與組合有關,因此同一輸入串列的組合比排列少。同樣,因為我們將r設定為2,所以每個分組中都有兩個名稱。

 

.combinations和.permutations只是強大庫的一個小例子,但是當你試圖快速解決演演算法問題時,即使這兩個函式也非常有用。

 

在下一次面試中,你可以放心地使用一些不太常見但功能更強大的標準特性。從整體上來說,要瞭解該語言有很多東西,但本文應該為大家提供一個起點,讓大家能夠更深入地瞭解該語言,同時在面試時更有效地使用Python。

 

原文:

https://realpython.com/python-coding-interview-tips/

    已同步到看一看
    贊(0)

    分享創造快樂