CodingTour
ARTS #73 | Programming Life

Algorithm

本周选择的算法题是:Merge Two Sorted Lists

规则如下:

Merge two sorted linked lists and return it as a new sorted list. The new list should be made by splicing together the nodes of the first two lists.

Example 1:

Input: l1 = [1,2,4], l2 = [1,3,4]
Output: [1,1,2,3,4,4]

Example 2:

Input: l1 = [], l2 = []
Output: []

Example 3:

Input: l1 = [], l2 = [0]
Output: [0]

Constraints:

  • The number of nodes in both lists is in the range [0, 50].
  • -100 <= Node.val <= 100
  • Both l1 and l2 are sorted in non-decreasing order.

Solution

递归

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if not l1: return l2
        if not l2: return l1

        if l1.val < l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

迭代

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        head = ListNode()
        curr = head
        while l1 and l2:
            if l1.val < l2.val:
                curr.next = l1
                l1 = l1.next
            else:
                curr.next = l2
                l2 = l2.next
            curr = curr.next
        curr.next = l1 if l1 else l2
        return head.next

Review

Goodbye Virtual Environments?

市面上有许多 Python 虚拟环境管理工具:

  • venv
  • poetry
  • pipenv

如作者所说,这些工具在解决环境隔离的同时引入了以下新的问题:

  • 额外的学习曲线
  • 终端隔离 - 在每个终端里都要做一遍重复的操作

  • 认知负担 - 你需要记得 activating / deactivating、环境安装位置等

虽然 PEP 582 仍处于草拟阶段,但可能是一个答案:

  • 将包安装到工程根目录下的 __pypackages__
  • 导包时优先导入 __pypackages__ 中的包
  • 因为是语言级支持,不再需要额外的工具和认知负担

不过想真正做到这些,实际上是将包管理工具和虚拟环境工具合二为一了。

Tip

海象操作符

# python3.8
if (test := "123    ".strip()) == "123":
    print(test)

表达式多了一个副作用:为目标赋值。

Share

分享一段通过 Python 管理 Provisioning Profile 的脚本吧,提供了以下能力:

  • 查询已注册的设备
  • 注册设备
  • 查询 Profile
  • 创建 Profile
  • 删除 Profile
  • 下载 Profile

代码如下:

Device = collections.namedtuple("Device", [
    "addedDate", # 添加日期
    "name", # 名称
    "deviceClass", # 设备类型
    "model", # 设备型号
    "id", # 唯一标识符
    "udid", # 设备标识符
    "status", # 启用状,ENABLED/DISABLED
])

Profile = collections.namedtuple("Profile", [
    "type", # 类型
    "id", # 唯一标识符
    "bundle_id", # 包名
    "certificates", # 证书
])

def generate_jwt(config: dict) -> str:
    """
    生成 JWT Token
    config: {
        issuer_id
        key_id
        key_dir # 密钥文件所在的目录
    }
    """
    print("开始生成 JWT Token...")

    issuer_id = config["issuer_id"]
    key_id = config["key_id"]
    key_dir = os.path.join(os.path.dirname(__file__), config["key_dir"])

    command = "xctoken generate"
    process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, env=dict(os.environ, ISSUER_ID=issuer_id, KEY_ID=key_id, KEY_DIR=key_dir))
    process.wait()
    token = process.stdout.read().decode("utf-8").strip()
    print("生成成功")
    return token

def make_request(func, token: str, url: str, params: dict = None) -> dict:
    response = func(url, headers={
        "Authorization": f"Bearer {token}"
    }, json=params)
    if response.status_code // 10 == 20 :
        if len(response.text) >= 2:
            return response.json()
        else:
            return response.text
    else:
        raise Exception(f"网络请求失败: {url}, {response.content}")

def fetch_devices(token: str) -> [Device]:
    """
    获取所有已注册的设备
    """
    print("开始获取目前所有已注册的设备...")
    response = make_request(requests.get, token, "https://api.appstoreconnect.apple.com/v1/devices?limit=100")
    raw_devices = response["data"]
    devices = [Device(*(raw_device[k] if k in raw_device else raw_device["attributes"][k] for k in Device._fields)) for raw_device in raw_devices]
    print(f"获取成功,当前已注册设备数量为{len(devices)}")
    return devices

def register_device(token: str, name: str, udid: str) -> str:
    """
    注册设备
    """
    print("正在注册设备...")
    response = make_request(requests.post, token, "https://api.appstoreconnect.apple.com/v1/devices", params={
        "data": {
            "attributes": {
                "name": name,
                "platform": "IOS",
                "udid": udid,
            }, "type": "devices"
        }
    })
    print("注册成功")
    return response["data"]["id"]

def create_and_download_profile(token: str, name: str, type: str, bundle_id: str, devices: [dict], certificates: [dict]):
    """
    创建并下载 Profile
    """
    response = make_request(requests.post, token, "https://api.appstoreconnect.apple.com/v1/profiles", params={
        "data": {
            "attributes": {
                "name": name,
                "profileType": type
            }, "relationships": {
                "bundleId": {
                    "data": bundle_id
                },
                "certificates": {
                    "data": certificates
                },
                "devices": {
                    "data": devices
                },
            }, "type": "profiles"
        }
    })
    attributes = response["data"]["attributes"]
    profileContent = base64.b64decode(attributes["profileContent"])

    # 下载到当前文件所在的目录
    path = os.path.join(os.path.dirname(__file__), f"{name}.mobileprovision")
    with open(path, 'wb') as file:
        file.write(profileContent)

def delete_profile(token: str, profile_id: str):
    """
    删除 Profile
    """
    print("准备删除旧的 Profile...")
    make_request(requests.delete, token, f"https://api.appstoreconnect.apple.com/v1/profiles/{profile_id}")
    print("删除成功")

def query_profile(token: str, name: str) -> dict:
    """
    获取 Profile 信息
    """
    print(f"开始获取 Profile 信息: {name}")
    response = make_request(requests.get, token, f"https://api.appstoreconnect.apple.com/v1/profiles?filter[name]={name}&fields[profiles]=certificates,uuid,bundleId,profileType&include=certificates,bundleId&fields[bundleIds]=identifier")
    raw_profile = response["data"][0]
    certificates = raw_profile["relationships"]["certificates"]["data"]
    profile_type = raw_profile["attributes"]["profileType"]
    profile_id = raw_profile["id"]
    profile_bundle_id = raw_profile["relationships"]["bundleId"]["data"]

    profile = Profile(profile_type, profile_id, profile_bundle_id, certificates)
    print("获取成功")
    return profile

封装一层简单的 wrapper 即可。