年轻创业者,卖产品时请绕开这些错误

你应该知道这样的情况:大部分创业公司过不了几年就都倒闭了。

年轻创业者,卖产品时请绕开这些错误

现在让我们来分析下这个问题。为什么这些聪明能干、有好想法的创业者会失败?通常这会有多方面的因素,比如创始人之间的冲突、资金问题、执行问题……

但是,企业失败的核心问题是其产品(或服务)卖不出去。

究其原因是大部分的创业者对市场营销都知之甚少。你可能对你的产品或服务非常有激情,领导着一帮很聪明的人,网站设计得也很出色……但是,如果你对市场和销售不在行,你的结果就是失败。很抱歉告诉你这个残酷的事实。

如果你要为自己辩解“我在大学里选修了营销课程,我的成绩还不错”什么的,还是省省吧。

我大学学的就是营销专业,还拿到了MBA学位,而且两所学校都是名牌大学,当我开始自己创业的时候,我才发现自己错的离谱。

我很快发现,创业时真实的市场营销工作与我在课堂上所学到的有天壤之别。商学院所教授的营销知识都是针对财富500强那样的企业,很难和真实的用户接触,而且那些企业在面对线上市场时落后了好几年。如果你要建立一家成功的公司,你就要努力学习、了解“真实的营销世界”。参加在线课程,多读书,多到外面走走看看,别闭门造车,请经验丰富的人多指导你。

如果你能做到这些,你的前途是无量的。你会获得让你成功的一技之长,无论你做哪行。

下面,我就为大家分享下很多创业者现在很可能正在犯的三大错误,以及该怎样改正这些错误。

错误一:想让每个人都成为你的客户

很多创业者都是善良的好人,他们该被祝福,他们都希望能出一份力,让这个世界更美好,但这也是创业者们的魔咒。为什么?就是因为他们想用自己的产品或服务去帮助每个人,结果他们谁也帮不了。

目标市场过于宽泛,可能是大家最容易犯的营销错误。为了能从市场竞争中拼杀出来,你的目标人群要设定得尽量具体,可以说用户群越小越好。

你可能在想:“我放弃的那大部分人群怎么办?”

你要换个角度来看问题:“我要服务好我所关注的这部分人群,这是一件伟大的事。”聚焦在特定的人群上,你能为他们提供更好的服务,并且能为他们的生活带来深远影响。

现在让我们来具体分析下。当你描述你的典型客户时,你要尽可能详细地描述他,不仅包括人口学上的特征,还要有心理层面的分析。

举例来说,“25岁-40岁的男性”这样的描述太差劲了。“25岁-40岁的职业男性,生活在大城市中,热爱户外,为自己的业余爱好拼命挤时间,他们担心最好的时光在离他们而去……”应该这样详细地描述。这样的目标客户对你才有用,你才能从服务他们的过程中挣到钱。

解决办法:把你的典型客户的特征写下来,描述他们的沮丧、担心及渴望所在。尽量深挖,尽量个性化。你要切身去体会他们的感受,想他们所想。

错误二:缺乏用户参与

很多创业者爱展望这样的蓝图:“我们花了几个月的时间建了厂房、仓库,生产了不同寻常的产品,接着对外展示我们的成果,然后取得了巨大的成功。”这是很可笑的想法,现实世界可不是这样的。

准确预测消费者需要什么很难,但反过来说,有了想法不经用户确认就开始闷头干却很容易。当你失败后你才认识到没人需要你的产品。

在做市场工作时,你要让自己具备“科学家”式的头脑:总要做实验测试自己的想法,和你的测试对象(用户)尽可能多的接触。

要做到这些,调整好你的心态非常重要。你收到的很多反馈肯定会令你不好受,但不要太介意,不要被你的情绪所控制。

如果你的点子很烂,当然越早发现越好,而不是在你已经把很多钱投进去的六个月后。

测试,测试,测试,这是成功的市场营销大师们在做的,因为这很管用。

解决办法:在决定要着手创业前,你要花时间与潜在客户多交流,看他们需要什么(了解他们的喜怒哀乐所在)。然后尽快做一个测试版进行测试,再获取客户反馈。然后重新结合反馈重新调整,再测试。重复这样的操作直到客户真正满意,愿意为此付钱为止。

错误三:让用户觉得你不是在和他自己沟通

现在你已经确定了你的典型客户,而且也提供了他们正想要的产品/服务,接下来就是有效地和他们沟通。

关键是你和每位客户的沟通要像一对一的直接沟通那样,而不是一对多。两者的差别非常大,而这点常被忽视。

当你给一个人写邮件时,你可以谈论他的沮丧、担心及渴望。你会让他们感觉被你所理解。通常这种理解就是市场营销中的魔力。

Eben Pagan,我们这个时代最伟大的营销大师之一曾说过:“从你的顾客觉得自己被理解了的那时起,奇迹就发生了。”

当他们觉得你了解他们的困难,他们就会觉得你肯定也有解决问题的办法。这样一来,销售就是顺水推舟了,而且会很有趣。

解决办法:每当你在做营销材料时,直接写给你的典型客户。直接拿自己做实验,问问自己:“这是对我说的吗?我觉得被理解了吗?这吸引我吗?”如果答案不是肯定的Yes,就拿回去继续修改吧。

结语

因为营销工作涉及到心理学,而心理是很复杂的科学,所以敏锐和关注细节造就了很多世界一流的营销大师。

其实要赢得你的目标客户群并不像想象的那么难。只要把三个错误的解决方法做好了,你很快就能从你的竞争对手中脱颖而出。最后再重复一遍三个“诀窍”:

1.彻底弄清楚你的目标客户是谁;

2.经常与客户保持接触,做他们真正需要的产品;

3.以“一对一”的方式与客户交流,让他们感觉被你所理解。

作者:Phil Drolet是一位管理培训师,他帮助企业家提升营销技能,改进思维方式

迷惑消费者的11种定价策略

你走进一家星巴克,看到店里对同一杯咖啡提供两种套餐:第一种是加量33%不加价;第二种是原价降价33%。哪种更好?

“它们差不多一样!”,如果你会这样说,那么你就错了!

这两种套餐看起来好像相等,但是实际上,33%的降价相当于加量50%。数学计算时间:假设标准咖啡的价格是一美元三夸 脱(即每夸脱0.33美元),第一种套餐一美元可以买到4夸脱咖啡(即每夸脱0.25美元),第二种66美分可以买到3夸脱咖啡(即每夸脱0.22美 元)。

结果:免费得到多余的东西比得到同样的东西、花钱更少,感觉好点。对这一简单事实的应用却是极广的。卖燕麦么?别谈什么降价,讲讲盒子大了多少!卖车?省省大谈MPG转换的口水吧,谈谈能多跑多少英里。

这种小技巧能行得通,有两个主要原因:第一,消费者们不知道到底商品该花多少钱,所以我们就依靠我们大脑中并不严格定量的部分;第二,尽管人们花的美元是有限的,但是我们却依据加起来能导致数学盲的线索和半思维来做出决定。

下面是另外十种消费者数学很差的方式,在历史学家兼作家威廉·庞德斯通(William Poundstone.)帮助下完成。

  1. 我们受到第一个数字的严重影响。你走进一家高端的商店,就假设是爱马仕 (Hermès,是世界著名的奢侈品品牌)吧,你看到一个包包的标价是7000美元。”哈哈,那简直贵得离谱!”你和朋友说。”7000美元就 买一个包!”然后你发现了一款很好的手表,标价367美元,和天美时(Timex,是美国人最喜欢的时尚类手表品牌,以低端产品为主)手表相 比,这款绝对超贵。但是和那款你刚刚记得的7000美元的包包相比,这绝对是便宜货。这样,商店就能调整或者定下你的消费预期。
  2. 我们很害怕极端。我们不喜欢廉价的感觉,我们也不喜欢上当受骗。既然我们不确定商品的价值多少,我们就会避开过高或过低的价格。商家就会利用我们对中庸的偏爱来对付我们。下面是个不错的故事:人们有两种啤酒可以选择:2.5美元的高级啤酒和1.8美元的廉价啤酒。约80%的人会选择更贵的那种啤酒。现在引进第 三种啤酒,除了前面两种,还有一种超便宜的啤酒,只需1.6美元,现在80%的人会买1.8美元的啤酒,其余人会买2.5美元的啤酒。没有人选择最便宜的啤酒。第三次,他们撤去了1.6美元的超便宜啤酒,换成一种3.4美元的超高级啤酒。大多数人选择2.5美元的啤酒,一小部分人选择1.8美元的啤酒,约10%的人选择最贵的3.4美元的啤酒。简而言之,我们都是金发姑娘。(Goldilocks,童话故事“The story of the three bears”里小女孩的名字。故事里Goldilocks去了三只小熊家,总是选软硬中等的椅子和床等)。
  3. 我们都爱理由。在其著作《无价之宝》中解释了,当威廉姆斯-索诺玛公司 (Williams-Sonoma)在279美元的模型旁边加上一个429美元的面包机后,发生了什么:便宜的一款的销售量翻倍,尽管实际上几乎没人买那 款429美元的面包机。经验教训:如果一款产品卖不出去,试着在旁边放置一个外形几乎差不多、但是价格却是其两倍的产品这让第一款产品看起来像是个一定要买的便宜货。这种策略有效的一个原因就是人们喜欢理由。由于很难知道商品的真实价值,我们需要一些说法来向我们自己解释所做的决定。价格差异为我们提供 了一个理由和一个动机:那个279美元的面包机比另一款便宜了差不多40%–我们占大便宜了!好理由。
  4. 我们被告知什么,就做什么。行为经济学家喜欢在学校里做实验,他们发现在水果上闪点光。把沙拉像糖果那样摆放,可以让孩子们多吃水果和沙拉。但是成人们也同样受这些简单游戏的影响。例如,一些精明的餐厅,就会利用一些简单的技巧, 如图片和画框等设计菜单,以将我们的目光吸引到最有利可图的菜品上。好的经验法则:如果你看到菜单上一道菜是被突出的,画框的,配图的或是与一个非常昂贵 的菜品放在一起的时候,它可能就是一个高利润的产品,餐厅希望你能看见并考虑。
  5. 我们让情绪控制自己。庞德斯通的书中一个著名实验中,为志愿者们提供10美元以内的一定数量的钱。一些被认为是不公的出价(假设是1美元),会激活脑叶皮层,另外这也会由疼痛和恶臭引发。当我们觉得我们被宰的时候,我们一般都 会觉得恶心——即使是个还不错的交易。庞德斯通将此与迷你吧实验等同起来。很晚了,你很饿,且那里就有一个士力架,但是价格让你觉得非常不爽,于是你决定宁愿饿着自己,也不要觉得是被宰了。另一方面,便宜货让我们自我感觉良好,即使是世界上最没用的垃圾也会很有吸引力,只要价格非常便宜。
  6. 我们很容易被酒精,时间,决定变得更笨。年轻的时候,你在一家酒吧喝醉了,你很容易和陌生人做一些蠢事。“我有没有全面地评估这个复杂的浪漫情景呢?”喝了7杯酒后,这个问题就很难回答了,所以我们通常会问自己一个更简单的问题:“他/她性感吗?”我们在喝醉、倍感压力、疲累等等疏忽的时候,关于买东西,我们更可能会询问和回答一些简单的问题。杂货店里,便宜的糖果条和口香糖 通常放在收银台附近,因为那里是疲惫的购物者最有可能放纵渴望而不关注价格的地方。有酒的午餐有利于达成协议,因为酒精会立即缩小我们脑袋里的复杂因素的 范围。如果你想让某人冒一次被低估的风险,把他灌醉,或者让他疲累或耗尽其自我。
  7. 我们心疼交易成本。在这里的一个私人理财专栏里,梅根·麦卡德尔(Megan McArdle)请她的读者放弃一些经常性支付的交易,例如健身房会员和订阅并不使用的报纸和服务。“别买那些你不消费的东西。”似乎是个再清晰不过的建议,但是梅根有重要的一点。我们受一些订阅、会员制和捆绑产品的吸引,部分是因为我们试图避免交易费。我们宁愿多付一点点,也不愿遭受拿出钱包、看着我们的钱流向每个健身房季/电影等等时的心理之苦。
  8. 但是关于回扣和担保我们却表现得很怪异。现在既然我已经告诉你消费者避免另外 的支付,我应该加上两种我们喜爱的额外支付:回扣和担保。第一种购买财富的幻想(我花钱还有钱拿!)第二种购买心理的安宁(现在我能永远拥有这个东西,不 用担心了!)这两种基本都是伎俩。”与其购买一件东西然后拿折扣,”庞德斯通写道,”为什么不一开始就支付较低的价格呢?”。 “ [担保]没有任何合理意义,”哈佛经济学家大卫·卡特勒(David Cutler)这样告诉《华盛顿邮报》,”一个产品坏掉的隐含概率要大大高于你负担不起修理或更换的风险。如果你买了一个400美元的东西,对大多数消费 者而言,那种消费水平在任何情况下,都不是你需要保证的风险。”
  9. 我们迷恋数字9. 高达65%的零售价格以9结尾,为什么?人人都知道20 美元和19.99美元是一回事。但是数字9告诉我们一个简单的道理:这件商品打折了。这件东西很便宜。这件东西是某个知道你喜欢打折和便宜商品的人定价 的。换句话说,9已经超越了魅力价格的地位,成为买者与卖者之间一根承载无声理解的电缆,说明该产品定价非常合理、有竞争力。一家高档餐厅里的壳鱼拼盘的 定价里有9是很愚蠢的。没有一个愿意花170美元吃龙虾的人会寻求打折。但是同一个人,在买内衣的时候(研究已经反复显示),更可能会买价格是以9结尾的 产品。记住:购物是一个注意力游戏,消费者们不仅是在寻找产品,他们也在寻找产品值得购买的线索,我们大脑中寻找便宜货/打折品的角落在数字9中发现了可 以达成的交易。
  10. 我们受一种强烈的公平感控制。我已经解释过我们的大脑在看到便宜货和宰人货时如何有不同的反应。购物者的大脑受一种公平感推动。我们又回到我们不知道东西该花多少钱这点上,所以我们利用线索告诉我们应该为商品花多少钱。经济学家 丹·阿雷利(Dan Ariely)做的一个实验非常好地解释了这一点。阿雷利谎称他要办一个诗歌朗诵会,然后他告诉一组学生,门票要花钱,告诉另一组学生参加朗诵会有钱可 拿。然后他再告诉两组学生,朗诵会是免费的。第一组学生急于参加,认为他们免费得到了一些有价值的东西;第二种学生大多数拒绝了,认为他们被迫为同一个事 件志愿服务,却没有补偿。  一个行为经济学家的诗歌朗诵会价值几何呢?学生们不知道。这就是关键。我也不知道。这也是关键。一件全系扣的衬衫价值多少呢?一杯咖啡价值多少?一张人寿保险单价值多少?谁知道!我们大多数人不知道。结果,购物大脑只在知道的地方使用:视觉线索,激发的情感,比较,比例,关于便宜货vs. 宰人货的感觉我们并不愚蠢,我们只是易受影响

来源:译言

 

PS: 我们都喜欢便宜的东西,我们更害怕欺骗!所以,我们会去比较,去寻找自认为价格合理的线索!

20100326044848990

如何取出 PayPal 帐户里的美元最为经济?

很多朋友通过Paypal账户收取国外货币,久而久之账户上也有不少钱,钱多了也想取出来,但不知道如何取出这些美元最合算呢?

下面来介绍一下 PayPal 的四种取现方式:

1. 取现到你的美国帐户,3-4个工作日,免费。这种方式是最好的取现方式,前提条件是你得拥有一个美国的银行账户。对于大部分没有美国帐户的网友,这种办法没有意义。美国银行开户要求非常苛刻,根据美国爱国法案,你不在美国,没有美国地址,是不允许开真正的美国银行帐号。网上有些关于可以提供代办美国银行帐号服务,不过请您留心一些,有猫腻。

美国银行个人开户文件要求:

1)护照或美国驾照影印件;
2)社会安全号码;
3)美国账单地址与联系电话号码;
4)开户基本存款,通常为500-2000美金。

2. 取现到你的香港帐户,5-7个工作日。1000(含)港元以上免费,1000港元以下3.5港元的手续费。PAYPAL要收取提款总额2.5%的币种兑换费。加上港币到人民币,又损失一些,而且只能算是现钞。前提条件是你得拥有一个香港的银行账户。国内很多朋友用香港“招商银行两地一卡通”来收取paypal上的钱,国内就能办理这种卡。这里也给大家提供一个如何开香港汇丰个人帐户的说明步骤。

去香港汇丰开户时最好能用护照,还要带上你三个月之内的住址证明原件(这个非常重要)。另外准备好你的住址的英文地址,在开了银行户口后,在办一个ATM机上的取款卡时要用到英文地址,汇丰银行需要用这个英文地址寄那张卡的密码给你,否则办不到卡的。有了那张卡,你以后才能在国内汇丰公司的ATM机上洎甴取款(每天能取2.1万)。去的时候身上可以带6万港币和6千人民币。

“三个月之内的住址证明原件”是指的什么?类似于户口本的东东吗?”是指在可以证明你的住址的一些证明原件,比如,你的手机话费单,上面有你的名字和地址,是中国电信公司印的,这样可以证明这个地址是你的地址,其他如水电费单等也可以的,但一定要原件,不能是自已手写的.

香港的银行是开放的银行,不存在什么外汇管制,世界上任何一个国家的钱都可以洎甴地汇到香港银行中,香港银行也可以洎甴地汇到世界各地,没有限制的

3. 支票取现,4-6周,申请一张支票,PayPal需要收取手续费$5美金。支票不能在中国的银行直接提现,要通过托收,存入你中国的银行的帐户,即你必须在你兑换支票的银行有帐户才行。这种办法比较慢,但对于大部分既没有美国帐户又没有香港帐户的网友来说,这是最为经济的取现方式。支票托收最好直接到中国银行办理,如果在其他银行办理,可能最后还得走中行,而且这还会让你多支出一笔中介行费用。另外,4-6周是PayPal 给你结算支票的时间,并不包括邮寄支票的时间。估计10-15天左右到收到支票,托收1个月左右。美国paypal用平信寄出存在寄丢的风险。提款的最小金额为$150.00 USD。中国银行从去年3月份开始,托收价格调整每笔到60元起。

4. 取现到你的国内银行帐户,需要双币种卡,无论帐户余额的币种,都可以兑换成美元通过电汇提现到国内银行账户,PayPal承诺3到7个工作日,美金就能到达国内银行双币账号,每笔提现的费用为$35。可能存在中间行的费用,每个中间行费用为5~25美元。所以一般收到电汇的费用会大于35美元。说实话不便宜,比较适合数额较大的提现,未认证账户月提现限额$500,认证帐户为$3000,支持向国内的工农中建招五大银行电汇。银行卡的帐户名必须和paypal的帐户名一致。

由此看来,对于中国大陆的网友而言,并没有既经济又快捷的方式来取出PayPal 里面的美元。不过,随着人民币的不断升值,PayPal 里面的美元也在不断贬值,放在帐户里面显然并不是最为合理的办法。那么,对于帐户里的美元怎么办?

网上有人提出申请 香港招商银行两岸一卡通,目前来说这是Paypal提现最适合的办法了。看看“如何办理招商银行的两岸一卡通!”

paypal_logo

过去的岁月为何总那么美好?

每当你回想起过去的时光,是不是都觉得很美好?但有一些往事其实并没有你想得那么美。卡内基梅隆大学的凯里·莫尔维基(Carey Morewedge)发现,当人们回顾往事时,会倾向于记住更美好的经历,从而产生一种不客观的“怀旧偏好”。

莫尔维基是卡内基梅隆大学泰珀商学院市场营销专业的副教授,他解释说:“不论是回忆过去还是评价当前的处境,美好的经历总会最先浮现出来。可是与评价当下相比,我们回忆过去时更难想起那些不愉快的经历。记忆就像是一家唱片店,新歌不管是好是坏都要卖,但却只会保留最经典的老歌。正因为我们习惯忘记过去的不愉快,人们经常会对往事有一种怀旧的偏好。”

在这项研究中,莫尔维基分析了人们对不同年代的电视节目和电影的评价。他发现在评价综合类节目时,年代比较久远的节目常常可以得到好评。

当被要求回忆出具体的节目时,参与者们提到的节目中既有许多年前的热门,也有当下最新的节目,这些节目通常是他们的最爱。但与评价最新节目的做法明显不同的是,参与者们对最喜爱节目的好印象,会影响其对所有老节目的判断,让他们对这些老节目也产生了好印象。

这些发现可以帮助人们做出最佳的商业决策。

“我们总是相信过去要比当下好,于是常常会抵触积极的改变。”莫尔维基说,“这项研究告诉我们在寻求改变时,要不断地提醒人们不论是在生活还是工作中,过去的许多做法是可以加以改进的。”

这些积极的改变涉及生活和工作的方方面面,如鼓励员工使用最新的科技工具,或者学着与来自不同背景的同事合作。另外,这项研究还可以帮助市场营销专家们进一步理解消费者的喜好如何随时间而改变。

而莫尔维基之前的一项研究发现,人们经常会根据对于某一经历的特殊记忆,来预测自己是否会享受类似的经历。比如球迷在决定是否要去观看下一场比赛时,会依据他对上一赛季最精彩的一场比赛的记忆做出判断。莫尔维基认为这一记忆偏见也可能会使我们感觉过去要比当下更美好。

回忆是座桥

[转自:果壳]

自动机的威力

 

本文只关系到 有穷状态自动机 ,本文也不讲具体的算法,只讲一些基本概念,以及用本软件的自动机 能做什么怎么用

也有其他一些开源软件实现了本软件的部分功能,但总体上,不论是从功能还是性能(运行速度,内存用量)上考虑,到目前为止,我能找到的其它所有同类开源软件都完全无法与本软件竞争!欢迎大家一起交流!

自动机是什么

关于自动机的形式化定义,可以参考 wikipedia:

  • 自动机是一个可以自己运行的机器或机器人
  • 有穷状态自动机 (FSA)
    • 有限状态机(FSM)或有限状态自动机、或只是一个状态机,是用于设计计算机程序和时序逻辑电路的数学计算模型。
  • 确定性的有穷自动机 (DFA)
    • 这是本文的重点
  • 非确定性的有穷自动机 (NFA)
    • 很多 DFA 的构造需要 NFA 作为媒介
  • DFA 的最小化

    • DFA 的等同
      • 如果两个dfa的状态转移图同构,那么这两个 DFA 等同
    • DFA 的等价
      • 如果两个 DFA 能接受的语言集合相同,那么这两个 DFA 等价
      • 等价的 DFA 不一定等同
    • 最小化的 DFA
      • 对于任何一个 DFA,存在唯一一个与该 DFA 等价的 MinDFA,该 MinDFA 的状态数是与原 DFA 等价的所有 DFA 中状态数最小的
      • 最小化的 DFA 需要的内存更小
      • 各种优化的 DFA 最小化算法是本软件的核心竞争力之一

    将 DFA 用做字典

    什么是字典

    字典,有时候也叫关联数组,可以认为就是一个 map<string, Value>,这是最简单直接的表达,在 C++ 标准库中,map是用 RBTree 实现的,当然,也可以用 hash_map(或称为 unordered_map)。这些字典在标准库中都有,不是特别追求cpu和内存效率的话,可以直接拿来时使用。

    但是,要知道,对于一般应用,将字典文件(假定是文本文件)加载到 map/hash_map 之后,内存占用量要比字典文件大两三倍。当数据源很大时,是不可接受的,虽然在现在这年代,几G可能都算不上很大,但是,如果再乘以3,可能就是十几二十G了,姑且不论数据加载产生的载入延迟(可能得几十分钟甚至一两个小时)。

    用 DFA 存储字典,在很多专门的领域中是一个标准做法,例如很多分词库都用 DoubleArray Trie DFA 存储词库,语音识别软件一般也用 DFA 来存储语音。

    无环DFA (ADFA, Acyclic DFA)

    用做字典的 DFA 是无环DFA (ADFA, Acyclic DFA),ADFA 的状态转移图是 DAG(有向无环图)。Trie 是一种最简单的 ADFA,同时也是(所有ADFA等价类中)最大的 ADFA。DoubleArray Trie 虽然广为人知,但相比 MinADFA,内存消耗要大得多。

    ADFA 可接受的语言是有限集合,从乔姆斯基语言分类的角度,是4型语言(General的NFA和DFA是3型语言)。当然,有限,只是有限而已,但这个集合可能非常大,一个很小的ADFA也能表达非常大的字符串集合,用正则表达式举例: [a-z]{9} ,这个正则表达式对应的DFA包含10个状态,除终止状态外,其他每个状态都有26个转移(图的边),这个语言集合的大小是 269 = 5,429,503,678,976:从 aaaaaaaaa 一直到 zzzzzzzzz。想象一下,用 HashMap 或者 TreeMap,或者 DoubleArray Trie 来表达该集合的话,将是多么恐怖!

    编译

    目前,要编译 febird 中的自动机库,需要 C++11 编译器,推荐 gcc4.7 以上版本

    $ svn checkout http://febird.googlecode.com/svn/trunk/ febird-read-only
    $ cd febird-read-only
    $ make
    $
    $ cd tools/automata/
    $ make
    $ ll rls/*.exe # 后面会讲到详细用法
    -rwxrwxrwx 1 root root 27520456  8月 17 14:34 rls/adfa_build.exe*
    -rwxrwxrwx 1 root root 12265256  8月 17 14:34 rls/adfa_unzip.exe*
    -rwxrwxrwx 1 root root 16994384  8月 17 14:34 rls/dawg_build.exe*
    $
    $ cd netbeans-cygwin/automata/
    $ make
    $ ll rls/*/*.exe # 这两个 exe 用来测试自动机生成的 (key,val) 二进制数据文件
    -rwxrwxr-x 1 user user  9050159 2013-08-08 16:26 rls/forward_match/on_key_value.exe
    -rwxrwxr-x 1 user user  8994311 2013-08-08 16:26 rls/forward_match/on_suffix_of.exe

    map 与 set

    传统上,ADFA 只能用作 set<Key> ,也就是字符串的集合。但是,本软件可以把 ADFA 用作 map<Key, Value>,通过两种方式可以达到这个目标:

    • map1: 扩展 ADFA(从而 DFA 的尺寸会大一点),查找 key 时,同时计算出一个整数 index,该 index 取值范围是 [0, n),n 是 map.size()。从而,应用程序可以在外部存储一个大小为 n 的数组,用该 index 去数组直接访问 value。
      • 本软件中有一个 utility 类用来简化这个流程
      • 本质上,这种方法无法动态插入 (key,val);但可以追加 (key,val),追加的意思是说,前一个加入的 key,按字典序必须小于后一个加入的 key
    • map2: 将 Value 编码成 string 形式,然后再生成一个新的 string kv = key + '\t' + value,将 kv 加入 ADFA,在这种情况下,同一个 key 可以有多个 value,相当于 std::multimap<string, Value>,这种方法的妙处在于,如果多个key对应的value相同,这些value就被自动机压缩成一份了!
      • 这种方法可以动态插入、删除 (key,val),不过,要支持动态插入、删除功能,需要大约4~5倍的额外内存
      • 更进一步,这种方法可以扩展到允许 key 是一个正则表达式!(目前还不支持)

    内存用量/查询性能

    本软件实现了两种 DFA,一种为运行速度优化,另一种为内存用量优化,前者一般比后者快4~6倍,后者一般比前者节省内存30~40%,具体使用哪一种,由使用者做权衡决策。

    不同的数据,DFA有不同的压缩率。 对于典型的应用,为内存优化的DFA,压缩率一般在3倍到20之间,相比RBTree/HashMap的膨胀3倍,内存节省就有9倍到60倍!同时仍然可以保持极高的查询速度(keylen=16字节,QPS 在 40万到60万之间),为速度优化的版本,QPS 有 250万。下面是几个性能数据(map指map1,为了对齐,在一些数字前面补了0):

      size(bytes) gzip DFA(small+slow) DFA(big+fast) KeyLen QPS(big+fast) DFA Build time
    File1(Query) 226,433,393 96,293,588 map:101,125,415
    set:073,122,182
    170,139,298 016.7 24,000,000 47’seconds
    File2(URL) 485,968,345 25,094,568 map:13,990,737
    set:10,850,052
    035,548,376 109.2 00,900,000 16’seconds

    URL文件的冗余比较大,虽然文件尺寸大一倍多,但最终的dfa文件却要小得多,创建dfa用的时间也少得多!dfa文件的加载速度非常快,相当于整个文件读取,如果文件已在缓冲区,则相当于memcpy

    警告: 如果key中包含有随机串,例如 guid、md5 等,会大大增加自动机的内存用量!不要自作聪明把自然的串转化成 md5 等 hash 串再插入自动机!

    自动机实用程序

    本软件包含几个程序,用来从文本文件生成自动机,生成的自动机可以用C++接口访问,这样,就将自动机的存储与业务逻辑完全分离。

    • adfa_build.exe options < input_text_file

    输入文件的格式是: key \t value\t 是 key, value 的分隔符,\t 也可以是其它字符,只要该分隔符不在 key 中出现即可,value 中则可以包含分隔符。该程序本质上不管每行的的内容是什么,只是忠实地将每行文本加入自动机。分隔符的作用体现在后面将要提到的 api: DFA_Interface::match_key 中。

    ||

    options arguments comments
    -o 输出文件:为 尺寸 优化的自动机 匹配速度较慢,尺寸较小,所以是小写o
    -O 输出文件:为 速度 优化的自动机 匹配速度很快,尺寸较大,所以是大写O
    -l 状态字节:为 尺寸 优化的自动机,可取值 4,5,6,7 自动机的每个状态占几个字节,越大的数字表示自动机能支持的最大状态数也越大,一般5就可以满足大多数需求了
    -b 状态字节:为 速度 优化的自动机,可取值 4,5,6,7
    -t 无参数 输出文本,仅用于测试
    -c 无参数 检查自动机正确性,仅用于测试
    • dawg_build.exe options < input_text_file

    生成扩展的 DFA,可以计算 key 的 index 号(字典序号),对应 map 的第一种实现方式(map1),使用方法同 adfa_build.exe ,输入文件的每行是一个 Key。

    • adfa_unzip.exe < dfa_binary_file

    解压 dfa_binary_file,按字典序将创建自动计时的输入文件 input_text_file 的每行写到标准输出 stdout ,可以接受基本 dfa(由 adfa_build.exe 生成的)文件 和扩展dfa(由 dawg_build.exe 生成的)文件

    • on_suffix_of.exe P1 P2 … < dfa_binary_file

    打印所有前缀为 Pn 的行 (adfa_build.exe 或 dawg_build.exe 输入文本的行) 的后缀

    • on_key_value.exe text1 text2 … < dfa_binary_file

    打印匹配所有 textn 的前缀的 Key (adfa_build.exe 或 dawg_build.exe 输入文本的行) 的 value, 用于测试 map 实现方法2 (Key Value 之间加分隔符)

    自动机的 C++接口

    本软件使用了 C++11 中的新特性,所以必须使用支持 C++11 的编译器,目前测试过的编译器有 gcc4.7 和 clang3.1。不过为了兼容,我提供了C++98 的接口,一旦编译出了静态库/动态库,C++11 就不再是必需的了。

    #include <febird/automata/dfa_interface.hpp>

    automata/dfa_interface.hpp

    头文件 febird/automata/dfa_interface.hpp 中主要包含以下 class:

    DFA_Interface

    这个类是最主要的 DFA 接口,对于应用程序,总是从 DFA_Interface::load_from(file) 加载一个自动机(adfa_build.exe 或 dawg_build.exe 生成的自动机文件),然后调用各种查找方法/成员函数。

    • size_t for_each_suffix(prefix, on_suffix[, tr])
      • 该方法接受一个字符串prefix,如果prefix是自动机中某些字符串的前缀,则通过 on_suffix(nth,suffix) 回调,告诉应用程序,前缀是prefix的那些字符串的后缀(去除prefix之后的剩余部分),nth 是后缀集合中字符串的字典序。 tr 是一个可选参数,用来转换字符,例如全部转小写,将 ::tolower 传作 tr 即可
      • 例如:对字符串集合 {com,comp,comparable,comparation,compare,compile,compiler,computer}, prefix=com 能匹配所有字符串(其中nth=0的后缀是空串),prefix=comp能匹配除com之外的所有其它字符串,此时nth=0的也是空串,而 compare 的后缀 are 对应的 nth=1
      • 返回值是后缀集合的尺寸,一般情况下没什么用处,可以忽略
    • size_t match_key(delim,str,on_match[, tr])
      • 该方法用于实现 map2,delim 是 key,val 之间的分隔符(如 ‘\t’ ),key中不可包含delimstr 是扫描的文本,如果在扫描过程中,发现 str 的长度为 Kn 的前缀 P 匹配某个 key,就将该 key 对应的所有 value 通过 on_match(Kn,idx, value) 回调告诉调用方, idx是同一个key对应的value集合中当前value的字典序。
      • 返回值是最长的 部分匹配 的长度,一般情况下没什么用处,可以忽略

    DAWG_Interface

    这个类用来实现 map1,DAWG 的全称是 Directed Acyclic Word Graph,可以在 ADFA 的基础上,在匹配的同时,计算一个字符串在 ADFA 中的字典序号(如果存在的话),同时,也可以从字典序号计算出相应的字符串,时间复杂度都是O(strlen(word))。

    • size_t index(string word)
      • 返回 word 的字典序,如果不存在,返回 size_t(-1)
    • string nth_word(size_t nth)
      • 从字典序 nth 计算对应的 word,如果 nth 在 [0, n) 之内,一定能得到相应的 word,如果 nth 不在 [0, n) 之内,会抛出异常
    • size_t v_match_words(string, on_match[, tr])
      • 依次对 string 的所有前缀计算 index,并通过 on_match(prelen,nth) 回调返回计算结果,prelen是匹配的前缀长度,该函数也有可选的 tr 参数
      • 返回值是最长的 部分匹配 的长度,一般情况下没什么用处,可以忽略
    • size_t longest_prefix(string, size_t*len, size_t*nth[, tr])
      • 相当于 v_match_words 的特化版,只返回最长的那个 prefix
      • 返回值是最长的 部分匹配 的长度,一般情况下没什么用处,可以忽略

    API使用方法的示例程序

    对应于svn代码目录: febird-read-only/netbeans-cygwin/automata/samples

    超级功能

    以拼音输入法为例

    为了保证输入效率,我们需要有一个从 词条拼音词条汉字 的映射表,比如,拼音序列 ZiDongJi 对应的词条是 自动机自冻鸡 ;从而,逻辑上讲,这是一个 map<string,list<string> >

    假定我们有一个汉语词表,该词表的词条超过千万,每个词条可能是一句话(比如名言警句),并且,因为汉语中存在多音字,从而,包含多个多音字的词条都可能有很多种发音,这个数目在最坏情况下是指数级的,第一个字有 X1 个读音,第二个字有 X2 个读音,…,n 个字的词条就有 X1*X2*X3**Xn 种读音。另外,除了多音字,还有简拼,对于短词来说,简拼的重码率很高,不实用,但是对于长词/句,简拼的重码率就很低了。

    如果我们用 HashMap/std::unordered_map 或 TreeMap/std::map 保存这个注音词典,对于普通无多音字的词条,无任何问题。一旦有多音字, X1*X2*X3**Xn 可能是一个非常大的数字,几十,几百,几千,几千万都有可能,这完全是不可接受的!一个折衷的办法就是仅选择概率最大的拼音,但很可惜,有些情况下这个拼音可能是错的!

    用自动机解决该问题,最简单的方法就是用 string kv = X1*X2*X3**Xn + ‘\t’ + 汉字词条 来逐个插入,动态 MinADFA 算法可以保证内存用量不会组合爆炸,但是,除了内存,还有时间,如此逐个展开,时间复杂度也是指数的!

    这个问题我想了很久,终于有一天,想出了一个完美的解决办法:

    1. 在 MinADFA 中加入一个功能:在线性时间内,给一个唯一的前缀,追加一个 ADFA后缀
      • ADFA后缀 可以包含 X1*X2*X3**Xn 个 word,并且结构还可以任意复杂(最近我的研究发现,该后缀甚至不必是ADFA,可以包含环!)
      • 这个算法是整个问题的关键,余下的,就只是简单的技巧而已
    2. string kv = X1*X2*X3**Xn + ‘\t’ + 汉字词条 翻转
      • rev(X1*X2*X3**Xn) 构成一个 ADFA
      • rev(‘\t’+汉字词条) 是一个唯一前缀
    3. 将所有的词条做这样的处理,就构成一个 DFA({rev(拼音+’\t’+汉字)})
    4. 将该 DFA 翻转,得到 NFA(rev(DFA({rev(拼音+’\t’+汉字)})),再将该 NFA 转化成 DFA
    5. 查找时,使用 map2 的方法(DFA_Interface::match_key),因为组合爆炸,不能用 map1

    这个方法非常完美!虽然 NFA 转化 DFA 最差情况下是 NSpace(比NP还难) 的,但是在这里,可以证明,这个转化是线性的:O(n)。

     

    【本文转自: http://url.cn/HybFTp , 有删改增】

    自动机