MENU

从 OJ 题目学习 python 技巧系列 (2):字符串format方法与IP地址相关的处理

December 17, 2018 • 编程开发

字符串format方法

Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。

基本语法是通过 {} 和 : 来代替以前的 % 。

“映射”示例

1. 通过位置

In [1]: '{0},{1}'.format('kzc',18)  
Out[1]: 'kzc,18'  
In [2]: '{},{}'.format('kzc',18)  
Out[2]: 'kzc,18'  
In [3]: '{1},{0},{1}'.format('kzc',18)  
Out[3]: '18,kzc,18'

2. 通过关键字参数

字符串的format函数可以接受不限个参数,位置可以不按顺序,可以不用或者用多次,不过2.6不能为空{},2.7才可以

In [5]: '{name},{age}'.format(age=18,name='kzc')  
Out[5]: 'kzc,18'

3. 通过对象属性

class Person:  
    def __init__(self,name,age):  
        self.name,self.age = name,age  
        def __str__(self):  
            return 'This guy is {self.name},is {self.age} old'.format(self=self)  

In [2]: str(Person('kzc',18))  
Out[2]: 'This guy is kzc,is 18 old'

4. 通过下标

In [7]: p=['kzc',18]
In [8]: '{0[0]},{0[1]}'.format(p)
Out[8]: 'kzc,18'

格式限定符

1. 填充与对齐

填充常跟对齐一起使用
^、<、> 分别是居中、左对齐、右对齐,后面带宽度;

:号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充

python_format.png

In [15]: '{:>8}'.format('189')
Out[15]: '     189'
In [16]: '{:0>8}'.format('189')
Out[16]: '00000189'
In [17]: '{:a>8}'.format('189')
Out[17]: 'aaaaa189'

2. 精度与类型f

精度常跟类型f一起使用

In [44]: '{:.2f}'.format(321.33345)
Out[44]: '321.33'

其中.2表示长度为2的精度,f表示float类型

3. 其他类型

主要就是进制了,b、d、o、x分别是二进制、十进制、八进制、十六进制

In [54]: '{:b}'.format(17)
Out[54]: '10001'
In [55]: '{:d}'.format(17)
Out[55]: '17'
In [56]: '{:o}'.format(17)
Out[56]: '21'
In [57]: '{:x}'.format(17)
Out[57]: '11'

,号还能用来做金额的千位分隔符

In [47]: '{:,}'.format(1234567890)
Out[47]: '1,234,567,890'

实际用例

s = '1.2.3.4'
['{}'.format(int(i)) for i in s.split('.')]
Out[34]: ['1', '2', '3', '4']
['{:b}'.format(int(i)) for i in s.split('.')]
Out[35]: ['1', '10', '11', '100']
['{:8b}'.format(int(i)) for i in s.split('.')]
Out[36]: ['       1', '      10', '      11', '     100']
['{:08b}'.format(int(i)) for i in s.split('.')]
Out[37]: ['00000001', '00000010', '00000011', '00000100']

IP地址处理相关问题

1. int32->IPv4 即点分十进制的实现

本题就是给一个Int32的地址,最后划归成对应的IPv4点分十进制的格式:

Examples
2149583361 ==> "128.32.10.1"
32         ==> "0.0.0.32"
0          ==> "0.0.0.0"

那么这个题目如果按照IPv4的定义去做就需要实现一个除2去幂的计算,也就是“点分十进制”的实现算法:

def int32_to_ip(int32):
    """
    standard format: 10000000.00100000.00001010.00000001
                     2**31 + 2**30 + 2**29 + ...... + 2**0
    """
    ip = ''

    for i in range(32)[::-1]:
        if int32 >= 2 ** i:
            ip += '1'
            int32 -= 2 ** i
        else:
            ip += '0'

    # After this step, the format of ip is: 10000000001000000000101000000001
    # Then try to separate it with '.' and convert it to

    ip = ".".join([str(int(ip[i:i + 8], 2)) for i in range(0, len(ip), 8)])

    return ip

如果使用Python3,那么就可以利用format的特性:

def int32_to_ip(int32):
    return '{}.{}.{}.{}'.format(*int32.to_bytes(4, 'big'))

ipaddress库解法

from ipaddress import IPv4Address

def int32_to_ip(int32):
    return str(IPv4Address(int32))

2. int32<->IPv4

这个题目就是实现Int32和IPv4点分十进制的格式的相互转化:

For s = "10.0.3.193", the output should be "167773121".
For s = "167969729", the output should be "10.3.3.193".

IPv4 -> int32

在这里是最体现语法糖的地方,利用了python的三元运算符、列表推导式、字符串format方法等技巧,整体过程的算法描述如下:

  1. 根据.对IPv4进行拆分
  2. 将拆分后的每一段数字是否在0-256之间
  3. 如果在转换成8位标准的二进制字符串
  4. 拼接4段8位二进制字符串,转换成10进制数字,再将数字转换成字符串返回

精简一下,最核心是IPv4点分十进制->32位二进制数字的过程:

s = '1.1.1.1'
"".join(["{:0>08b}".format(int(i)) for i in s.split('.')])
Out[8]: '00000001000000010000000100000001'

IPv4 <- int32

这里和第一题一样,但是这里不用去实现点分十进制的过程了,给出一个精简的计算:

ip_str = bin(int(s)).replace('0b', '')
ip_str = (32 - len(ip_str)) * '0' + ip_str
return ".".join([str(int(ip_str[i:i + 8], 2)) for i in range(0, len(ip_str), 8)])

3. 计算两个IPv4地址之前的数量

题目很简单,就是标题的内容,下面给几个例子:

Examples
ipsBetween("10.0.0.0", "10.0.0.50")  =>   50 
ipsBetween("10.0.0.0", "10.0.1.0")   =>  256 
ipsBetween("20.0.0.10", "20.0.1.0")  =>  246

那么通过1、2两题的方式,我们就可以直接将IPv4格式变成2进制或者10进制进行减法:

def ips_between(start, end):
    return ipToNumber(end) - ipToNumber(start)

def ipToNumber(p1):
    rets = ''
    for t in p1.split('.'):
        rets += '{0:08b}'.format(int(t))
    return int(rets,2)

ipaddress库解法

from ipaddress import ip_address

def ips_between(start, end):
    return int(ip_address(end)) - int(ip_address(start))

4. 获取一个CIDR地址的所有IP

Examples:

 Input = 192.168.1.0/31
 Output = ['192.168.1.0, 192.168.1.1']

 For a subnet that is not appear to be an IPv4 or IPv6 network generate exception:
 Input = 213.256.46.160/28
 Output = "not appear to be an IPv4 or IPv6 network"
import ipaddress

def ipsubnet2list(subnet):
    ip_split_list = ['{:0>08b}'.format(int(i)) if 0 <= int(i) < 256 else False for i in subnet.split('/')[0].split('.')]
    if False in ip_split_list:
        return "not appear to be an IPv4 or IPv6 network"

    return ", ".join([str(i) for i in list(ipaddress.ip_network(subnet).hosts())])
Tags: python, IP
Archives QR Code
QR Code for this page
Tipping QR Code