面试官:谈谈你对geohash的理解和如何实现附近人功能呢?
前言
Hello,小伙们好,我是阿沐!一个喜欢通过实际项目实践来分享技术点的程序员!
你们有没有遇到被面试官嘲讽的场景;之前有位刚毕业的小学弟在上海魔都某某某大公司面试,二面主要是问了关于redis的相关知识点,回答的也是磕磕绊绊的,其中一个问题是如何实现搜索附近人加好友功能;想跟小伙伴们一起分享、一起探讨下。如果有不正确的地方,欢迎指正批评,共同进步~~~
面试官的主要考点
- 考点一:面试官考点之Geohash是什么
知识存储量,没用过但是不能不知道
- 考点二:面试官考点之原理与算法
考验算法基本功
- 考点三:面试官考点之redis的geohash的基础命令
考察底子
- 考点四:面试官考点之实现想法与方案
考察思维能力
- 考点五:面试官考点之实际项目应用
实战经验
当你看到面试官想考验你这些知识点的时候;你在面试官问的过程中,就脑海在飞快的转动着,组合一系列的数据场景准备应战。
Geohash概念介绍
geohash就是一种地理位置编码。用来查询附近的POI(POI是“Point of Interest”的缩写,中文可以翻译为“兴趣点”。在地理信息系统中,一个POI可以是一栋房子、一个商铺、一个邮筒、一个公交站等),也是一种算法的思想。
通过将地球看成一个二维的平面图,然后将平面递归切分成更小的模块,然后将空间经纬度数据进行编码生成一个二进制的字符串,再通过base32将其转换为一个字符串。最终是通过比较geohash的值的相似程度查询附近目标元素。
Geohash能实现什么功能?
- 地图导航; 高德地图、百度地图、腾讯地图
- 附近人功能;微信附近人、微信摇一摇、拼夕夕附近人、扣扣附近人
Geohash 算法原理
讲真地,当我要准备讲解原理和算法的时候,也很纠结,毕竟算法不是我的强项且百度一下千篇一律;并且都是大神级人物总结,且不敢妄自菲薄,所以还是站在前人的肩膀上来理解下geohash原理与算法。
“附近的人”也就是常说的 LBS (Location Based Services,基于位置服务),它围绕用户当前地理位置数据而展开的服务,为用户提供精准的增值服务。
“附近的人” 核心思想如下:
① 以“自己”为中心,搜索附近的用户
② 以“自己”当前的地理位置为准,计算出别人和 “我” 之间的距离
③ 按“自己”与别人距离的远近排序,筛选出离我最近的用户或者商店等
那么我们按照我们以往的操作方式:我们在搜索附近人时,会将整个站点的用户信息塞到一个list中,然后去遍历所有节点,检查哪一个节点在自己的范围内;时间复杂度就变成了n*m(n搜索次数,m用户数据)这谁顶得住啊,就是全部放redis一旦数据量上来也顶不住,搜索效率极低。
上面大家应该可以看出来吧,其实就是把自己的坐标作为一个中心;哎,然后我们要找到围绕我们方圆10公里以内的附近小伙伴:
X轴:我们可以看做是纬度,左半边范围是-180°~~ 0°;右半边是0° ~~ 180° Y轴:我们可以看做是经度,上半边范围是0° ~~ 90°;下半边是-90° ~~ 0° 原点:我们就看做是(0,0)位置;就好像是我们自己的位置
举例子
假如我们现在地点在广州字节跳动有限公司(广州市天河区珠江东路6号)经纬度是:113.326059(经度),23.117596(纬度)
geohash实质就是将经纬度进行二分法的形式落于相对应的区间中,越分越细一直到趋近于某一个临界值,那么分的层数越多,精确度越准确。
原则是:左区间标注 0;右区间标注 1。
例如我们用代码实现上面经纬度二分法生成的二进制:
/*** @desc 利用递归思想 找出经纬度的二进制编码* @param float $place 经度或纬度* @param string $binary_array 每次递归拿到的bit值* @param int $max_separate_num递归总次数* @param array $section 区间值* @param int $num 递归次数* @return array*/
public function binary($place = 0, $binary_array = [], $max_recursion_num = 20, $section = [], $num = 1)
{if (!$section) return $binary_array;// 获取中间值$count = ($section['max'] - $section['min']) / 2;// 左半边区间$left = ['min' => $section['min'],'max' => $section['min'] + $count];// 右半边区间$right = ['min' => $section['min'] + $count,'max' => $section['max']];// 假如给点的经纬度值大于右边最小值 则属于右半边区间为1 否则就是左半边区间 0array_push($binary_array, $place > $right['min'] ? 1 : 0);// 如果递归次数已经达到最大值 则直接返回结果集if ($max_recursion_num <= $num) return $binary_array;// 下一次递归我们需要传入的经纬度 区间值$section = $place > $right['min'] ? $right : $left;// 继续针对自身做的递归处理 一直到出现结果return $this->binary($place, $binary_array, $max_recursion_num, $section, $num + 1);
}// 实例化调用该方法
require_once './Geohash.php';$geohash = new Geohash();echo json_encode($geohash->binary(23.117596,[],$geohash->baseLengthGetNums(4, 1), $geohash->interval[0],1));//结果集
[1,0,1,0,0,0,0,0,1,1]-> 二进制 101000 00011 这样是不是很清晰//再看不明白的话 那么久打印出范围值:
[{"min":-90,"max":90},{"min":0,"max":90},{"min":0,"max":45},{"min":22.5,"max":45},{"min":22.5,"max":33.75},{"min":22.5,"max":28.125},{"min":22.5,"max":25.3125},{"min":22.5,"max":23.90625},{"min":22.5,"max":23.203125},{"min":22.8515625,"max":23.203125}]
从上面的脚本实现来看是不是更清晰了呢?那么我在使用lua语言给大家实现展示一下,本身原理基本一致:
local cjson = require("cjson")-- 定义经纬度区间范围
local interval = {{ min = -90, max = 90 },{ min = -180, max = 180 }
}--- @desc 利用递归思想 找出经纬度的二进制编码
--- @param place string 经度或纬度
--- @param binary_array table 每次递归拿到的bit值
--- @param max_separate_num number 递归总次数
--- @param section table 区间值
--- @param num number 递归次数
function binary(place, binary_array, max_recursion_num, section, num)-- bodyplace = tonumber(place) or 0binary_array = binary_array and binary_array or {}max_recursion_num = tonumber(max_recursion_num) or 20section = section and section or {}num = tonumber(num) or 1if not next(section) thenreturn binary_arrayendprint(cjson.encode(section))-- 获取中间值local count = (section["max"] - section["min"]) / 2-- 左半边区间local left = {min = section["min"],max = section["min"] + count}-- 右半边区间local right = {min = section["min"] + count,max = section["max"]}-- 假如给点的经纬度值大于右边最小值 则属于右半边区间为1 否则就是左半边区间 0binary_array[#binary_array+1] = place > right["min"] and 1 or 0-- 如果递归次数已经达到最大值 则直接返回结果集if max_recursion_num <= num thenreturn binary_arrayend-- 下一次递归我们需要传入的经纬度 区间值local _section = place > right["min"] and right or leftreturn binary(place, binary_array, max_recursion_num, _section, num + 1)
endlocal res = binary(113.326059, {}, _base_length_get_nums(4, 1), interval[2], 1)print(cjson.encode(res))//打印结果
{"max":180,"min":-180}
{"max":180,"min":0}
{"max":180,"min":90}
{"max":135,"min":90}
{"max":135,"min":112.5}
{"max":123.75,"min":112.5}
{"max":118.125,"min":112.5}
{"max":115.3125,"min":112.5}
{"max":113.90625,"min":112.5}
{"max":113.90625,"min":113.203125}
[1,1,0,1,0,0,0,0,1,0]
我们可以实际手动打一遍执行下,聪明的朋友应该看到一个函数php($geohash->baseLengthGetNums
)和lua中(_base_length_get_nums
)私有方法,这个是干嘛用的,通过方法注释我们看到大概意思是我们二分层数:
--- @desc 根据指定编码长度获取经纬度的 二分层数
--- @param int $length 编码精确度
--- @param int $type 类型 0-纬度;1-经度
--- @return mixed
local function _base_length_get_nums(length, typ)-- 第一种方法写死local list = { {2, 3}, {5, 5}, {7, 8}, {10, 10}, {12, 13}, {15, 15}, {17, 18}, {20, 20}, {22, 23}, {25, 25}, {27, 28}, {30, 30} }-- 第二种通过规律计算纬度位数组合成listlocal cycle_num = 12local list_res = {}local lat, lng = 0, 0for i = 1, 12, 1 dolat = i % 2 == 0 and lat + 3 or lat + 2lng = i % 2 == 0 and lng + 2 or lng + 3list_res[#list_res + 1] = {lat, lng}endreturn list[length][typ]
end
面试官:谈谈你对geohash的理解和如何实现附近人功能呢?相关推荐
- 面试官:谈谈分库分表吧?
面试官:"有并发的经验没?" 应聘者:"有一点." 面试官:"那你们为了处理并发,做了哪些优化?" 应聘者:"前后端分离 ...
- 联合索引会创建几个索引_面试官:谈谈你对mysql联合索引的认识?
引言 这篇文章作为<面试官:谈谈你对mysql索引的认识>的续篇,我当时在写这篇的时候,考虑到篇幅问题所以略去了联合索引的内容,今天给大家补上. 本文预计分为两个部分:(1)联合索引部分的 ...
- 面试官: 谈谈什么是守护线程以及作用 ?
来自:小哈学Java 目录 一.什么是守护线程 二.守护线程的作用及应用场景 三.总结 一.什么是守护线程 守护线程相对于正常线程来说,是比较特殊的一类线程,那么它特殊在哪里呢?别急,在了解它之前,我 ...
- 面试官:说说对observable的理解
一.Observable 是什么 Observable 翻译过来我们可以理解成可观察的 我们先来看一下其在Vue中的定义 Vue.observable,让一个对象变成响应式数据.Vue 内部会用它来处 ...
- 面试官:因为这个语言,我淘汰了90%的人!
很多人都有这样的经历: 大量重复性工作:日报.周报.各种报,无穷无尽:不计其数的数据提取······琐碎繁杂的事务让工作的效率极低. 如果可以一键完成就好了. 对这些问题来说,最高效的解决途径就是 P ...
- 面试官:因为这个语言,我淘汰了90%的人!!
很多人都有这样的经历: 大量重复性工作:日报.周报.各种报,无穷无尽:不计其数的数据提取······琐碎繁杂的事务让工作的效率极低. 如果可以一键完成就好了. 对这些问题来说,最高效的解决途径就是 P ...
- access建立两个字段唯一索引_面试官:谈谈你对mysql索引的认识?
引言 这篇我们就来谈谈关于索引方面的mysql面试题.还是老规矩,讲的是在Innodb存储引擎下的情形,毕竟我还真没用过Mysiam之类的存储引擎. ps:其实很早就想写了,一直偷懒! 其实这下面每个 ...
- 程序员面试怎么回答面试官:你对Spring的理解?
spring呢,是pivotal公司维护的一系列开源工具的总称,最为人所知的是spring mvc,事实上,他们都是基于spring framework,并且再其上继续增强,为某一方面服务的java组 ...
- 面试官:说说你对操作系统的理解?核心概念有哪些?
一.是什么 操作系统(Operating System,缩写:OS)是一组主管并控制计算机操作.运用和运行硬件.软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序,同时也是计算机系统的内核与 ...
- MySQL面试:谈谈你对聚簇索引的理解
通俗的讲 聚簇索引: 将数据存储和索引放到了一块,找到了索引也就找到了数据 一般情况下主键会默认创建聚簇索引,且一张表只允许存在一个聚簇索引. 非聚簇索引: 将数据存储于索引分开结构,索引结构的叶子节 ...
最新文章
- 计算机书籍-Exploring Cloud Computing免费电子书
- java EE的apache-tomcat配置文件web.xml
- Servlet 实例
- 万里挑一!北大AI女神每天必看的几个优质公众号
- struts2框架之文件下载(参考第三天学习笔记)
- [原][osgearth]osgearthElvation中的一帧
- 计算机基础(六):内存申请方式
- linux面试题(填空部分)
- 2022张宇考研基础30讲 第九讲 一元函数积分学的几何应用
- 【货位优化】基于遗传算法实现仓库货位优化问题含Matlab源码
- 转载 基于MATLAB 进行图像分类
- Text file busy问题解决
- 【重磅】凯文.凯利8个小时演讲实录
- Predicting drug–disease associations through layer attention graph convolutional network
- halcon相机标定助手_使用Halcon助手来制作标定数据
- 邮箱服务器满了foxmail,foxmail邮箱的容量满了,应该怎样删除-上海腾讯企业邮箱[foxmail]...
- [全国计算机二级]基础知识汇总(一)
- Qt5.9自定义按钮实例(上图片下文字,图片文字间距任意可调)
- http中get/put/post区别
- 什么叫地推模式_区域商家的地推模式以及推广方案
热门文章
- 孪生素数对php编写,孪生素数
- matlab zxing ean13,条形码研究-EAN13 条形码
- SequoiaDB 网络通信三剑客(maxsocketpernode,maxsocketperthread,maxsocketthread)
- 第三版新视野大学英语读写教程4结业考点(1,2,3,5,6单元)
- ios 根据日期知道周几_iOS 计算指定日期是周几星期几
- python-爬取东方财富网期货市场大商所数据
- 工具----9、浏览器攻击框架--(BeEF)
- hash与history 以及区别
- 如何免费pdf全部转化为word版
- android常见的限制符和屏幕适配