码迷,mamicode.com
首页 > 编程语言 > 详细

数据结构与算法 (03)

时间:2020-05-13 00:35:16      阅读:70      评论:0      收藏:0      [点我收藏+]

标签:接受   推导式   ict   优雅   mos   slice   需要   from   有一个   

继续来进行 pythonCookbook. 学习大佬代码使我快乐, 与其自己写, 还不如直接抄, 然后加上自己的注解, 这样才是最适合我的学习之道哇.

仰之弥高,钻之弥坚

老祖宗还是厉害呀, 要是我能认识到这种境界, 夫复何求呦.

序列元素去重并保持顺序

需求

在一个序列上, 保持元素顺序的前提下, 对相同元素值进行去重

方案

如果序列中的值都是 hashable 类型的, 那直接用 集合 set 或者 生成器 就轻松解决了.

def filter_dumplicate(items):
    """序列元素去重"""
    seen = set()
    for item in items:
        if item not in seen:
            # 所有的 yield item 组成 生成器对象
            yield item 
            seen.add(item)

# test            
a = [1, 5, 2, 1, 9, 1, 5, 10]
print(list(filter_dumplicate(a)))
[1, 5, 2, 9, 10]

我感觉, 这个大佬, 是不是搞得太复杂, 还是我 too simple ?

# 这样不更简单吗
ret = []
for i in a:
    if i not in ret:
        ret.append(i)
print(ret)
[1, 5, 2, 9, 10]

就去个重, 私以为不用那么麻烦的吧. 其实更多的是, 我们通常不用考虑其顺序, 就去重, 这样一来, 直接用集合, 一波带走.

print(set(a))  # 集合元素的顺序, 呃,,我也不清楚
{1, 2, 5, 9, 10}

这类写法, 针对元素是 hashable 的时候ok的, 但对于不可哈希, 如 dict 类型 的序列, 要去重元素的话, 也可以这样.

def dedupe(items, key=None):
    """字典元素去重"""
    seen = set()
    for item in items:
        # key 有值则为dict,取其值, else 是单序列值
        val = item if key is None else key(item)
        if val not in seen:
            yield item 
            seen.add(val)
    
# test 以后写函数, 优先用 yield 而不用 list 来不断 append 啦
a = [
    {‘x‘:1, ‘y‘:2}, {‘x‘:1, ‘y‘:3},
    {‘x‘:1, ‘y‘:2}, {‘x‘:2, ‘y‘:4}
    ]


print(list(dedupe(a, key=lambda d: (d[‘x‘], d[‘y‘]))))

print(list(dedupe(a, key=lambda d: d[‘x‘])))
[{‘x‘: 1, ‘y‘: 2}, {‘x‘: 1, ‘y‘: 3}, {‘x‘: 2, ‘y‘: 4}]
[{‘x‘: 1, ‘y‘: 2}, {‘x‘: 2, ‘y‘: 4}]

这个把函数改为生成器, 确实优雅呀, 学到了, 以后, 优先考虑哦. 不要再傻傻地只会 append 啦. 基于此中方法, 再对某单个字段或者属性, 或者更复杂的 数据结构来去重, 也是ok的.

可以发现, 针对于序列的遍历, 生成器 yield 则会更加通用, 而非以前傻傻滴, 先定义个空 lst , 过程中不断 append 这样就造成浪费内存了, yield 返回就行啦, 最后再统一 处理即可. 生成器, 对于文件中, 消除重复行, 也是可以的, 套路都固定的.

with open(some_file, ‘r‘) as f:
    for line in dedupe(f):
        ....

命名切片

需求

清理一大堆, 已经无法直视的硬编码切片下标.

方案

假设我们有一个切片, 是一个字符串中固定的位置, 如字符串或文件等.

###### 0123456789012345678901234567890123456789012345678901234567890‘ record = ‘....................100 .......513.25 ..........‘ cost = int(record[20:23]) * float(record[31:37])
record = ‘....................100 .......513.25 ..........‘ cost = int(record[20:23]) * float(record[31:37])
cost = int(record[20:23]) * float(record[31:37])

如果这样写切片, 维护起来简直崩溃, 于是呢,应该而给切片进行命名呀.

shares = slice(20, 33)

即用内置的 slice() 函数 创建一个切片对象, 可以被用在, 任何允许使用的方法.

items = [0, 1, 2, 3, 4, 5, 6]

# 前闭后开
a = slice(2,4)

print(items[2:4])
print(items[a])

items[a] = [88,99]
print(items)

del items[a]
print(items)

[2, 3]
[2, 3]
[0, 1, 88, 99, 4, 5, 6]
[0, 1, 4, 5, 6]

对于一个切片对象a, 还可以调用其 a.start, a.stop 等属性获取更多信息.

print(a)
print(a.start)
print(a.stop)
slice(2, 4, None)
2
4
个人感觉这玩意儿, 也没什么用呀.

序列中出现最多的元素

需求

找出序列中, 出现最多次数的元素.

方案

我做数据工作, 就会经常做这类的事情, 但我从来没有用过内置工具, 都是, 以字典的方式来进行词频统计, 感觉也行.

内置的 collections.Counter 类, 就专门来解决此类问题的, most_common() 方法, 很厉害的哦.

词频统计, 最为经典的就是.

words = [ ‘look‘, ‘into‘, ‘my‘, ‘eyes‘, ‘look‘, ‘into‘, 
         ‘my‘, ‘eyes‘, ‘the‘, ‘eyes‘, ‘the‘, ‘eyes‘, ‘the‘, 
         ‘eyes‘, ‘not‘, ‘around‘, ‘the‘, ‘eyes‘, "don‘t", 
         ‘look‘, ‘around‘, ‘the‘, ‘eyes‘, ‘look‘, ‘into‘, 
         ‘my‘, ‘eyes‘, "you‘re", ‘under‘ 
        ] 

from collections import Counter 

# 初始化Counter 对象, 将统计序列加进去
word_counts = Counter(words)
print(word_counts.most_common()) 
# 选出频率最高的3个词
print("--"*8)
top_three = word_counts.most_common(3)
print(top_three)
[(‘eyes‘, 8), (‘the‘, 5), (‘look‘, 4), (‘into‘, 3), (‘my‘, 3), (‘around‘, 2), (‘not‘, 1), ("don‘t", 1), ("you‘re", 1), (‘under‘, 1)]
----------------
[(‘eyes‘, 8), (‘the‘, 5), (‘look‘, 4)]

作为输入, Counter 对象可以接受任意, 可哈希元素构成的序列对象. 在底层的实现上, 一个 Counter 对象就是一个车字典, 将元素映射到它出现的次数上.

# Counter 对象, 其实就是字典, value 是频次
print(word_counts[‘eyes‘])
print(word_counts[‘not‘])
8
1

结论就是, 词频统计之类的, 可以优先考虑 Counter 对象, 当然, 我手动弄字典也可以呀, 我乐意, 又不难.

过滤序列元素

需求

过滤序列元素, 根据某些规则

方案

最为简单和我最喜欢用的是, 列表推导式.

my_list = [1, 4, -5, 10, 2, 3, -1]

# 过滤出 大于 0 的元素
print([i for i in my_list if i > 0])

# 小于0, 且是 "奇数"
print([i for i in my_list if i < 0 and i % 2 == 1])
[1, 4, 10, 2, 3]
[-5, -1]

用列表推导式的一个问题在于, 当输入很大, 则会得到一个大的结果集, 浪费内存, 解决方案就是用 迭代器呀.

#  生成器就节约内存了, 优先考虑
pos = (i for i in my_list if i > 0 and i % 2 == 0)
print(pos)

for i in pos:
    print(i)
<generator object <genexpr> at 0x00000217B924F938>
4
10
2

有时候, 过滤条件复杂, 就需要把过滤的规则给放到一个函数中啦, 然后将其放到内置的 filter() 函数中.

values = [‘1‘, ‘2‘, ‘-3‘, ‘-‘, ‘4‘, ‘N/A‘, ‘5‘]

def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False
    
# test
list(filter(is_int, values))
[‘1‘, ‘2‘, ‘-3‘, ‘4‘, ‘5‘]

我感觉这 filter 和 map 函数的用法是差不多的, 一个是过滤出, True 的, 一个是做映射, 映射到每个元素. 同时, filter() 函数创建了一个迭代器.

想想, 还是列表推导式, 更加爽一点哦, 比如, 在推导的时候, 还是可以做数据转换的.

lst = [1, 4, -5, 10, -9, 2, 3, -1]

import math
print([math.sqrt(i) for i in lst if i > 0])
[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]

不仅仅可以过滤, 结合 if - else 还能实现赋值.

# RELU 函数
print([i if i > 0 else 0 for i in my_list])
[1, 4, 0, 10, 2, 3, 0]

小结

  • 序列去重且有序, 用集合 set 和 生成器 yield, 优先考虑
  • 切片对象, 可用 slice 函数来创建命名对象, 便于管理
  • 序列统计, 如词频统计, 优先用 collectiions.Counter().most_common() 方法, 自己写也不难.
  • 过滤序列, 用列表推导式和生成器(元组推导式), 复杂结合 filter 函数, 结合 [a if aaa else b for aaa in xxx ]

数据结构与算法 (03)

标签:接受   推导式   ict   优雅   mos   slice   需要   from   有一个   

原文地址:https://www.cnblogs.com/chenjieyouge/p/12879740.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!