lisp封装成vla函数_Lisp List 和函数式编程 (in Python)
![](/assets/blank.gif)
注:本篇文章的内容纯属学术性质,代码相较于常规可能会更慢,更占用空间,请读者以读懂为主;虽然在本文平台上(python)是慢的,但它们却是在函数式语言中快的存在. "cons list" 就是所谓的列表处理器 (list processor) 所处理的列表。
Scheme 曾经是我最喜爱的语言,其中的列表和其它语言的数组有很大的不同:首先,它是递归嵌套的,其次,每一个列表的“长度”都是 2,再之,每一个列表都必须有一个“空列表”。起初听着会让人感觉比较奇怪,尤其是习惯了常规的过程式思想的玩家。
为了不让大家学习 Lisp,我在 Python 上稍微翻译了一下具有函数思维的代码,来让读者在不了解 lisp 的情况下了解所谓的 cons
是什么东西。 当然本人能力有限,代码肯定也有能够改进的地方,过于核心的东西也讲解不了(<del>本人也不会</del>)。
lisp list 首先,长这样:
'()
或 null
或 empty
:这是一个空列表,长度为 0,什么也没有。
(cons 1 null
): 这个列表有两个元素,1 和另一个空列表。一般来说后面的这个空列表不算数,所以长度是 1. 前面的 cons
是前缀式代表着列表的开始,可以理解成 cons(1, null)
。
(cons 1 (cons 2 (cons 3 (cons 4 null))))
:长度是4. 由此可见列表是嵌套组成的。在一对括号中,第一个就是我们要存的变量,第二项则必须是另一个列表。所以一个列表里的最后一项必须是空列表。如果要更改其中的 1,则需要把带有 1 的这项剥开扔掉,换成新的:
( cons 1 (cons 2 (cons 3 (cons 4 null))))
---> (cons 2 (cons 3 (cons 4 null)))
--->(cons "hello" (cons 2 (cons 3 (cons 4 null))))
所以这其实是产生了一个新列表。
(cons 1 . 2)
:这货无视就好了。
随便定义一些前缀式函数
from typing import Union, Callable
Number = Union[int, float]def add(a: Number, b: Number) -> Number:return a + b
def sub(a: Number, b: Number) -> Number:return a - b
def mul(a: Number, b: Number) -> Number:return a * b
def div(a: Number, b: Number) -> Number:return a / b
def string_append(a: str, b:str) -> str:return a + badd(1,2) # ==> 3
这样看着更像是 scheme。
定义 cons
以下的函数会都在类里定义,这叫取其精华,要不然括号有点太多。
class cons(tuple):def __new__(cls, *arg):if len(arg) != 0:if len(arg) != 2: # 如果列表非空,必须有两项raise ValueError('arity mismatch')if not isinstance(arg[1], cls): # 第二项必须也是一个列表raise ValueError('second argument must be a list')self = super().__new__(cls, arg)return self
可以看见我是继承的元组。其实像链表一样定义左右两边也是可以的。这里利用了元组不能改变的特性,所以这强迫我们如果要修改列表,必须要生成一个新的。其中 *arg
是和普通元组不一样的地方,一般的如果我们想要一个元组是 tuple([1,2])
,解包的写法比较方便。 看一看效果:
# 空列表
> empty := cons()
()
> type(empty)
__main__.cons
# 长度为 2
> cons(1, cons(2, cons()))
(1, (2, ()))
# 长度为 4, 第一项又嵌套一个长度为 2 的列表
> cons(cons(1, cons(2, cons())), cons("Hi", cons(int, cons(lambda: print('hello world'), cons()))))
((1, (2, ())), ('Hi', (int, (<function __main__.<lambda>()>, ()))))
一般来说,一对括号里的第一项称之为 first
,第二项称之为 rest
(剩余的列表部分)。
def first(self): return self[0]def rest(self): return self[1]car = first # first 的别名cdr = rest # rest 的别名def isnull(self) -> bool: # 判断一个列表是否是空的return len(self) == 0
对于 Python 来说, _getitem_
最多索引到 1.
看看列表是怎么递归的
首先是按照序列来取内容。
def ref(self, index: int):if index == 1:return self.first()else:return self.rest().ref(index-1)
rest
会返回列表的剩余部分,这部分是一个新的列表。这代码的意思是一直剥离掉当前列表中最外面的那一层,当剥的数量足够了,剩下列表的第一项就是想要的内容。
> x = cons(cons(1, cons(2, cons())), cons("Hi", cons(int, cons(lambda: print('hello world'), cons()))))
> x.ref(3)
int
其次是取得列表的最后一项。当一个列表只有一个元素和一个空列表的时候,即第二项是空列表,他的第一项就是最后一项,这就是常说的 base case。在取得最后一个之前,一直剥离外层的括号就OK了。
def last(self):def last1(lst: cons):if lst.rest().isnull():return lst.first()else:return last1(lst.rest())return last1(self)
这里面放了一个本地函数,不放的话其实和上面的函数一模一样。(练习:把这个函数写成5行)我比较喜欢这样,免得让人老以为他在更改 "self"。
> x.last()
<function __main__.<lambda>()>
对于列表的递归操作一般就是这样,套路感十分明显。再来一个获取列表长度的例子
def length(self) -> int:def length1(lst: cons, n: int) -> int:if lst.isnull():return nelse:return length1(lst.rest(), n+1)return length1(self, 0)
> x.length()
4
> x.car().length()
2
下面的例子返回一个反转之后的列表:
def reverse(self):def reverse1(lst: cons, reversedd: cons):if lst.isnull():return reverseddelse:return reverse1(lst.rest(), cons(lst.first(), reversedd))return reverse1(self, cons())
首先,把原来的列表放在左边,右边放一个空表。把左边列表最外层和右边列表结合形成一个新列表,这样原来的最外项就成了右边的最内项。最后当左边取完了的时候,返回右边的。
> null = cons()
> cons(1, cons(2, cons(3, null))).reverse()
(3, (2, (1, ())))
还有许多的诸如替换,增加,复制等操作大多都是 if list is null then ... else do ...
套路的,很好学习。
最后来一个打印整个列表的函数:
def quasi_repr(self) -> str:if self.isnull():return ''()'def reprB(item) -> str:if isinstance(item, cons):if item.isnull():return '()'else:return string_append('(', repr1(item))else:return str(item)def repr1(lst: cons) -> str:if lst.rest().isnull():return string_append(reprB(lst.first()), ')')else:return string_append(reprB(lst.first()), string_append(', ', repr1(lst.rest()) ))return string_append(''(', repr1(self))
是的,你没看错,这是两个递归函数在相互调用!再有嵌套列表的时候,要考虑两层,一方面是当前对象是列表中第一项的时候,一方面是当前对象又是一个列表的时候。在函数式和列表里,这是常规操作。在其他语言中,需要用到这种思维的地方一般由一个循环和一个递归来实现,比如获取文件夹的大小。
> print(cons(1, cons(2, null)) )
`(1, 2)
> print(cons(cons(1, cons(2, cons())), cons("Hi", cons(int, cons(lambda: print('hello world'), null)))).quasi_repr() )
`((1, 2), Hi, <class 'int'>, <function <lambda> at 0x000002279DCE5488>)
在这里,在开头的括号前面加一个分号(嵌套的没有)是通常表示 cons list 的方法。与 cons 写法不一样,分号写法不包括最后的空列表。(如果一个空列表以成员的身份出现,则显示。比如cons(null, null)
的分号记法是 '( () )
)
cons list 和普通列表的互换
虽然意义不大,但这是需要的。因为 Py 不是 Lisp 语言,在非常需要普通列表的地方 cons
一个第三者是办不到的。
转换到普通列表(的方法):
def to_vector(self, store: list) -> list:def to_vector1(lst: cons):if not lst.isnull():store.append(lst.first())return to_vector1(lst.rest())return to_vector1(self)
需要一个空列表来储存内容,因为不是主要内容,不过多解释
普通列表转换到 cons list(的函数):
def vector_to_cons(vec: list) -> cons:length: int = len(vec)def vector_to_cons1(count: int) -> cons:if count == length:return cons()else:return cons(vec[count], vector_to_cons1(count+1))return vector_to_cons1(0)def lispList(*args) -> cons:return vector_to_cons(args)
通过这第三个函数就可以像这样来创建列表了:
# 相当于 cons(1, cons(2, cons(cons(2, cons(4, cons(null, null))), null)))
> y = lispList(1,2,lispList(2,4,lispList()))
> y.quasi_repr()
`(1, 2, (2, 4, ()))
> y.rest().quasi_repr()
`(2, (2, 4, ()))
把这样的列表作为参数传递给函数
也就是 apply
函数。在 Python 中,可以这样实现
def apply_to_sup(self, f: Callable):temp = list()self.to_vector(temp)return f(*temp)
但如果我们不想展开这个列表成普通的列表(一般情况下也不能),那就要了解一下函数了。
其实函数也和 cons list 差不多。首先是组合函数:
def compose(f: Callable, g: Callable) -> Callable:def fg(*args):return f(g(*args))return fg
先算出 g的结果,再自动当作 f 的函数。实际上在这个表达式里,嵌套的括号就已经有点列表的意思了,但还不够。参数列表我们在这里假装它是一个 cons list。
函数的居里化:
这样对于 f,我可以暂时先生成一个不依赖于 x 的函数,等到知道 x 的时候,在把它传送到 f'(y) 中。这样的特点是本来需要一次性完成的工作,现在可以分好几步。最后一个表达式的意思是,我生成了一个两个参数都固定的函数,只要这个函数一执行,他的结果是固定的。
下面这个函数可以把传入函数的第一个参数挂起(传入参数的最小参数个数是 2*,这样居里化之后的函数参数个数是 1):
def curry(f: Callable) -> Callable:'requires the argument count for f is at least 2'def g(arg2, *args_rest):def h(arg1):return f(arg1, arg2, *args_rest)return hreturn g
> def f(x, y, z): return x - y * z
> f(1,10, 2)
-19
> curry(f)(10, 2)(1)
-19
> curry(curry(f))(2)(10)(1)
-19# 生成一个加一函数
> add1 = curry(add)(1)
> add1(3)
4
可以注意到,直接传参数的顺序是 1,10,2,而在完全把参数剥离开之后,顺序颠倒了过来。这是因为在 curry 的时候,只能从最左边的变量开始剥。如果观察这函数的参数的话,会发现它和 cons list 的原理是一样的,我们把一个函数从复杂的形态(要传一大堆参数)扒皮成最原始的形态(单变量函数),相当于把一个很长的列表进行索引,到最后只剩下了那个空列表,此空列表就是这种直接就能运行的函数。
这样的话,如果想要给一个多变量函数传参数的话,就先把它的未知量这层皮剥掉,把我们的 cons list 作为皮换上去,这样未知量变成已知量,函数就能运行了。
这种换衣服的操作就是这样的:
def apply_to(self, f: Callable):if self.isnull():return f()def reduce_function(f1: Callable, n: int) -> Callable:if n == 1:return f1else:return reduce_function(curry(f1), n-1)def fill_args(f1: Callable, arg_lst_r: cons):if arg_lst_r.rest().isnull():return f1(arg_lst_r.first())else:return fill_args(f1(arg_lst_r.first()), arg_lst_r.rest())return fill_args(reduce_function(f, self.length()), self.reverse())
如果列表是空的,即代表函数不接受参数,那么直接返回他的执行结果。否则的话就把他扒成单变量函数**,再用我的参数列表(此时应该是倒序的)一层一层地穿上。
> lispList(1,10,2).apply_to(f)
-19
> lispList(1,2).apply_to(add)
3
这就是实现多参数函数的原理。
对于 map 和 折叠等的操作,看缘分有空再发。
从一个列表简洁地转换到另一个列表
介绍函数的部分算是一个小插曲,以下的部分更重要一些。
- Filter
筛选函数的工作原理是:对于每一对括号,如果第一项是符合条件的,那么我们就保留这一项,接着嵌套剩余的部分,如果不符合条件,就丢掉这一项再继续。比如在 cons(1, cons(2, cons(3, null)))
中,如果只要奇数,那么遇到 1 的时候保留,遇到 2 的时候跳过,此时的列表解析成 cons(1, cons(3, ...))
。此函数相当简单:
def lispFilter(cond: Callable, lst: cons) -> cons:if lst.isnull():return lstelif cond(lst.first()):return cons(lst.first(), lispFilter(cond, lst.rest()))else:return lispFilter(cond, lst.rest())
最后的两个语句就是不一样的地方。如果是常规列表,可能是这样的:if cond(array[i]) keep array[i,i+1,...] else keep array[i+1,...] loop
。举个例子
> def isodd(x): return x % 2 == 1
> res = lispFilter(isodd, lispList(-1,0,1,2,3,4,5))
> print(res.quasi_repr())
`(-1, 1, 3, 5)
> res = lispFilter(cons.isnull, lispList(null, null, lispList(2,3), lispList(null), lispList()))
> print(res.quasi_repr())
`((), (), ()) # 思考下为什么是三个空列表?
- Map
对一个列表进行一些映射是非常常规的操作。在 cons list 里实现还是用一个新的项去替换原来的最外层的。
def lispMap1(f: Callable, lst: cons) -> cons:if lst.isnull():return cons()else:return cons(f(lst.first()),lispMap1(f, lst.rest()))
注意这两个函数的返回值都是列表。
> res = lispMap1(add1, lispList(1,2,3,4,5,-1))
> print(res.quasi_repr())
`(2, 3, 4, 5, 6, 0)
列表的折叠
- 右折叠 (foldr)
如果函数是右结合的,那么其可以右折叠。一个表达式里最右面的和倒数第二个元素可以结合成一个新的元素,然后作为一个整体再和倒数第三个结合。怎么理解呢?想象一下如果我不写括号来写出一个列表:
cons 1 cons 2 cons 3 cons 4 cons 5 null
根据规定,一对括号如果非空里必须有两项,所以只能让两个元素结合,其次,第二个必须是列表,这样我们只能把最右边的 5 和 空集结合成一个新项。
(cons 1 cons 2) cons 3 cons 4 cons 5 null # 不对
cons 1 cons 2 cons 3 cons 4 (cons 5 null) # 对
cons 1 cons 2 cons 3 (cons 4 (cons 5 null)) # 对
所以实际上,列表的构造就是一个右折叠的实例。当然,这个表达式没法再简化了,但是,有可以继续简化的:
add 1 add 2 add 3 add 4 add 5 0
用右折叠的思维去想(这种前缀式函数是右结合的典范——好像我见过的大多数数学物件都是右结合的),故这行字就翻译成
(add 1 (add 2 (add 3 (add 4 (add 5 0)))))
或者是
add(1, add(2, add(3, add(4, add(5, 0)))))
先让5和0相加,结果再加4,结果再加3……
看这个表达式和
cons(1, cons(2, cons(3, cons(4, cons(5, null)))))
的相似度。因为彼此都是右结合的,因此可以类比:add
就是 cons
,而 0 就是 null
。在每一对括号里,第一项就是任意元素,而第二项就是所谓的 base case (终止条件)。右折叠故而可以这样实现:
def lispFoldr1(f: Callable, end, lst: cons):if lst.isnull():return endelse:return f(lst.first(), lispFoldr1(f, end, lst.rest()))
如果和文章开头的几个函数类比,会觉得很像。
# 列表的求和和求积
> lispFoldr(add, 0, lispList(1,2,3,4,5))
15
> lispFoldr(mul, 1, lispList(1,2,3,4,5))
120
右折叠函数所传入的第一个参数要接受两个变量,第一个变量称之为当前项,第二个称之为递归结果。对于这个 add
函数,准确的说要这么写: lambda x, rr: x + rr
。如果你看上面的类比例子,就可以看到 add(1, add(2, add(3, add(4, add(5, 0)))))
中, 1 可以看作是当前项,而 add(2, add(3, add(4, add(5, 0))))
就是递归结果。而 5 可以看成是当前项,那个 0 作为递归的中止项,也是递归结果。
这样说的话,以下的结果读者也都猜到了:
#仅仅遍历了一次而没做改变
> unchanged = lispFoldr(cons, null, lispList(1,2,3,4))
> print(unchanged.quasi_repr())
`(1, 2, 3, 4)
更为让人震惊的是,map
函数可以用 foldr
来实现:
def mapp(f: Callable, lst: cons) -> cons:return lispFoldr1(lambda x, rr: cons(f(x), rr), cons(), lst)
请问数组能做到吗?
- 左折叠 (foldl)
这部分其实挺坑,因为不同的语言的实现和原理都不同,并没有统一的标准,故略过。
(实际上左折叠和 cons list 背道而驰,这可能也是为什么 racket 要重新发明一遍它)
- 降价
当一个函数可以说是即能右折叠又可以左折叠的时候,而且结果不变,那么它就是具有交换性。这个时候它就可以自然地写成中缀式(比如 add(1,2)
和 1 + 2
),当然写成中缀式不一定就代表他就可交换了,不深究。这个时候左折叠和右折叠合并成一个,称之为 reduce
。
同时处理多个列表
比如,如果我们想比较两个列表是不是相等,那么它的代码会是这样:
def listsEqual1(lst1: cons, lst2: cons) -> bool:if lst1.isnull() and lst2.isnull():return Trueelif (lst1.isnull() and not lst2.isnull()) or (not lst1.isnull() and lst2.isnull()):return Falseelif lst1.first() == lst2.first():return True and listsEqual1(lst1.rest(), lst2.rest())else:return False
不难理解。如果两个列表都空,那么返回是,这就是 base case。如果其中一个空,另一个不空,那么他们肯定不一样,如果都不空的话,就再比较这两项。
> listsEqual1(lispList(), lispList())
True
> listsEqual1(lispList(1,2,3), lispList(1,2))
False
> listsEqual1(lispList(1,2,3), lispList(1,2,3))
True
这样的例子有很多,比如合并列表,以及点乘:
- 多变量 Map 函数
真正的 map
可以同时处理多个列表,然后返回一个新的处理完的列表。
> v = lispList(1,2,-1)
> w = lispList(-2,0,7)
# 每一项都相乘,返回新的列表
> res = lispMap(mul, v, w)
> print(res.quasi_repr())
`(-2, 0, -7)
# 然后再加起来就是
> lispFoldr1(add, 0, res)
-9
我刚才写的盗版 map1
只能处理一个列表,下面的这些玩意就能支持多列表了:
def lispMap(f: Callable, *varlists: cons) -> cons:def map1(f1, alst: cons) -> cons:if alst.isnull():return cons()else:return cons(f1(alst.first()),map1(f1, alst.rest()))if len(varlists) == 1:return map1(f, varlists[0])def var_map(lists: cons):if lists.first().isnull():return cons()else:return cons(map1(cons.first, lists).apply_to(f),var_map(map1(cons.rest, lists)))return var_map(vector_to_cons(varlists)) # 只能传入 cons list,先忽略掉 py 本身数组的特性
仍然需要注意,在这里 cons.first
等不带括号的是方法函数的本身。这个大函数的原理是把传入的参数列表中 ,每个列表的第一项提出来给函数 f
当参数,然后返回组成新列表的第一项。
这个函数默认所有列表等长。
所以在数组的转置(map(list, zip(*matrix))
)之外,cons list 的转置方法如下:
> matrix = lispList(lispList(1,2,3,4),lispList(33,4,-7,0),lispList(2,0,0,-9),lispList(0,0,0,0))
> print(matrix.quasi_repr())
`((1, 2, 3, 4), (33, 4, -7, 0), (2, 0, 0, -9), (0, 0, 0, 0))
> matrixT = cons(lispList, matrix).apply_to(lispMap)
> print(matrixT.quasi_repr())
`((1, 33, 2, 0), (2, 4, 0, 0), (3, -7, 0, 0), (4, 0, -9, 0))
- 多变量 foldr
真正的右折叠能同时处理多个列表。
# 给定一堆列表,把列表的所有值相加,列表长度要相同
> lispFoldr(lambda x,y,z,t,rr: x+y+z+t+rr, 0,lispList(1,3,5),lispList(4,4,-5),lispList(4,4,-5),lispList(4,4,-5))
18
函数的真正的定义也是比较群魔乱舞:
def lispFoldr(f: Callable, end, *varlists: cons):def mapadd(f1: Callable, lst: cons, last) -> cons:if lst.isnull():return cons(last, cons())else:return cons(f1(lst.first()), mapadd(f1, lst.rest(), last))def var_foldr(lists: cons):if lists.first().isnull():return endelse:return mapadd(cons.first, lists, var_foldr(lispMap(cons.rest, lists)) ).apply_to(f)return var_foldr(vector_to_cons(varlists))
另一个例子:
# 一个列表叫变量名,一个列表叫变量值,我想让对应变量名的值加一
> def incr_args(argn, argv, this):return lispFoldr(lambda x, y, rr: cons(add1(x), rr) if y == this else cons(x, rr),null, argv, argn)
> res = incr_args(lispList('x','y','z'), lispList(1, 5, 2.3),'y')
> print(res.quasi_repr())
`(1, 6, 2.3)
如果看完了觉得还行,建议关注专栏。
---------------------------------------------------------------------
*,**:其实也可以把一个单变量函数脱成一个无变量函数:
def curry1(f: Callable) -> Callable:'requires the argument count for f is 1'def g():def h(arg1):return f(arg1)return hreturn g
curry1(curry(curry(f)))()(2)(10)(1) # ==> -19
只不过在目前用处不是很大。
lisp封装成vla函数_Lisp List 和函数式编程 (in Python)相关推荐
- lisp封装成vla函数_[良心教程]分享最新最实用的按键精灵封装函数
金猪脚本(原飞猪脚本)以按键精灵教学为主,涉及UiBot,Python,Lua等脚本编程语言,教学包括全自动办公脚本,游戏辅助脚本,引流脚本,网页脚本,安卓脚本,IOS脚本,注册脚本,点赞脚本,阅读脚 ...
- 封装成vla函数_不知道怎么封装代码?看看这几种设计模式吧!
为什么要封装代码? 我们经常听说:"写代码要有良好的封装,要高内聚,低耦合".那怎样才算良好的封装,我们为什么要封装呢?其实封装有这样几个好处: 封装好的代码,内部变量不会污染外部 ...
- 封装成vla函数_第四章:Python之函数
第一节:函数入门与定义函数 理解函数 所谓函数,就是为一段实现特定功能的代码"取"个名字,以后即可通过该名字来执行(调用)这段代码 从逻辑上看,函数相当于一个黑匣子 定义函数的语法 ...
- 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]
本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ 作者:杨昆 [编写高质量函数系列]中, < ...
- es6 filter函数的用法_Python 函数式编程指北,不只是面向对象哦!超级详细!
Python 函数式编程指北, 不只是面向对象哦 了解在Python中如何使用 lambda, map, filter 和 reduce 函数来转换数据结构 Photo by Markus Spisk ...
- less 函数_Python中的函数式编程教程,学会用一行代码搞定所有内容
前言 在本文中,您将了解什么是函数范型,以及如何在Python中使用函数式编程.在Python中,函数式编程中的map和filter可以做与列表相同的事情.这打破了Python的禅宗规则之一,因此函数 ...
- Python 函数式编程,Python中内置的高阶函数:map()、reduce()、filter()与sorted(),Python中返回函数
函数式编程 是一种编程范式,比函数更高层次的抽象. 函数式编程将计算视为函数而非指令. 纯函数式编程:不需要变量,没有副作用,测试简单. 支持高阶函数,代码简洁. Python 支持的函数式编程 不是 ...
- python内置高阶函数求导_Python——函数式编程、高阶函数和内置函数,及
Python--函数式编程.高阶函数及内置函数 函数式编程 一.不可变数据:不用变量保存状态不修改变量 二.第一类对象:函数即"变量" 1.函数名可以当做参数传递 2.返回值可以是 ...
- lisp封装为vlx方法_lisp 创建自定义菜单行数过多为什么不能打包成VLX
大师们.LISP创建菜单行数过多后怎么不能打包. ; 编译终止 ; 错误: 编译器发现致命错误 "菜单工具.lsp" _$ ;;; 说明:用lisp创建菜单 ;;; 作者:lang ...
最新文章
- SSM整合Shiro 身份验证及密码加密简单实现
- spring连接jdbc_在Spring JDBC中添加C3PO连接池
- C++primer第八章 IO库 8.3string流
- kibana-7.15.2 一分钟下载、安装、部署 linux
- python找钱_python 递归 找零钱
- Telephone Linse(POJ-3662)
- python读json文件太大github_GitHub上最火的开源项目是啥|JSON文件实战处理
- sql 只要一个字段相同则只显示一条数据_数据库
- 行内元素多出的空白文本节点的解决方法
- 论如何优雅的处理回文串 - 回文自动机详解.
- Windows SharePoint Services Search和Office SharePoint Server Search的区别
- 驱动精灵w8ndows xp sp2,爱普生Epson TM-U220打印机驱动官方正式版下载,适用于winxp,winvista,win7,win8,win10-驱动精灵...
- Kali Linux 1.0 新手折腾笔记(2013.3.21更新)
- XS9932A/XS9932B 4 通道模拟复合视频解码芯片方案
- 520送什么蓝牙耳机好?高颜值高性价比的无线蓝牙耳机推荐
- 股指期货开户的保证金和手续费是多少?怎么计算?
- 《异常检测——从经典算法到深度学习》5 Opprentice——异常检测经典算法最终篇
- Android LruCache和DiskLruCache相结合打造图片加载框架(仿微信图片选择,照片墙)
- 浅谈马尔可夫决策过程(一)
- Python生成英文词云图