c

codeye

V1

2022/09/22阅读:28主题:默认主题

ddm

先写英文再写Python 对我帮助很大的一件事是在写Python之前先写英文。只要写下你所有的想法。不断添加到你的想法列表中。有时你必须用一个新的思想清单重新开始。把所有的想法写出来,然后选择你想先开始编码的想法。

总结 好了,这些都是我最喜欢的技巧,从我的脑海中浮现。如果我想到了,我还会添加更多。

我最喜欢的LeetCode问题的Python技巧 最近我花了很多时间在LeetCode上练习,所以我想我应该分享一些我最喜欢的中级Python技巧。我还会介绍一些你可能还没有开始使用的Python的新功能。我将从基本技巧开始,然后转向更高级的技巧。

获取帮助() Python 的文档非常棒,其中一些例子就来自那里。

例如,如果你只用google搜索 "heapq",你会看到heapq的官方文档,这通常是足够的。

然而,有时在shell中快速使用help()也很有帮助。在这里,我不记得push()实际上被称为append()。

>>> help([])

>>> dir([])

>>> help([].append)

enumerate() 如果你需要在一个列表上循环,你可以使用enumerate()来获得项目以及索引。作为一个气动装置,我喜欢在enumerate(...)中为(i, x)考虑。

for (i, x) in enumerate(some_list):
    ...
items()

同样,当使用items()在一个dict上循环时,你可以同时获得键和值。

for (k, v) in some_dict.items():
    ...
    [] vs. get()

记住,当你在dict中使用[]时,如果值不存在,你会得到一个KeyError。与其看一个项目是否在dict中,然后查找它的值,不如使用get()。

val = some_dict.get(key)  # It defaults to None.
if val is None:
    ...
Similarly, .setdefault() is sometimes helpful.

val = some_dict.get(key) # 它默认为None。 如果val是None: ... 同样地,.setdefault()有时也很有帮助。

有些人喜欢直接使用[]来处理KeyError,因为异常在Python中并不像在其他语言中那么昂贵。

range()比你想象的更聪明

range() is smarter than you think
for item in range(items):
    ...
    
for index in range(len(items)):
    ...
    
# Count by 2s.
for i in range(0, 100, 2):
    ...

# Count backward from 100 to 0 inclusive.
for i in range(100, -1, -1):
    ...
    

#以2为单位计数。 for i in range(0, 100, 2): ...

#从100到0(含)向后数。 for i in range(100, -1, -1): ...

#好吧,聪明的裤子先生,我相信你知道这些,但是你知道吗? #你可以传递一个范围对象,而且它知道如何通过切片符号来扭转 #通过切分符号来反转自己?

r = range(100)
r = r[::-1] # range(99, -1, -1)
print(f'') 调试

你已经改用Python的新格式字符串了吗?它们比%和.format()更方便、更安全(从注入漏洞来看)。它们甚至有一种语法可以输出东西以及它的值。

#得到2+2=4

print(f'Got {2+2=}')

for else

Python有一个我在其他编程语言中没有见过的特性。for和while后面都可以跟上一个else子句,这在你搜索某些东西的时候很有用。


for item in some_list:
    if is_what_im_looking_for(item):
        print(f"Yay! It's {item}.")
        break
else:
    print("I couldn't find what I was looking for.")

将列表作为堆栈 使用一个列表作为堆栈的成本是(摊销后)O(1)。

elements = []
elements.append(element)  # Not push
element = elements.pop()

请注意,在列表的开头或中间插入东西的成本更高,它必须将所有东西向右移动--见下面的deque。

sort() vs. sorted()
#sort() 对一个列表进行原地排序。
my_list.sort()

#而 sorted() 则返回一个迭代器的排序 *副本*。
my_sorted_list = sorted(some_iterable)

而且,如果你需要对对象进行排序,这两个函数都可以接受一个关键函数。

集合和frozenset 集合在很多问题上都非常有用! 以防你不知道其中的一些技巧。

#现在有了创建集合的语法。

s = {'Von'}

#有一些集合的 "理解",就像列表的理解一样,不过是针对集合的理解。

s2 = {f'{name} the III' for name in s}.
{'Von the III'}

#如果你不记得如何使用union、intersection、difference等等。

help(set())

#如果你需要一个不可变的集合,例如,作为一个dict键使用,可以使用frozenset。

frozenset((1, 2, 3))
deque

如果你发现自己需要一个队列或列表,你可以从任何一方推送和弹出,使用deque。

>>> from collections import deque
>>> 
>>> d = deque()
>>> d.append(3)
>>> d.append(4)
>>> d.appendleft(2)
>>> d.appendleft(1)
>>> d
deque([1, 2, 3, 4])
>>> d.popleft()
1
>>> d.pop()
4

使用堆栈而不是递归 不要使用递归(递归的深度约为1024帧),你可以使用一个while循环,自己手动管理一个堆栈。下面是一个略显矫揉造作的例子。

work = [create_initial_work()]
while work:
    work_item = work.pop()
    result = process(work_item)
    if is_done(result):
        return result
    work.push(result.pieces[0])
    work.push(result.pieces[1])

使用yield 如果你不知道yield,你可以去花些时间学习一下。它很厉害。

有时,当你在一个生成器中时,你需要调用另一个生成器。Python 现在有 yield from 来解决这个问题。

def my_generator():
    yield 1
    yield from some_other_generator()
    yield 6

所以,这里有一个回溯的例子。

class Solution:
    def problem(self, digits: str) -> List[str]:
        def generate_possibilities(work_so_far, remaining_work):
            if not remaining_work:
                if work_so_far:
                    yield work_so_far
                return
            first_part, remaining_part = remaining_work[0], remaining_work[1:]
            for i in things_to_try:
                yield from generate_possibilities(work_so_far + i, remaining_part)
        
        output = list(generate_possibilities(no_work_so_far, its_all_remaining_work))
        return output

如果递归少于998,约1000个 "层级",但每个级别都有大量的可能性,这就很合适。如果你需要超过1000层的递归,这就行不通了。在这种情况下,请切换到 "使用堆栈而不是递归"。

预先初始化你的列表 如果你提前知道你的列表有多长,你可以通过预先初始化它来避免多次调整其大小。

dp = [None] * len(items)
collections.Counter()

你有多少次用dict来计算东西?它在Python中是内置的。

>>> from collections import Counter
>>> c = Counter('abcabcabcaaa')
>>> c
Counter({'a': 6, 'b': 3, 'c': 3})

defaultdict 同样地,还有defaultdict

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> d['girls'].append('Jocylenn')
>>> d['boys'].append('Greggory')
>>> d

defaultdict(<class 'list'>, {'girls': ['Jocylenn'], 'boys': ['Greggory']})

堆 heapq 我在学校里听说过堆,但我并不真正知道它们是什么。好吧,事实证明它们对几个问题很有帮助,而且 Python 内置了一个基于列表的堆实现。

如果你不知道什么是堆,我推荐这个视频和这个视频。它们将解释什么是堆,以及如何用列表实现一个堆。

heapq模块是一个管理堆的内置模块。它建立在现有列表的基础上。

import heapq

some_list = ...
heapq.heapify(some_list)

# The head of the heap is some_list[0].
# The len of the heap is still len(some_list).

heapq.heappush(some_list, item)
head_item = heapq.heappop(some_list)

heapq模块还内置了nlargest和nsmallest,所以你不必自己去实现这些东西。

请记住,heapq是一个迷你堆。假设你真正想要的是一个最大堆,而且你不是在处理整数,而是在处理对象。下面是如何调整你的数据以使其符合heapq的思维方式。

heap = []
heapq.heappush(heap, (-obj.value, obj))
(ignored, first_obj) = heapq.heappop()

这里,我用-来使它成为一个最大的堆。我把东西包装在一个元组中,这样它就被obj.value排序了,而且我把obj作为第二个值,这样我就可以得到它。

使用bisect进行二进制搜索 我相信你以前已经实现了二进制搜索。Python 已经内置了它。它甚至有关键字参数,你可以用它来只在列表的一部分进行搜索。

import bisect

insertion_point = bisect.bisect_left(sorted_list, some_item, lo=lo, high=high)

注意关键参数,它有时很有用,但可能要花点功夫才能按你想要的方式工作。

namedtuple和数据类 元组是很好的,但是如果要记住元素的顺序或者只解压元组中的一个元素,可能是一件很痛苦的事情。这就是namedtuple出现的地方。

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x''y'])
>>> p = Point(5, 7)
>>> p
Point(x=5, y=7)
>>> p.x
5
>>> q = p._replace(x=92)
>>> p
Point(x=5, y=7)
>>> q
Point(x=92, y=7)

请记住,图元是不可变的。我特别喜欢使用命名图元来解决回溯问题。在这种情况下,不可变性实际上是一个巨大的资产。我使用一个命名图元来表示问题在每一步的状态。我已经做了这么多东西,还有这么多东西没做,这是我现在的位置,等等。在每个步骤中,你都要用旧的命名图元,并以不可改变的方式创建一个新的。

如果你需要一些可变的东西,用一个数据类来代替。

"""
用于跟踪库存中的物品的类。
名称: str
单位价格: float
库存数量: int = 0
"
""
    
from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

item = InventoryItem(name='Box', unit_price=19, quantity_on_hand=2)

当你想用一个小类来保存一些数据,但又不想浪费很多时间从头开始写一个时,数据类是非常好的。

int, decimal, math.inf,等等。 值得庆幸的是,Python的int类型默认支持任意大的值。

>>> 1 << 128
340282366920938463463374607431768211456

如果你需要处理像钱这样的东西,而浮点数不够精确,或者需要很多小数位的精度时,还有十进制模块。

有时,他们会说范围是-2 ^ 32到2 ^ 32 - 1,你可以通过移位得到这些值。

>>> -(2 ** 32) == -(1 << 32)
True
>>> (2 ** 32) - 1 == (1 << 32) - 1
True

有时,用math.inf(即无穷大)初始化一个变量,然后试图找到小于这个值的新值,这很有用。

闭包 我不确定每个面试官都会喜欢这个,但我倾向于跳过OOP的东西,使用一堆本地辅助函数,这样我就可以通过闭包来访问东西。


class Solution():  # This is what LeetCode gave me.
    def solveProblem(self, arg1, arg2):  
    # Why they used camelCase?
      
        def helper_function():
            # 我可以通过闭包访问arg1和arg2。
            # 我不需要在自己身上存储它们,也不需要明确地传递它们。
            # 明确地传递。
            return arg1 + arg2
          
        counter = 0
        
        def can_mutate_counter():
            # 通过使用非本地,我甚至可以改变计数器。
            # 我在实践中很少使用这种方法。我通常把它作为参数传入
            # 作为一个参数并返回一个值。
            非本地计数器
            counter += 1
            
       can_mutate_counter()
       return helper_function() + counter

匹配语句 你知道 Python 现在有一个匹配语句吗?

取自:https://learnpython.com/blog/python-match-case-statement/

>>> command = 'Hello, World!
>>> 匹配命令。
... case '
Hello, World!':
... print('
Hello to you too!')
... case '
Goodbye, World!':
... print('
See you later')
... case other:
... print('
没有找到匹配的内容')

它实际上比switch语句复杂得多,所以看看吧,特别是如果你从未在Haskell这样的函数式语言中使用过match。

有序数据 如果你需要实现一个 LRU 缓存,有一个 OrderedDict 将会非常有用。

Python 的 dicts 现在默认是有序的。然而,OrderedDict 的文档说,在某些情况下,你可能仍然需要使用 OrderedDict。我不记得了。如果你从来不需要你的dict是有序的,只需阅读文档并弄清楚你是否需要OrderedDict,或者你是否可以只使用普通的dict。

@functools.cache 如果你需要一个缓存,有时候你可以直接把你的代码包在一个函数里,然后使用functools.cache。

from functools import cache

@cache
def factorial(n):
    return n * factorial(n - 1) if n else 1
  
print(factorial(5))

factorial.cache_info() # CacheInfo(hit=3, misses=8, maxsize=32, currsize=8)

调试ListNodes

很多问题都涉及到由LeetCode提供的ListNode类。它不是很 "容易调试"。暂时添加这段代码来改善这一点。

def list_node_str(head):
    seen_before = set()
    pieces = []
    p = head
    while p is not None:
        if p in seen_before:
            pieces.append(f'loop at {p.val}')
            break
        pieces.append(str(p.val))
        seen_before.add(p)
        p = p.next
    joined_pieces = ', '.join(pieces)  
    return f'[{joined_pieces}]'

ListNode.str = list_node_str 用数组模块节省内存 有时你需要一个非常长的简单数字(或布尔)值的列表。数组模块可以帮助解决这个问题,而且在你已经得到你的算法后,这是一个减少内存使用的简单方法。

>>> import array
>>> array_of_bytes = array.array('b')
>>> array_of_bytes.frombytes(b'\0' * (array_of_bytes.itemsize * 10_000_000))

密切注意你配置的数组所接受的值的类型。阅读文档。

我相信有一种方法可以为布尔运算数组使用单独的位来节省更多的空间,但这可能会花费更多的CPU,而我通常更关心CPU而不是内存。

对成功的情况而不是错误的情况使用一个异常 很多Python程序员不喜欢这个技巧,因为它等同于goto,但我还是偶尔会发现它很方便。


class Eureka(StopIteration):
    """Eureka means "I found it!" """
    pass

  
def do_something_else():
    some_value = 5
    raise Eureka(some_value)


def do_something():
    do_something_else()


try:
    do_something()
except Eureka as exc:
    print(f'I found it: {exc.args[0]}')

先写英文再写Python 对我帮助很大的一件事是在写Python之前先写英文。只要写下你所有的想法。不断添加到你的想法列表中。有时你必须用一个新的思想清单重新开始。把所有的想法写出来,然后选择你想先开始编码的想法。

总结 好了,这些都是我最喜欢的技巧,从我的脑海中浮现。如果我想到了,我还会添加更多。

分类:

后端

标签:

后端

作者介绍

c
codeye
V1