/**
* PHP字符串“异或”算法
* param array key
* @param Request $request
* @return mixed|string|void
*/
public function setSecretKey(Request $request){
$keyArr = $request->input('key');
if(!is_array($keyArr) || empty($keyArr))
return;
foreach ($keyArr as $v){
if(empty($v) || (strlen($v) != 32)){
return;
}
}
if(count($keyArr) == 1)
return $keyArr[0];
$arrLength = count($keyArr);
$initKey = "00000000000000000000000000000000";
$initKeyArr = str_split($initKey);
for($i = 0;$i < $arrLength;$i++){
$newKey = '';
for($j = 0;$j < strlen($keyArr[$i]);$j++){
$str = '';
$tmpArr = str_split($keyArr[$i]);
$tmpA = str_pad(base_convert($tmpArr[$j],16,2),4,0,STR_PAD_LEFT);
$tmpB = str_pad(base_convert($initKeyArr[$j],16,2),4,0,STR_PAD_LEFT);
for($k=0;$k<strlen($tmpA);$k++){
$str .=(intval($tmpA[$k]) ^ intval($tmpB[$k]));
}
$tmpOneKey = strtoupper(base_convert($str,2,16));
unset($str);
$newKey .= $tmpOneKey;
}
unset($initKeyArr);
$initKeyArr = str_split($newKey);
}
return join($initKeyArr);
}
主要是聊基础算法知识和代码题。
算法简单,而且效率高,每次可以操作8个字节的数据,加密解密的KEY为16字节,即包含4个int数据的int型数组,加密轮数应为8的倍数,一般比较常用的轮数为64,32,16,QQ原来就是用TEA16来还原密码的. TEA算法 核心为: PHP部分代码非我原创,大家可以了解一下这方面的知识 上面的是TEA的算法,XTEA的算法为: #include
又到安利Python的时间, 最终代码不超过30行(优化前),加上优化也不过40行。
第一步. 构造Trie(用dict登记结点信息和维持子结点集合):
-- 思路:对词典中的每个单词,逐词逐字母拓展Trie,单词完结处的结点用None标识。
def make_trie(words):
trie = {}
for word in words:
t = trie
for c in word:
if c not in t: t[c] = {}
t = t[c]
t[None] = None
return trie
第二步. 容错查找(容错数为tol):
-- 思路:实质上是对Trie的深度优先搜索,每一步加深时就消耗目标词的一个字母。当搜索到达某个结点时,分为不消耗容错数和消耗容错数的情形,继续搜索直到目标词为空。搜索过程中,用path记录搜索路径,该路径即为一个词典中存在的词,作为纠错的参考。
-- 最终结果即为诸多搜索停止位置的结点路径的并集。
def check_fuzzy(trie, word, path='', tol=1):
if word == '':
return {path} if None in trie else set()
else:
p0 = set()
if word[0] in trie:
p0 = check_fuzzy(trie[word[0]], word[1:], path+word[0], tol)
p1 = set()
if tol > 0:
for k in trie:
if k is not None and k != word[0]:
p1.update(check_fuzzy(trie[k], word[1:], path+k, tol-1))
return p0 | p1
简单测试代码 ------
构造Trie:
words = ['hello', 'hela', 'dome']
t = make_trie(words)
In [11]: t
Out[11]:
{'d': {'o': {'m': {'e': {'$': {}}}}},
'h': {'e': {'l': {'a': {'$': {}}, 'l': {'o': {'$': {}}}}}}}
容错查找:
In [50]: check_fuzzy(t, 'hellu', tol=0)
Out[50]: {}
In [51]: check_fuzzy(t, 'hellu', tol=1)
Out[51]: {'hello'}
In [52]: check_fuzzy(t, 'healu', tol=1)
Out[52]: {}
In [53]: check_fuzzy(t, 'healu', tol=2)
Out[53]: {'hello'}
似乎靠谱~
---------------------------分--割--线--------------------------------------
以上是基于Trie的approach,另外的approach可以参看@黄振童鞋推荐Peter Norvig即P神的How to Write a Spelling Corrector
虽然我已有意无意模仿P神的代码风格,但每次看到P神的源码还是立马跪...
话说word[1:]这种表达方式其实是有渊源的,相信有的童鞋对(cdr word)早已烂熟于心...(呵呵
------------------------分-----割-----线-----二--------------------------------------
回归正题.....有童鞋说可不可以增加新的容错条件,比如增删字母,我大致对v2方法作了点拓展,得到下面的v3版本。
拓展的关键在于递归的终止,即每一次递归调用必须对参数进行有效缩减,要么是参数word,要么是参数tol~
def check_fuzzy(trie, word, path='', tol=1):
if tol < 0:
return set()
elif word == '':
results = set()
if None in trie:
results.add(path)
# 增加词尾字母
for k in trie:
if k is not None:
results |= check_fuzzy(trie[k], '', path+k, tol-1)
return results
else:
results = set()
# 首字母匹配
if word[0] in trie:
results |= check_fuzzy(trie[word[0]], word[1:], path + word[0], tol)
# 分情形继续搜索(相当于保留待探索的回溯分支)
for k in trie:
if k is not None and k != word[0]:
# 用可能正确的字母置换首字母
results |= check_fuzzy(trie[k], word[1:], path+k, tol-1)
# 插入可能正确的字母作为首字母
results |= check_fuzzy(trie[k], word, path+k, tol-1)
# 跳过余词首字母
results |= check_fuzzy(trie, word[1:], path, tol-1)
# 交换原词头两个字母
if len(word) > 1:
results |= check_fuzzy(trie, word[1]+word[0]+word[2:], path, tol-1)
return results
好像还是没有过30行……注释不算(
本答案的算法只在追求极致简洁的表达,概括问题的大致思路。至于实际应用的话可能需要很多Adaption和Tuning,包括基于统计和学习得到一些词语校正的bias。我猜测这些拓展都可以反映到Trie的结点构造上面,比如在结点处附加一个概率值,通过这个概率值来影响搜索倾向;也可能反映到更多的搜索分支的控制参数上面,比如增加一些更有脑洞的搜索分支。(更细节的问题这里就不深入了逃
----------------------------------分-割-线-三----------------------------------------
童鞋们可能会关心时间和空间复杂度的问题,因为上述这种优(cu)雅(bao)的写法会导致产生的集合对象呈指数级增加,集合的合并操作时间也指数级增加,还使得gc不堪重负。而且,我们并不希望搜索算法一下就把所有结果枚举出来(消耗的时间亦太昂贵),有可能我们只需要搜索结果的集合中前三个结果,如果不满意再搜索三个,诸如此类...
那肿么办呢?................是时候祭出yield小魔杖了゚ ∀゚)ノ
下述版本姑且称之为lazy,看上去和v3很像(其实它俩在语义上是几乎等同的
def check_lazy(trie, word, path='', tol=1):
if tol < 0:
pass
elif word == '':
if None in trie:
yield path
# 增加词尾字母
for k in trie:
if k is not None:
yield from check_lazy(trie[k], '', path + k, tol - 1)
else:
if word[0] in trie:
# 首字母匹配成功
yield from check_lazy(trie[word[0]], word[1:], path+word[0], tol)
# 分情形继续搜索(相当于保留待探索的回溯分支)
for k in trie:
if k is not None and k != word[0]:
# 用可能正确的字母置换首字母
yield from check_lazy(trie[k], word[1:], path+k, tol-1)
# 插入可能正确的字母作为首字母
yield from check_lazy(trie[k], word, path+k, tol-1)
# 跳过余词首字母
yield from check_lazy(trie, word[1:], path, tol-1)
# 交换原词头两个字母
if len(word) > 1:
yield from check_lazy(trie, word[1]+word[0]+word[2:], path, tol-1)
不借助任何容器对象,我们近乎声明式地使用递归子序列拼接成了一个序列。
[新手注释] yield是什么意思呢?就是程序暂停在这里了,返回给你一个结果,然后当你调用next的时候,它从暂停的位置继续走,直到有下个结果然后再暂停。要理解yield,你得先理解yield... Nonono,你得先理解iter函数和next函数,然后再深入理解for循环,具体内容童鞋们可以看官方文档。而yield from x即相当于for y in x: yield y。
给刚认识yield的童鞋一个小科普,顺便回忆一下组合数C(n,m)的定义即
C(n, m) = C(n-1, m-1) + C(n-1, m)
如果我们把C视为根据n和m确定的集合,加号视为并集,利用下面这个generator我们可以懒惰地逐步获取所有组合元素:
def combinations(seq, m):
if m > len(seq):
raise ValueError('Cannot choose more than sequence has.')
elif m == 0:
yield ()
elif m == len(seq):
yield tuple(seq)
else:
for p in combinations(seq[1:], m-1):
yield (seq[0],) + p
yield from combinations(seq[1:], m)
for combi in combinations('abcde', 2):
print(combi)
可以看到,generator结构精准地反映了集合运算的特征,而且蕴含了对元素进行映射的逻辑,可读性非常强。
OK,代码到此为止。利用next函数,我们可以懒惰地获取查找结果。
In [54]: words = ['hell', 'hello', 'hela', 'helmut', 'dome']
In [55]: t = make_trie(words)
In [57]: c = check_lazy(t, 'hell')
In [58]: next(c)
Out[58]: 'hell'
In [59]: next(c)
Out[59]: 'hello'
In [60]: next(c)
Out[60]: 'hela'
话说回来,lazy的一个问题在于我们不能提前预测并剔除重复的元素。你可以采用一个小利器decorator,修饰一个generator,保证结果不重复。
from functools import wraps
def uniq(func):
@wraps(func)
def _func(*a, **kw):
seen = set()
it = func(*a, **kw)
while 1:
x = next(it)
if x not in seen:
yield x
seen.add(x)
return _func
这个url打开的文件包含常用英语词汇,可以用来测试代码:
In [10]: import urllib
In [11]: f = urllib.request.urlopen("https://raw.githubusercontent.com/eneko/data-repository/master/data/words.txt")
# 去除换行符
In [12]: t = make_trie(line.decode().strip() for line in f.readlines())
In [13]: f.close()
----------------------分-割-线-四-----------------------------
最后的最后,Python中递归是很昂贵的,但是递归的优势在于描述问题。为了追求极致性能,我们可以把递归转成迭代,把去除重复的逻辑直接代入进来,于是有了这个v4版本:
from collections import deque
def check_iter(trie, word, tol=1):
seen = set()
q = deque([(trie, word, '', tol)])
while q:
trie, word, path, tol = q.popleft()
if word == '':
if None in trie:
if path not in seen:
seen.add(path)
yield path
if tol > 0:
for k in trie:
if k is not None:
q.appendleft((trie[k], '', path+k, tol-1))
else:
if word[0] in trie:
q.appendleft((trie[word[0]], word[1:], path+word[0], tol))
if tol > 0:
for k in trie.keys():
if k is not None and k != word[0]:
q.append((trie[k], word[1:], path+k, tol-1))
q.append((trie[k], word, path+k, tol-1))
q.append((trie, word[1:], path, tol-1))
if len(word) > 1:
q.append((trie, word[1]+word[0]+word[2:], path, tol-1))
可以看到,转为迭代方式后我们仍然可以最大程度保留递归风格的程序形状,但也提供了更强的灵活性(对于递归,相当于我们只能用栈来实现这个q)。基于这种迭代程序的结构,如果你有词频数据,可以用该数据维持一个最优堆q,甚至可以是根据上下文自动调整词频的动态堆,维持高频词汇在堆顶,为词语修正节省不少性能。这里就不深入了。
【可选的一步】我们在对单词进行纠正的时候往往倾向于认为首字母是无误的,利用这个现象可以减轻不少搜索压力,花费的时间可以少数倍。
def check_head_fixed(trie, word, tol=1):
for p in check_lazy(trie[word[0]], word[1:], tol=tol):
yield word[0] + p
最终我们简单地benchmark一下:
In [18]: list(check_head_fixed(trie, 'misella', tol=2))
Out[18]:
['micellar',
'malella',
'mesilla',
'morella',
'mysell',
'micelle',
'milla',
'misally',
'mistell',
'miserly']
In [19]: %timeit list(check_head_fixed(trie, 'misella', tol=2))
1.52 ms ± 2.84 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
在Win10的i7上可以在两毫秒左右返回所有结果,可以说令人满意。
在当今数字化时代,大数据已成为各行各业不可忽视的重要资产。对于数据科学家和数据分析师来说,掌握大数据算法是至关重要的技能之一。随着数据量的不断增长和复杂性的提升,大数据算法的应用范围也越来越广泛。
大数据算法是指为处理大规模数据而设计的一组算法和技术。在处理海量数据时,传统的算法可能无法有效地运行,因此需要专门针对大数据量级和特点设计的算法来进行处理。
大数据算法的重要性在于它可以帮助企业从海量数据中提取出有用的信息、模式和见解,为决策提供支持。通过运用大数据算法,企业可以更好地理解客户需求、优化产品设计、改进营销策略,从而提升竞争力。
下面列举了一些常见的大数据算法面试题,希望能够帮助准备面试的同学更好地理解和掌握相关知识:
为了更好地准备大数据算法面试,以下是一些建议:
大数据算法在当今信息爆炸的时代扮演着至关重要的角色,对于从事数据分析和数据科学相关工作的人员来说,掌握大数据算法是必备的技能之一。通过不断学习、实践和应用,相信每个人都可以在大数据算法领域取得优异的成绩。
PHP一直是Web开发领域中备受推崇的编程语言之一,许多公司在招聘开发人员时都会考察候选人的PHP技能。因此,掌握一些常见的PHP面试题是非常重要的。无论您是准备面试,还是想进一步加深对PHP的理解,本文将为您提供一些从初级到高级的PHP面试题,帮助您在面试中脱颖而出。
1. 什么是PHP? PHP即“Hypertext Preprocessor”的缩写,是一种开源的服务器端脚本语言,适用于Web开发和可嵌入中使用。PHP脚本在服务器端运行,生成HTML输出到客户端浏览器。
2. PHP的特点有哪些? PHP具有许多特点,包括开源、跨平台、易学易用、功能强大、支持多种数据库等。PHP的灵活性和扩展性使其成为许多开发人员的首选语言之一。
3. 如何在PHP中输出文本?
在PHP中,您可以使用echo或print语句来输出文本。例如,您可以使用echo "Hello, World!";
来输出“Hello, World!”。
1. 什么是PHP中的变量作用域? 在PHP中,变量的作用域指的是变量在脚本中可见的区域。PHP具有四种不同的作用域:局部作用域、全局作用域、静态作用域和超全局作用域。
2. 如何包含一个文件到PHP页面中? 您可以使用include或require语句包含一个文件到PHP页面中。区别在于如果文件不存在,include会发出警告并继续执行脚本,而require会发出致命错误并停止脚本执行。
3. 什么是PHP中的SESSION? SESSION是一种将用户信息存储在服务器上的方法,在用户访问您的站点时创建。PHP中的SESSION通过一个唯一的SESSION ID来识别每个用户,并将数据存储在服务器的内存中。
1. 什么是PHP的自动加载? PHP的自动加载功能允许您在类被实例化或类被调用时自动加载类文件。这样可以提高代码的模块化和灵活性,避免手动包含大量的类文件。
2. 什么是PHP中的命名空间? PHP的命名空间是一种将类、函数和常量组织到更合理和更具可读性的结构中的方式。通过命名空间,可以避免命名冲突,提高代码的可维护性。
3. 什么是PHP中的trait? Trait是PHP中一种代码复用的机制,它类似于类的一个部分,可以在不同类之间复用方法集。Trait提供了一种更优雅的代码组织方式,避免多重继承的复杂性。
通过以上PHP面试题的介绍,相信您对PHP的知识有了更深入的了解,也为您在面试中展现出色的机会提供了帮助。继续学习和提升自己的PHP技能,相信您一定能在职业道路上获得更多的成就!
在软件开发领域,掌握算法是每个程序员成为顶尖开发人员所必备的技能之一。无论你是初学者还是有经验的PHP开发人员,提升自己的算法技能都是一个持续学习和发展的过程。为了帮助你在PHP编程中更好地应用和理解算法,今天我们要介绍一些很有用的PHP算法题库。
1. CodeSignal
CodeSignal 是一个面向开发人员的技能评估平台,它提供了大量的编程题目和挑战。你可以在这个平台上找到很多关于PHP算法的题目,并通过解答这些题目来提升自己的编程能力。CodeSignal 的题目涵盖了各个难度级别,从入门到高级,适合不同水平的开发人员。此外,CodeSignal 还提供了社区功能,你可以与其他开发人员交流和分享解题思路。
2. LeetCode
LeetCode 是一个非常流行的在线编程平台,它提供了大量的算法题目。你可以使用PHP解答这些题目,并在上面的讨论区与其他开发人员交流和学习。LeetCode 的题库非常全面,覆盖了各种不同类型的算法问题,包括数组、字符串、链表、树等等。解答这些问题可以帮助你更好地理解PHP的数据结构和算法。
3. HackerRank
HackerRank 是一个技术面试和编程竞赛平台,它也提供了许多PHP算法题目。通过解答这些题目,你可以进行技术练习,并将自己的解答与其他开发人员进行比较。HackerRank 的题库涵盖了各个难度级别,从入门到高级,适合不同水平的开发人员。
4. Project Euler
Project Euler 是一个以数学和计算为主题的编程挑战平台。尽管它的题目不是专门为PHP开发人员设计的,但通过解答这些题目,你可以提升你的编程技能,并且更好地理解算法的应用。Project Euler 的题目涵盖了各种数学问题,其中很多问题可以用PHP解决。
5. Codewars
Codewars 是一个以编程挑战为主题的平台,它提供了大量的算法题目。你可以选择不同级别的挑战,并通过解答这些题目来提升自己的编程能力。Codewars 的题目包括了许多与PHP相关的问题,可以帮助你更好地理解PHP的特性和语法。
6. Topcoder
Topcoder 是一个专业的算法竞赛平台,它提供了各种不同类型的算法题目。虽然 Topcoder 的题目不是专门为PHP开发人员设计的,但通过解答这些题目,你可以提升你的算法思维和解决问题的能力。Topcoder 的题目难度很高,适合有一定编程经验的开发人员。
以上是一些非常有用的PHP算法题库,通过解答这些题目,你可以提升自己的编程技能和算法思维。不论你是初学者还是有经验的开发人员,挑战不同难度级别的题目都能帮助你不断进步。如果你想在PHP编程中更加高效和灵活地应用算法,那么这些题库将是你的良师益友。加油!
在计算机科学中,算法是解决问题的步骤和方法的描述,它是解决问题的有效工具。
对于那些使用PHP编程语言的开发人员来说,了解和应用各种算法是提高代码质量和性能的关键。本文将介绍一些与PHP算法相关的书籍,帮助你深入理解算法并提升自己的编程技能。
《算法导论》是由Thomas H. Cormen等人编写的经典教材,它详尽地介绍了各种常见的算法和数据结构。这本书对于计算机科学专业的学生来说非常重要,无论是入门还是进阶,都能从中受益匪浅。
利用语言书写代码时,掌握一些高效的算法可以极大地提升网页的性能。如何快速排序、查找最短路径、优化搜索算法等等,这些内容都可以在《算法导论》中找到详细解释。不仅如此,书中的练习题和示例代码也让你有机会实际动手应用这些算法。
对于初学者或对算法感到困惑的开发人员来说,《算法图解》是一个很好的起点。这本书以图解的方式介绍了常见的算法和数据结构,用简单明了的语言解释复杂的概念。
PHP语言的特点是简洁易懂,结合《算法图解》一书,你可以更深入地理解和应用各种算法。书中的示例代码使用PHP语言编写,方便实践和理解算法的运行过程。
《算法笔记》是国内著名的算法教材,深受学生和开发人员的喜爱。它的特点是通俗易懂,注重算法的实际应用。这本书以PHP语言为例,详细讲解了常用的算法设计思想和解题思路。
PHP算法的学习没有固定的先后顺序,因此《算法笔记》适合初学者和有一定编程基础的人阅读。书中的例子丰富多样,通过实际案例分析,帮助读者理解和掌握不同类型的算法。
如果你希望通过实践来学习PHP算法与数据结构,那么《PHP算法与数据结构实战教程》是一个不错的选择。本书重点关注PHP语言中的常用算法和数据结构的实际应用。
在该书中,你将学习到如何使用PHP编写二分搜索算法、堆排序算法、动态规划算法等等。此外,书中还介绍了PHP中常用的数据结构,如链表、栈、队列等,并通过实战示例展示其在实际项目中的应用。
虽然不是严格意义上的算法书籍,但《PHP设计模式与最佳实践》对于PHP开发人员来说是一本非常有价值的书。设计模式是一种解决问题的方法,它能够组织代码,提高可读性和可维护性。
在PHP编程中,合理使用设计模式可以使代码更加优雅且易于维护。《PHP设计模式与最佳实践》一书通过实例介绍了常用的设计模式,并结合实际项目示例说明了它们的应用场景。
掌握设计模式有助于你在PHP编程中更好地组织代码,提高代码的可重用性和可扩展性,进而在实际应用中实现高效的算法。
无论你是PHP初学者还是经验丰富的开发人员,理解和应用不同的算法都是提高自己的编程水平的关键。通过阅读上述推荐的书籍,你将为自己打下坚实的算法基础,更好地应对PHP编程中遇到的各种挑战。
按数量级递增排列,常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3)
复制代码 代码如下:
//二分查找O(log2n)
function erfen($a,$l,$h,$f){
if($l >$h){ return false;}
$m = intval(($l+$h)/2);
if ($a[$m] == $f){
return $m;
}elseif ($f < $a[$m]){
return erfen($a, $l, $m-1, $f);
}else{
return erfen($a, $m+1, $h, $f);
}
}
$a = array(1,12,23,67,88,100);
var_dump(erfen($a,0,5,1));
//遍历树O(log2n)
function bianli($p){
$a = array();
foreach (glob($p.'/*') as $f){
if(is_dir($f)){
$a = array_merge($a,bianli($f));
}else{
$a[] = $f;
}
}
return $a;
}
//阶乘O(log2n)
function jc($n){
if($n<=1){
return 1;
}else{
return $n*jc($n-1);
}
}
//快速查找 O(n *log2(n))
function kuaisu($a){
$c = count($a);
if($c <= 1){return $a;}
$l = $r = array();
for ($i=1;$i<$c;$i++){
if($a[$i] < $a[0]){
$l[] = $a[$i];
}else{
$r[] = $a[$i];
}
}
$l = kuaisu($l);
$r = kuaisu($r);
return array_merge($l,array($a[0]),$r);
}
//插入排序 O(N*N)
function charu($a){
$c = count($a);
for($i=1;$i<$c;$i++){
$t = $a[$i];
for($j=$i;$j>0 && $a[$j-1]>$t;$j--){
$a[$j] = $a[$j-1];
}
$a[$j] = $t;
}
return $a;
}
//选择排序O(N*N)
function xuanze($a){
$c = count($a);
for($i=0;$i<$c;$i++){
for ($j=$i+1;$j<$c;$j++){
if($a[$i]>$a[$j]){
$t = $a[$j];
$a[$j] = $a[$i];
$a[$i] = $t;
}
}
}
return $a;
}
//冒泡排序 O(N*N)
function maopao($a){
$c = count($a);
for($i=0;$i<$c;$i++){
for ($j=$c-1;$j>$i;$j--){
if($a[$j] < $a[$j-1]){
$t = $a[$j-1];
$a[$j-1] = $a[$j];
$a[$j] = $t;
}
}
}
return $a;
}
复制代码 代码如下:
/**
* 排列组合
* 采用二进制方法进行组合的选择,如表示5选3时,只需有3位为1就可以了,所以可得到的组合是 01101 11100 00111 10011 01110等10种组合
*
* @param 需要排列的数组 $arr
* @param 最小个数 $min_size
* @return 满足条件的新数组组合
*/
function plzh($arr,$size=5) {
$len = count($arr);
$max = pow(2,$len);
$min = pow(2,$size)-1;
$r_arr = array();
for ($i=$min; $i<$max; $i++){
$count = 0;
$t_arr = array();
for ($j=0; $j<$len; $j++){
$a = pow(2, $j);
$t = $i&$a;
if($t == $a){
$t_arr[] = $arr[$j];
$count++;
}
}
if($count == $size){
$r_arr[] = $t_arr;
}
}
return $r_arr;
}
$pl = pl(array(1,2,3,4,5,6,7),5);
var_dump($pl);