【OD机试题解法笔记】根据IP查找城市

题目描述

某业务需要根据终端的IP地址获取该终端归属的城市,可以根据公开的IP地址池信息查询归属城市。
地址池格式如下:

城市名=起始IP,结束IP

起始和结束地址按照英文逗号分隔,多个地址段采用英文分号分隔。比如:

City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6

一个城市可以有多个IP段,比如City1有2个IP段。
城市间也可能存在包含关系,如City3的IP段包含City2的IP段范围。
现在要根据输入的IP列表,返回最佳匹配的城市列表。
注:最佳匹配即包含待查询IP且长度最小的IP段,比如例子中3.4.4.4最佳匹配是City2=3.3.3.3,4.4.4.4,5.5.5.5的最佳匹配是City3=2.2.2.2,6.6.6.6

输入描述
第一行为城市的IP段列表,多个IP段采用英文分号 ‘;’ 分隔,IP段列表最大不超过500000。城市名称只包含英文字母、数字和下划线。最多不超过100000个。IP段包含关系可能有多层,但不超过100层。
第二行为查询的IP列表,多个IP采用英文逗号 ‘,’ 分隔,最多不超过10000条。

输出描述
最佳匹配的城市名列表,采用英文逗号 ‘,’ 分隔,城市列表长度应该跟查询的IP列表长度一致。
备注
无论是否查到匹配正常都要输出分隔符。举例:假如输入IP列表为IPa,IPb,两个IP均未有匹配城市,此时输出为",",即只有一个逗号分隔符,两个城市均为空;
可以假定用例中的所有输入均合法,IP地址均为合法的ipv4地址,满足 (1-255).(0-255).(0-255).(0-255)的格式,且可以假定用例中不会出现组播和广播地址。

用例
输入

City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3

输出

City1,City2,City3

说明

1)City1有2个IP段,City3的IP段包含City2的IP段;
2)1.1.1.15仅匹配City1=1.1.1.11,1.1.1.16,所以City1就是最佳匹配;2.2.2.3仅匹配City3=2.2.2.2,6.6.6.6,所以City3是最佳匹配;3.3.3.5同时匹配为City2=3.3.3.3,4.4.4.4和City3=2.2.2.2,6.6.6.6,但是City2=3.3.3.3,4.4.4.4的IP段范围更小,所以City3为最佳匹配;

思考

难点在于判断 ip 地址包含关系。比如 3.3.3.5 包含于 [3.3.3.3,4.4.4.4.4],认为 3.3.3.5 大于 3.3.3.3,显然前面 3 个十进制数都相等,最后一个 5 > 3。3.3.3.5 小于 4.4.4.4 是怎么得出的呢? 5 > 4, 但是前面的 3 小于 4,这是把 3.3.3.5 看成 3335, 把 4.4.4.4 看成 4444 ,比较 3335 < 4444 ? 假如比较 3.3.255.54.4.4.4 大小,还是 332555 > 4444 ?显然不对,3.3.255.5 应该小于 4.4.4.4,左边是ip地址的高位,比较从高位往地位走。ip地址本质上是把一个32位整数转成二进制形式,然后均分成 4 段,每一段的二进制单独转成一个整数,最后用点连接起来形成一个地址字符串,这是点分表示法。这种点分形式从高位往地位进行比较大小和十进制整数类似,但操作起来不方便,可用直接把ip地址还原成整数再比较十进制整数值就方便多了。3.3.255.54.4.4.4 的二进制表示分别为00000011.00000011.11111111.0000010100000100.00000100.00000100.00000100,去掉分割点,再转成十进制分别为12844805、67372036,显然后者更大。

参考代码

// 将IP地址转换为长整型数的函数
function ipToLong(ip) {
  // 使用split('.')将IP地址分割为四部分,然后使用reduce方法累加每部分
  return ip
    .split(".")
    .reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
}

// 解析IP地址池的函数
function parseIpPool(ipPool) {
  const cityIpRanges = {}; // 创建一个对象用于存储城市和对应的IP范围
  ipPool.split(";").forEach((cityRange) => {
    const [city, range] = cityRange.split("="); // 分割字符串获取城市名和IP范围
    const [startIp, endIp] = range.split(","); // 分割字符串获取起始IP和结束IP
    const start = ipToLong(startIp); // 将起始IP转换为长整型数
    const end = ipToLong(endIp); // 将结束IP转换为长整型数
    if (!cityIpRanges[city]) cityIpRanges[city] = []; // 如果对象中不存在该城市,则初始化一个空数组
    cityIpRanges[city].push({ start, end }); // 将IP范围对象添加到对应城市的数组中
  });
  return cityIpRanges; // 返回解析后的城市IP范围对象
}

// 匹配城市的函数
function matchCities(ipPool, queryIPs) {
  const cityIpRanges = parseIpPool(ipPool); // 调用parseIpPool函数解析IP池
  return queryIPs
    .split(",")
    .map((ip) => {
      const ipNum = ipToLong(ip); // 将查询的IP地址转换为长整型数
      let bestMatchCity = ""; // 初始化最佳匹配城市为空字符串
      let smallestRange = Number.MAX_SAFE_INTEGER; // 初始化最小范围为最大安全整数
      for (const city in cityIpRanges) {
        // 遍历城市IP范围对象
        cityIpRanges[city].forEach((range) => {
          if (ipNum >= range.start && ipNum <= range.end) {
            // 判断IP是否在当前范围内
            const rangeSize = range.end - range.start; // 计算当前范围的大小
            if (rangeSize < smallestRange) {
              // 如果当前范围小于已知的最小范围
              bestMatchCity = city; // 更新最佳匹配城市
              smallestRange = rangeSize; // 更新最小范围
            }
          }
        });
      }
      return bestMatchCity; // 返回最佳匹配城市
    })
    .join(","); // 将匹配结果数组转换为字符串,并用逗号分隔
}


function solution() {
  const ipPool = readline().split(';').map(item => {
    const [city, ip] = item.split('=');
    return [city, ip.split(',').map(ipToLong)];
  });
  const checkIps = readline().split(',').map(ipToLong);
  const result = [];

  for (let i = 0; i < checkIps.length; i++) {
    let ip = checkIps[i], found = false, smallestRange = Infinity;
    for (let [city, [startIp, endIp]] of ipPool) {
      if (ip >= startIp && ip <= endIp) {
        found = true;
        let len = endIp - startIp;
        if (smallestRange === Infinity) {
          result.push(city);
          smallestRange = len;
        } else if (smallestRange > len) {
          result[i] = city;
          smallestRange = len;
        }
      }
    }
    if (!found) {
      result.push('');
    }
  }

  console.log(result.join(','));
}

const cases = [
  `City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3`,
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution();
});
;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值