pybind11 | 绑定CGAL几何算法(numpy数据交换)

news2024/9/30 13:27:18

文章目录

  • 一 前言
  • 二 numpy数据交换
    • 2.1 pybind11对numpy的支持
    • 2.2 Numpy VF(py::array_t)与CGAL mesh(Surface Mesh)之间的转换
  • 三 绑定CGAL算法示例
    • 3.1 示例函数
    • 3.2 绑定部分代码
    • 3.3 示例完整代码
  • 四 编译生成和测试
    • 4.1 编译生成pyd文件
    • 4.2 Python调用测试
  • 五 总结
  • 参考和拓展

一 前言

对于CGAL,前段时间也用过相关的Python绑定,详情见文章:【CGAL+Python】安装CGAL的Python绑定。如果我们想要调用[Polygon Mesh Processing](CGAL 5.5.1 - Polygon Mesh Processing: User Manual)中的算法,在不就地读取网格文件的前提下,需要在Python代码中构建CGAL的多边形网格对象,显得非常的不方便和蹩脚。正好,我在之前使用过libigl库的Python绑定,其数据通过numpy进行交换,即输入和输出都是numpy数组。于是,在对pybind11进行了一段时间的学习后,我开始尝试通过pybind11对CGAL的相关函数进行绑定生成Python调用接口,更重要的是使用numpy数组进行数据交换。

二 numpy数据交换

2.1 pybind11对numpy的支持

pybind11/numpy.h头文件中,提供了对Numpy array的支持。我们可以通过py::array_t<T>来实现一个Numpy array。

py::array_t支持一些基于Numpy的API:

  • .dtype()返回数组元素的类型。
  • .strides()返回数组strides的指针。
  • .reshape({i, j, ...})返回指定shape的数组视图。.resize({})也可以。
  • .index_at(i, j, ...)获取数组指定索引的元素。

为了更高效地访问大型数组,pybind11提供了不带检查的代理类unchecked<N>mutable_unchecked<N>,其中N为数组所需的维数。

m.def("sum_3d", [](py::array_t<double> x) {
    auto r = x.unchecked<3>(); // x must have ndim = 3; can be non-writeable
    double sum = 0;
    for (py::ssize_t i = 0; i < r.shape(0); i++)
        for (py::ssize_t j = 0; j < r.shape(1); j++)
            for (py::ssize_t k = 0; k < r.shape(2); k++)
                sum += r(i, j, k);
    return sum;
});
m.def("increment_3d", [](py::array_t<double> x) {
    auto r = x.mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false
    for (py::ssize_t i = 0; i < r.shape(0); i++)
        for (py::ssize_t j = 0; j < r.shape(1); j++)
            for (py::ssize_t k = 0; k < r.shape(2); k++)
                r(i, j, k) += 1.0;
}, py::arg().noconvert());

这两个代理类的区别就是:当只用从一个array_t<T>对象中读取数据时,我们使用unchecked<N>对它进行代理访问;当我们需要修改一个array_t<T>对象中的数据时,我们使用mutable_unchecked<N>对它进行代理访问。

同时Numpy array支持缓冲协议(buffer protocol),因此我们也可以通过.request()对其的缓冲区信息进行访问。

struct buffer_info {
    void *ptr;	// Pointer to the underlying storage
    py::ssize_t itemsize;	// Size of individual items in bytes
    std::string format;
    py::ssize_t ndim;		// Number of dimensions
    std::vector<py::ssize_t> shape; // Shape of the tensor (1 entry per dimension)
    std::vector<py::ssize_t> strides;	//Number of bytes between adjacent entries
};

其他详细信息参见文档:NumPy - pybind11 documentation。

2.2 Numpy VF(py::array_t)与CGAL mesh(Surface Mesh)之间的转换

// 将Numpy VF转换成CGAL mesh
CGAL_Mesh convert_mesh_from_Numpy_to_CGAL(py::array_t<double>& V, py::array_t<int>& F)
{
    CGAL_Mesh CGAL_mesh;
    // 获取V, F的信息
    py::buffer_info buf_v = V.request();
    py::buffer_info buf_f = F.request();
    if (buf_v.shape[1] != 3)
        throw std::runtime_error("vertex must be 3 dims ");
    if (buf_f.shape[1] != 3)
        throw std::runtime_error("face must be 3 dims ");

    auto v = V.unchecked<2>();
    auto f = F.unchecked<2>();

    // clear and reserve the mesh
    CGAL_mesh.clear();
    int vn = buf_v.shape[0];    // 顶点个数
    int fn = buf_f.shape[0];    // 面个数
    int e = 0;
    CGAL_mesh.reserve(vn, 2 * fn, e);

    //copy the vertices
    double x, y, z;
    for (py::ssize_t i = 0; i < v.shape(0); i++)
    {
        Point p;
        x = v(i, 0);
        y = v(i, 1);
        z = v(i, 2);
        p = Point(x, y, z);
        CGAL_mesh.add_vertex(p);
    }

    // copy the faces
    std::vector <int> vertices;
    for (py::ssize_t i = 0; i < f.shape(0); i++)
    {
        vertices.resize(3);
        vertices[0] = f(i, 0);
        vertices[1] = f(i, 1);
        vertices[2] = f(i, 2);
        CGAL_mesh.add_face(CGAL_Mesh::Vertex_index(vertices[0]),
            CGAL_Mesh::Vertex_index(vertices[1]),
            CGAL_Mesh::Vertex_index(vertices[2]));
    }

    return CGAL_mesh;
}

// 将CGAL mesh转换成Numpy VF
std::pair<py::array_t<double>, py::array_t<int>> convert_mesh_from_CGAL_to_Numpy(CGAL_Mesh& CGAL_mesh)
{
    //申请内存
    py::array_t<double> V = py::array_t<double>(CGAL_mesh.number_of_vertices() * 3);
    py::array_t<int> F = py::array_t<int>(CGAL_mesh.number_of_faces() * 3);
    std::pair<py::array_t<double>, py::array_t<int>> VF(V,F);

    V.resize({ int(CGAL_mesh.number_of_vertices()), 3 });
    F.resize({ int(CGAL_mesh.number_of_faces()), 3 });

    auto v = V.mutable_unchecked<2>();  // mutable_unchecked: can be writeable
    auto f = F.mutable_unchecked<2>();

    int i = 0;
    for (CGAL_Mesh::Vertex_index vd : vertices(CGAL_mesh))
    {
        v(i, 0) = CGAL_mesh.point(vd).x();
        v(i, 1) = CGAL_mesh.point(vd).y();
        v(i, 2) = CGAL_mesh.point(vd).z();
        i++;
    }

    i = 0;
    for (CGAL_Mesh::Face_index fd : faces(CGAL_mesh))
    {
        int j = 0;
        for (CGAL_Mesh::Vertex_index vd : CGAL::vertices_around_face(CGAL_mesh.halfedge(fd), CGAL_mesh))
        {
            f(i, j) = int(vd);
            j++;
        }
        i++;
    }

    return VF;
}

三 绑定CGAL算法示例

3.1 示例函数

在本文中我们尝试绑定[Polygon Mesh Processing](CGAL 5.5.1 - Polygon Mesh Processing: User Manual)中的isotropic_remeshing函数。

struct halfedge2edge
{
    halfedge2edge(const CGAL_Mesh& m, std::vector<edge_descriptor>& edges)
        : m_mesh(m), m_edges(edges)
    {}
    void operator()(const halfedge_descriptor& h) const
    {
        m_edges.push_back(edge(h, m_mesh));
    }
    const CGAL_Mesh& m_mesh;
    std::vector<edge_descriptor>& m_edges;
};

std::pair<py::array_t<double>, py::array_t<int>> isotropic_remeshing(py::array_t<double>& V, py::array_t<int>& F, double target_edge_length = 1, unsigned int nb_iter = 5)
{
    CGAL_Mesh mesh = convert_mesh_from_Numpy_to_CGAL(V, F);
    std::vector<edge_descriptor> border;
    PMP::border_halfedges(faces(mesh), mesh, boost::make_function_output_iterator(halfedge2edge(mesh, border)));
    PMP::split_long_edges(border, target_edge_length, mesh);
    PMP::isotropic_remeshing(faces(mesh), target_edge_length, mesh,
        CGAL::parameters::number_of_iterations(nb_iter)
        .protect_constraints(true));
    return convert_mesh_from_CGAL_to_Numpy(mesh);
}

3.2 绑定部分代码

PYBIND11_MODULE(numpy_cgal, m) {

    m.doc() = "The CGAL geometry algorithms which called via the Numpy array";
    m.def("isotropic_remeshing", &isotropic_remeshing, "remeshes a triangular mesh",
        py::arg("V"), py::arg("F"), 
        py::arg("target_edge_length") = 1, py::arg("nb_iter") = 5);

}

3.3 示例完整代码

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
namespace PMP = CGAL::Polygon_mesh_processing;
namespace py = pybind11;
typedef CGAL::Simple_cartesian<double>                          Kernal;
typedef Kernal::Point_3                                         Point;
typedef CGAL::Surface_mesh<Point>                               CGAL_Mesh;
typedef boost::graph_traits<CGAL_Mesh>::halfedge_descriptor     halfedge_descriptor;
typedef boost::graph_traits<CGAL_Mesh>::edge_descriptor         edge_descriptor;


// 将Numpy VF转换成CGAL mesh
CGAL_Mesh convert_mesh_from_Numpy_to_CGAL(py::array_t<double>& V, py::array_t<int>& F)
{
    CGAL_Mesh CGAL_mesh;
    // 获取V, F的信息
    py::buffer_info buf_v = V.request();
    py::buffer_info buf_f = F.request();
    // Validate
    if (buf_v.ndim != 2)
        throw std::runtime_error("Number of dimensions of `V` must be 2");

    if (buf_v.shape[1] != 3)
        throw std::runtime_error("Number of columns in `V` must be 3");

    if (buf_f.ndim != 2)
        throw std::runtime_error("Number of dimensions of `F` must be 2");

    if (buf_f.shape[1] != 3)
        throw std::runtime_error("Number of columns in `F` must be 3");

    auto v = V.unchecked<2>(); // unchecked: can be non - writeable
    auto f = F.unchecked<2>();

    // clear and reserve the mesh
    CGAL_mesh.clear();
    int vn = buf_v.shape[0];    // 顶点个数
    int fn = buf_f.shape[0];    // 面个数
    int e = 0;
    CGAL_mesh.reserve(vn, e, fn);

    //copy the vertices
    double x, y, z;
    for (py::ssize_t i = 0; i < v.shape(0); i++)
    {
        Point p;
        x = v(i, 0);
        y = v(i, 1);
        z = v(i, 2);
        p = Point(x, y, z);
        CGAL_mesh.add_vertex(p);
    }

    // copy the faces
    std::vector <int> vertices;
    for (py::ssize_t i = 0; i < f.shape(0); i++)
    {
        vertices.resize(3);
        vertices[0] = f(i, 0);
        vertices[1] = f(i, 1);
        vertices[2] = f(i, 2);
        CGAL_mesh.add_face(CGAL_Mesh::Vertex_index(vertices[0]),
            CGAL_Mesh::Vertex_index(vertices[1]),
            CGAL_Mesh::Vertex_index(vertices[2]));
    }

    return CGAL_mesh;
}

// 将CGAL mesh转换成Numpy VF
std::pair<py::array_t<double>, py::array_t<int>> convert_mesh_from_CGAL_to_Numpy(CGAL_Mesh& CGAL_mesh)
{
    //申请内存
    py::array_t<double> V = py::array_t<double>(CGAL_mesh.number_of_vertices() * 3);
    py::array_t<int> F = py::array_t<int>(CGAL_mesh.number_of_faces() * 3);
    std::pair<py::array_t<double>, py::array_t<int>> VF(V,F);

    V.resize({ int(CGAL_mesh.number_of_vertices()), 3 });
    F.resize({ int(CGAL_mesh.number_of_faces()), 3 });

    auto v = V.mutable_unchecked<2>();  // mutable_unchecked: can be writeable
    auto f = F.mutable_unchecked<2>();

    int i = 0;
    for (CGAL_Mesh::Vertex_index vd : vertices(CGAL_mesh))
    {
        v(i, 0) = CGAL_mesh.point(vd).x();
        v(i, 1) = CGAL_mesh.point(vd).y();
        v(i, 2) = CGAL_mesh.point(vd).z();
        i++;
    }

    i = 0;
    for (CGAL_Mesh::Face_index fd : faces(CGAL_mesh))
    {
        int j = 0;
        for (CGAL_Mesh::Vertex_index vd : CGAL::vertices_around_face(CGAL_mesh.halfedge(fd), CGAL_mesh))
        {
            f(i, j) = int(vd);
            j++;
        }
        i++;
    }

    return VF;
}

struct halfedge2edge
{
    halfedge2edge(const CGAL_Mesh& m, std::vector<edge_descriptor>& edges)
        : m_mesh(m), m_edges(edges)
    {}
    void operator()(const halfedge_descriptor& h) const
    {
        m_edges.push_back(edge(h, m_mesh));
    }
    const CGAL_Mesh& m_mesh;
    std::vector<edge_descriptor>& m_edges;
};

std::pair<py::array_t<double>, py::array_t<int>> isotropic_remeshing(py::array_t<double>& V, py::array_t<int>& F, double target_edge_length = 1, unsigned int nb_iter = 5)
{
    CGAL_Mesh mesh = convert_mesh_from_Numpy_to_CGAL(V, F);
    std::vector<edge_descriptor> border;
    PMP::border_halfedges(faces(mesh), mesh, boost::make_function_output_iterator(halfedge2edge(mesh, border)));
    PMP::split_long_edges(border, target_edge_length, mesh);
    PMP::isotropic_remeshing(faces(mesh), target_edge_length, mesh,
        CGAL::parameters::number_of_iterations(nb_iter)
        .protect_constraints(true));
    return convert_mesh_from_CGAL_to_Numpy(mesh);
}

// 绑定代码
PYBIND11_MODULE(numpy_cgal, m) 
{
    m.doc() = "The CGAL geometry algorithms which called via the Numpy array";
    m.def("isotropic_remeshing", &isotropic_remeshing, "remeshes a triangular mesh",
        py::arg("V"), py::arg("F"), 
        py::arg("target_edge_length") = 1, py::arg("nb_iter") = 5);
}

这里为了图方便将所有代码全写在了一个源文件里,正常来说应当将绑定代码和绑定对象分开。

四 编译生成和测试

4.1 编译生成pyd文件

在这个示例中,我是直接使用Visual Studio编译生成pyd文件。操作很简单,首先是按照这篇文章:pybind11学习 | VS2022下安装配置在VS中配置好pybind11,然后按照这篇文章:CGAL的安装与在VS中的配置在VS中配置好CGAL。在pybind11和CGAL都配置成功的前提下,生成解决方案即可得到pyd文件。如果电脑上没装Visual Studio,也可以尝试使用CMake进行构建,也是十分简单的,只需在这篇文章:pybind11学习 | 使用CMake构建系统并生成pyd文件的示例基础上在CMakeLists.txt中添加CGAL的库文件和头文件即可。

4.2 Python调用测试

测试代码如下:

import igl
import numpy as np
from CGAL import CGAL_Polygon_mesh_processing
from CGAL.CGAL_Polyhedron_3 import Polyhedron_3
import os.path


def isotropic_remeshing_off(filename, targetedgelength, remeshIter):
    if os.path.isfile(filename + ".off"):
        V, F, _ = igl.read_off(filename + "_iso_remesh.off", False)
        return F, V
    P = Polyhedron_3(filename + ".off")
    flist = []
    for fh in P.facets():
        flist.append(fh)
    CGAL_Polygon_mesh_processing.isotropic_remeshing(flist, targetedgelength, P, remeshIter)
    P.write_to_file(filename + "_iso_remesh.off")
    V, F, _ = igl.read_off(filename + "_iso_remesh.off", False)
    return F, V


def isotropic_remeshing_bindings(V, F, targetedgelength, remeshIter):
    import numpy_cgal
    V, F = numpy_cgal.isotropic_remeshing(V, F, targetedgelength, remeshIter)
    return F, V


if __name__ == "__main__":
    v, f = igl.read_triangle_mesh("test_mesh.off", dtypef = np.float64)
    f_remeshed, v_remeshed = isotropic_remeshing_bindings(v, f, 1, 5)

其中,第一个isotropic_remeshing_off函数是通过文章【CGAL+Python】安装CGAL的Python绑定中提到的绑定加上读写off文件实现的CGAL重新网格化算法调用。第二个isotropic_remeshing_bindings函数是通过调用pybind11生成的Python拓展模块(即本文的方法,numpy_cgal.pyd为上一小节生成的pyd文件)实现的。

调试结果如下:

在这里插入图片描述

可以看到,函数输入为ndarray类型,输出仍然为ndarray类型,且成功重新网格化,测试完毕。

五 总结

本文主要介绍一种在Python代码中调用C++第三方库API的思路。主要目的是抛砖引玉,本文的方法不一定是最好的,仅供大家参考学习。

参考和拓展

[1] CGAL 5.5.1 - Surface Mesh: User Manual

[2] CGAL 5.5.1 - Polygon Mesh Processing: User Manual

[3] pybind11 documentation

[4] pybind11—opencv图像处理(numpy数据交换) - 简书 (jianshu.com)

[5] pybind11-wrapped CGAL (github.com)

[6] cmpute/cgal.py: Pybind11 binding of cgal. Aimed at extending and easy use (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128569.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day04 IDEA数组

第一部分 : IDEA开发工具 参见 &#xff1a;IEDA的安装请参考文件夹PPT中的 04_IDEA.ppt 1.数组 1.1 数组介绍 ​ 数组就是存储数据长度固定的容器&#xff0c;存储多个数据的数据类型要一致。 1.2 数组的定义格式 1.2.1 第一种格式 ​ 数据类型[] 数组名 ​ 示例&…

【Linux】进程创建|进程终止|进程等待|进程程序替换

索引1.进程创建fork函数初识&#x1f60a;我们先来看这样的一个程序:写时拷贝fork返回值的三个问题2.进程终止进程退出场景进程常见退出方法进程退出码&#xff1a;3.进程等待进程等待的方法wait方法waitpid方法获取子进程status进程的阻塞等待方式&#xff1a;进程的非阻塞等待…

vue实现导入表格数据【纯前端实现】

一、文章引导 #mermaid-svg-3VJi5rNvrLDOy2MT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3VJi5rNvrLDOy2MT .error-icon{fill:#552222;}#mermaid-svg-3VJi5rNvrLDOy2MT .error-text{fill:#552222;stroke:#55222…

WSL Ubuntu SSH

WSL中的IP wsl中的ubuntu的ip是动态分配的&#xff0c;每次开机都不一样&#xff0c;而且动态分配的ip和windows系统中的ip不在同一网段&#xff0c;但是我发现在windows中能ping通wsl中ubuntu的ip&#xff0c;这说明子系统与虚拟机不同&#xff0c;在查看ubuntu系统ip时&…

第010课 - docker安装mysql

第010课 - docker安装mysql docker run -p 3306:3306 --name mysql \ -v /mydata/mysql/log:/var/log/mysql \ # 这个里面是容器内mysql相关的日志 -v /mydata/mysql/data:/var/lib/mysql \ # 这个里面是msyql数据相关的内容 -v /mydata/mysql/conf:/etc/mysql \ # 这个里面是容…

数据结构进阶 二叉树OJ题

作者&#xff1a;小萌新 专栏&#xff1a;数据结构进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍几道二叉树的oj题 二叉树OJ题题目一 根据二叉树创建字符串题目二 二叉树的层序遍历题目三 二叉树的最近公共祖先题目一 根据…

华为机试 HJ35 蛇形矩阵

华为机试 HJ35 蛇形矩阵[HJ35 蛇形矩阵](https://www.nowcoder.com/practice/649b210ef44446e3b1cd1be6fa4cab5e)方法一&#xff1a;顺序填表方法2&#xff1a;数学规律HJ35 蛇形矩阵 描述 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 例如&#xff0c;当输入5…

【数据结构】链表基础知识讲解

文章目录链表链表的结构使用链表的优点模拟实现链表链表 在之前的学习中我们讲解了顺序表ArrayList&#xff0c;Java模拟实现顺序表&#xff0c;如果需要大家可以去看一看&#xff0c;顺序表底层的实现逻辑其实就是数组&#xff0c;在物理存储结构和逻辑上都是连续的&#xff…

Eth 03 -以太网驱动Eth的配置

以太网的配置,下面这张图描述了以太网的配置参数: EthCtrlConfig:单个控制器的配置EthCtrlEnableMii :启用/禁用用于收发器访问的媒体独立接口 (MII)EthCtrlEnableRxInterrupt:启用/禁用接收中断EthCtrlEnableTxInterrupt:启用/禁用传输中断EthCtrlIdx:指定已配置控制…

【BUUCTF】MISC(第一页wp)

文章目录签到金三胖二维码你竟然赶我走大白N种方法解决乌镇峰会种图基础破解wireshark文件中的秘密图片exifLSBLSB隐写&#xff08;最低有效位隐写&#xff09;&#xff1a;zip伪加密ZIP 文件由**三个部分**组成&#xff1a;**压缩源文件数据区**&#xff1a;**压缩源文件目录区…

FS4412环境搭建

目录 一、开发板硬件资源介绍 二、交叉开发环境 2.1安装交叉编译工具链 2.2配置全局变量​编辑 2.3测试​编辑 2.4终端 2.5安装串口驱动 2.6上电测试 三、地址映射表 一、开发板硬件资源介绍 中间红色的是samsung的主控&#xff0c;四个粉色的256M的内存条&#xff0…

STM32F4SysTick记录

滴哒主要用于延时和实时系统 模板为原子串口实验源码&#xff0c;入口为24行 120行为滴哒定时器的CTRL寄存器位时钟源设置 这个参数的必要性是用于溢出时间的计算参数之一 可以设置为HCLK或HCLK的8分频 延时函数理解 设置LOAD是设置重装载值 设置VAL清空计数值以及标志位 …

强大的ANTLR4(2)

每次在命令行里输入文本有点麻烦&#xff0c;可以将hello slb保存于hello.txt文本文件中&#xff0c;然后运行命令&#xff1a; antlr4-parse Hello.g4 r -tokens hello.txt出现如下内容&#xff1a; [0,0:4hello,<hello>,1:0] [1,6:8slb,<ID>,1:6] [2,9:8<EO…

JDBC开荒

docker 创建MySQL 一、简介 Java DataBase Connectivity &#xff0c;是Java程序访问数据库的标准接口 Java访问DB的时候&#xff0c;并不是直接通过TCP连接的&#xff0c;而是通过JDBC接口&#xff0c;而JDBC接口又是通过JDBC驱动来访问的 JDBC是Java标准库自带的&#xff0…

(HP)next.js入门

推荐文档&#xff1a;生成<head> - 《next.js v7.0 中文文档》 - 书栈网 BookStack 1&#xff0c;解决的问题 SPA单页面应用的两个问题&#xff1a;首屏加载过慢&#xff0c;不能SEO(搜索引擎抓取&#xff09; 2&#xff0c;它是一个react服务端渲染框架 3&#xff0c;…

ArcGIS去除黑边方法汇总

概述 在使用ArcGIS对影像进行应用的时候&#xff0c;如果出现了黑边&#xff0c;除了影响美观之外&#xff0c;进行镶嵌处理也可能会有问题&#xff0c;这里&#xff0c;我们介绍一下几种ArcGIS去除黑边的方法&#xff0c;希望能够对大家有所帮助。 数据来源 教程所使用的实…

【C++进阶】类型转换

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…

蓝桥杯Python练习题8-查找整数

资源限制   内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述   给出一个包含n个整数的数列&#xff0c;问整数a在数列中的第一次出现是第几个。 输入格式   第一行包含一个整数n。  …

欢迎谷歌回归中国,但有前提!李彦宏也发了条朋友圈

2018年&#xff0c;Google部分功能成功回归中国大陆 周一&#xff0c;人民日报在海外社交媒体平台Twitter和Facebook上刊文&#xff0c;针对谷歌计划以过滤版搜索引擎重返中国大陆的消息回应称&#xff0c;欢迎谷歌重返中国大陆&#xff0c;但前提是必须遵守中国法律。李彦宏在…

云计算IaaS、PaaS(iPaaS/aPaaS)以及SaaS以及发展趋势

一、云计算IaaS、PaaS以及SaaS架构 云计算涉及了很多产品与技术&#xff0c;表面上看起来的确有点纷繁复杂&#xff0c;但是云计算本身还是有迹可循和有理可依的&#xff0c;下面介绍一套云计算的架构&#xff0c;具体请看图&#xff1a; 上面这个云架构共分为服务和管理这两…