OpenCV+python: HoughLines accumulator access since 3.4.2 - python

In OpenCV 3.4.2 the option to return the number of votes (accumulator value) for each line returned by HoughLines() was added. In python this seems to be supported as well as read in the python docstring of my OpenCV installation:
"Each line is represented by a 2 or 3 element vector (ρ, θ) or (ρ, θ, votes) ."
It is also included in the docs (with some broken formatting).
However I can find no way to return the 3 element option (ρ, θ, votes) in python.
Here is code demonstrating the problem:
import numpy as np
import cv2
print('OpenCV should be at least 3.4.2 to test: ', cv2.__version__)
image = np.eye(10, dtype='uint8')
lines = cv2.HoughLines(image, 1, np.pi/180, 5)
print('(number of lines, 1, output vector dimension): ', lines.shape)
print(lines)
outputs
OpenCV should be at least 3.4.2 to test: 3.4.2
(number of lines, 1, output vector dimension): (3, 1, 2)
[[[ 0. 2.3212879]]
[[ 1. 2.2340214]]
[[-1. 2.4609141]]]
The desired behavior is an extra column with the amount of votes each line received. With the vote values more advanced options than the standard thresholding can be applied, as such it has been often requested and asked about on SE (here, here, here and here) with sometimes the equivalent for HoughCircles(). However both the questions and answers (such as modifying source and recompiling) are from before it was added officially, and therefore do not apply to the current situation.

As of vanilla OpenCV 3.4.3, you can't use this functionality from Python.
How it Works in C++
First of all in the implementation of HoughLines, we can see code that selects the type of the output array lines:
int type = CV_32FC2;
if (lines.fixedType())
{
type = lines.type();
CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Wrong type of output lines");
}
We can then see this parameter used in implementation of HoughLinesStandard when populating lines:
if (type == CV_32FC2)
{
_lines.at<Vec2f>(i) = Vec2f(line.rho, line.angle);
}
else
{
CV_DbgAssert(type == CV_32FC3);
_lines.at<Vec3f>(i) = Vec3f(line.rho, line.angle, (float)accum[idx]);
}
Similar code can be seen in HoughLinesSDiv.
Based on this, we need to pass in an _OutputArray that is fixed type, and stores 32bit floats in 3 channels. How to make a fixed type (but not fixed size, since the algorithm needs to be able to resize it) _OutputArray? Let's look at the implementation again:
A generic cv::Mat is not fixed type, neither is cv::UMat
One option is std::vector<cv::Vec3f>
Another option is cv::Mat3f (that's a cv::Matx<_Tp, m, n>)
Sample Code:
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat image(cv::Mat::eye(10, 10, CV_8UC1) * 255);
cv::Mat2f lines2;
cv::HoughLines(image, lines2, 1, CV_PI / 180, 4); // runs the actual detection
std::cout << lines2 << "\n";
cv::Mat3f lines3;;
cv::HoughLines(image, lines3, 1, CV_PI / 180, 4); // runs the actual detection
std::cout << lines3 << "\n";
return 0;
}
Console Output:
[0, 2.3212879;
1, 2.2340214;
-1, 2.4609141]
[0, 2.3212879, 10;
1, 2.2340214, 6;
-1, 2.4609141, 6]
How the Python Wrapper Works
Let's look at the autogenerated code wrapping the HoughLines function:
static PyObject* pyopencv_cv_HoughLines(PyObject* , PyObject* args, PyObject* kw)
{
using namespace cv;
{
PyObject* pyobj_image = NULL;
Mat image;
PyObject* pyobj_lines = NULL;
Mat lines;
double rho=0;
double theta=0;
int threshold=0;
double srn=0;
double stn=0;
double min_theta=0;
double max_theta=CV_PI;
const char* keywords[] = { "image", "rho", "theta", "threshold", "lines", "srn", "stn", "min_theta", "max_theta", NULL };
if( PyArg_ParseTupleAndKeywords(args, kw, "Oddi|Odddd:HoughLines", (char**)keywords, &pyobj_image, &rho, &theta, &threshold, &pyobj_lines, &srn, &stn, &min_theta, &max_theta) &&
pyopencv_to(pyobj_image, image, ArgInfo("image", 0)) &&
pyopencv_to(pyobj_lines, lines, ArgInfo("lines", 1)) )
{
ERRWRAP2(cv::HoughLines(image, lines, rho, theta, threshold, srn, stn, min_theta, max_theta));
return pyopencv_from(lines);
}
}
PyErr_Clear();
// Similar snippet handling UMat...
return NULL;
}
To summarize this, it tries to convert the object passed in the lines parameter to a cv::Mat, and then it calls cv::HoughLines with the cv::Mat as the output parameter. (If this fails, then it tries the same thing with cv::UMat) Unfortunately, this means that there is no way to give cv::HoughLines a fixed type lines, so as of 3.4.3 this functionality is inaccessible from Python.
Solutions
The only solutions, as far as I can see, involve modifying the OpenCV source code, and rebuilding.
Quick Hack
This is trivial, edit the implementation of cv::HoughLines and change the default type to be CV_32FC3:
int type = CV_32FC3;
However this means that you will always get the votes (which also means that the OpenCL optimization, if present, won't get used).
Better Patch
Add an optional boolean parameter return_votes with default value false. Modify the code such that when return_votes is true, the type is forced to CV_32FC3.
Header:
CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI,
bool return_votes = false );
Implementation:
void HoughLines( InputArray _image, OutputArray lines,
double rho, double theta, int threshold,
double srn, double stn, double min_theta, double max_theta,
bool return_votes )
{
CV_INSTRUMENT_REGION()
int type = CV_32FC2;
if (return_votes)
{
type = CV_32FC3;
}
else if (lines.fixedType())
{
type = lines.type();
CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Wrong type of output lines");
}
// the rest...

There is a new python binding (opencv 4.5.1)
doc : cv.HoughLinesWithAccumulator

Related

pybind11 passing numpy array to C++ jumbles up elements

I have a C++ function that takes in three 1D numpy arrays as an input:
py::array_t<double> myFunc( py::array_t<double> xcoords, py::array_t<double> ycoords, py::array_t<double> zcoords, const int nparts)
{
// process numpy array inputs from the python side
auto buf2 = xcoords.request();
double *x = (double *)buf2.ptr;
auto buf3 = ycoords.request();
double *y = (double *)buf3.ptr;
auto buf4 = zcoords.request();
double *z = (double *)buf4.ptr;
// allocate mem for the output numpy array
py::array_t<double> result = py::array_t<double>(nbins);
auto buf5 = result.request();
// c++ counterpart of the result array
double *hist = (double *)buf5.ptr;
// var declarations
int i;
// loop over all points
for (i = 0; i < nparts; i++)
{
std::cout << i << " " << x[i] << " " << y[i] << " " << z[i] << std::endl;
// do stuff to populate elements of result
}
return result;
}
I create these three arrays in python by:
points = np.random.rand(N,3) * L
and pass into the function
res = module.myFunc(points[:,0], points[:,1], points[:,2], 10)
An example set of values would be:
4.6880498641905195 0.335265721430103 4.095097368241972
1.287345441223418 0.18213643934310575 1.9076717100770817
3.9083692529696985 3.58692445785723 1.2090842532316792
2.0727395137272273 0.8991931179824342 3.428767449961339
2.8468968826288634 4.935019700055825 1.8476045204842446
2.2248370075368733 4.684880446417251 3.7368688164101376
3.4876671749517643 1.7266611398614629 0.22011783388784623
1.5372784463757512 4.6664761047248575 4.148012219052029
3.10720337544295 4.670106619033467 2.8297722540139763
Which is what the python code prints out when I check the values in points. However, the C++ code produces the following output:
0 4.68805 0.335266 4.0951
1 0.335266 4.0951 1.28735
2 4.0951 1.28735 0.182136
3 1.28735 0.182136 1.90767
4 0.182136 1.90767 3.90837
5 1.90767 3.90837 3.58692
6 3.90837 3.58692 1.20908
7 3.58692 1.20908 2.07274
8 1.20908 2.07274 0.899193
9 2.07274 0.899193 3.42877
The values somehow become jumbled up. points[1,0] ends up duplicating points[0,1], etc. It looks like a silly memory/indexing problem, but I can't figure it out. What am I missing?
Edit: There's a statement in the documentation that says: "Data in NumPy arrays is not guaranteed to packed in a dense manner; furthermore, entries can be separated by arbitrary column and row strides. Sometimes, it can be useful to require a function to only accept dense arrays using either the C (row-major) or Fortran (column-major) ordering. This can be accomplished via a second template argument with values py::array::c_style or py::array::f_style."
So instead of array_t<double>, I put <double, py::array::c_style | py::array::forcecast>. It seems to work. I don't understand why numpy entries can be jumbled up by default. I would appreciate any insight into this strange behavior.

Text Zooming Effect Trembling

I created a text zooming animation with MoviePy and Gizeh. However the results turned out different when the code is run on different OS. The following two gifs are generated by the same python code. The effect looks pretty good on MacOS, but poor on Linux.
pretty good on MacOS
effect trembling on Linux
I'm sure that the libraries(MoviePy, Gizeh, Cairocffi, cffi, etc.) versions are the same on both systems. I also tried high fps or downsize the test but it didn't work.
I don't know if this may be an issue or bad coding from my side.
I tried to find out what caused this issue. I found the return values of
the function ctx.text_extents(txt) in gizeh.py Line 489 ( same as cairo_text_extents, in cairocffi library, context.py, Line 2003) varies every frame on Linux. And on Mac the function always return the same values. However, even if I fixed the values, the effect remained the same.
import sys
import gizeh as gz
from moviepy.editor import *
def make_frame(t):
surface = gz.Surface(360, 180, bg_color=(0, 0, 0))
text = gz.text('ENLARGE', fontsize=40, fontfamily='Arial Unicode',
fill=(255,255,255), xy=(180, 90), fontweight='normal')
text = text.scale(1+0.5*t, center=[180, 90])
text.draw(surface)
return surface.get_npimage()
child_clip = VideoClip(make_frame, duration=3)
child_clip.write_gif('Result.gif',fps=24)
I have locate this problem in Cairo library. I wrote a simple demo to create a sequence of images enlarging the text string. And I printed the return values of the function cairo_text_extents() which indicated the extents of the string. I run the same code on Linux and MacOS. The values stayed the same on MacOS. While on Linux, the values varied every frame. Tring to figure out why.
#include "cairo.h"
#include <stdio.h>
int draw (const char* filename, float t)
{
cairo_surface_t *ImageSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,360,360);
cairo_t *cr = cairo_create(ImageSurface);
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_paint (cr);
cairo_matrix_t matrix;
float scale = (1+0.5*t);
float offset = -(0.5*t)/2*355;
matrix.xx = scale;
matrix.xy = 0;
matrix.x0 = offset;
matrix.yx = 0;
matrix.yy = scale;
matrix.y0 = offset;
cairo_set_matrix(cr,&matrix);
cairo_font_options_t *font_options = cairo_font_options_create();
cairo_font_options_set_antialias(font_options,CAIRO_ANTIALIAS_NONE);
cairo_font_options_set_hint_style(font_options,CAIRO_HINT_STYLE_NONE);
cairo_font_options_set_hint_metrics(font_options,CAIRO_HINT_METRICS_OFF);
cairo_set_font_options(cr,font_options);
cairo_select_font_face (cr, "Arial Unicode",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr,60);
cairo_text_extents_t *text_extents = new cairo_text_extents_t;
cairo_text_extents(cr,"ENLARGE",text_extents);
printf("%f %f %f %f %f %f\n",text_extents->width,
text_extents->height,
text_extents->x_advance,
text_extents->y_advance,
text_extents->x_bearing,
text_extents->y_bearing);
int x_shift = -text_extents->width/2-text_extents->x_bearing;
int y_shift = -text_extents->height/2-text_extents->y_bearing;
int x = 180 + x_shift;
int y = 180 + y_shift;
cairo_move_to (cr, x, y);
cairo_text_path(cr,"ENLARGE");
cairo_set_source_rgb(cr,0,0,0);
cairo_fill(cr);
cairo_surface_write_to_png(ImageSurface,filename);
cairo_font_options_destroy(font_options);
delete text_extents;
cairo_destroy(cr);
cairo_surface_destroy(ImageSurface);
return 0;
}
int main()
{
int i = 0;
for(i = 0;i<10;i++)
{
char filename[256] = "";
sprintf(filename,"result_%d.png",i);
float t = 1.0/10 * i;
draw((const char*)filename,t);
}
printf("hello world!!!\n");
getchar();
return 0;
}
You have to disable font hinting in Cairo (or fontconfig). I guess this means to set CAIRO_HINT_METRICS_OFF and/or CAIRO_HINT_STYLE_NONE. However, I do not even know what Gizeh is and so I cannot tell you how to disable hinting there.

why cv2.bitwise_and function of opencv-python returns four element array on single scalar value

I'm trying to understand cv2.bitwise_and function of opencv-python. So I tried it as:
import cv2
cv2.bitwise_and(1,1)
above code returns
array([[1.],
[0.],
[0.],
[0.]])
I don't understand why it returns this.
Documentation says :
dst(I) = src1(I) ^ src2(I) if mask(I) != 0
according to this output should be single value 1. where am I going wrong?
The documentation says clearly that the function performs the operations dst(I) = src1(I) ^ src2(I) if mask(I) != 0 if the inputs are two arrays of the same size.
So try:
import numpy as np # Opecv works with numpy arrays
import cv2
a = np.uint8([1])
b = np.uint8([1])
cv2.bitwise_and(a, b)
That code returns:
array([[1]], dtype=uint8)
That is a one dimensional array containing the number 1.
The documentation also mentions that the operation can be done with an array and a scalar, but not with two scalars, so the input cv2.bitwise_and(1,1) is not correct.
The documentation is a bit vague in this aspect, and it will take some digging through both source, as well as docs to properly explain what's happening.
First of all -- scalars. In context of data types, we have a cv::Scalar, which is actually a specialization of template cv::Scalar_. It represents a 4-element vector, and derives from cv::Vec -- a template representing a fixed size vector, which is again a special case of cv::Matx, a class representing small fixed size matrices.
That's scalar the data type, however in the context of the bitwise_and (and related functions), the concept what is and isn't a scalar is much looser -- the function in fact is not aware that gave it an instance of cv::Scalar.
If you look at the signature of the function, you'll notice that the inputs are InputArrays. So the inputs are always arrays, but it's possible that some of their properties differ (kind, element type, size, dimensionality, etc.).
The specific check in the code verifies that size, type and kind match. If that's the case (and in your scenario it is), the operation dst(I) = src1(I) ^ src2(I) if mask(I) != 0 runs.
Otherwise it will check whether one of the input arrays represents a scalar. It uses function checkScalar to do that, and the return statement says most of it:
return sz == Size(1, 1)
|| sz == Size(1, cn) || sz == Size(cn, 1)
|| (sz == Size(1, 4) && sc.type() == CV_64F && cn <= 4);
Anything that has size 1 x 1
Anything that size 1 x cn or cn x 1 (where cn is the number of channels if the other input array).
Anything that has size 1 x 4 and elements are 64bit floating point values, but only when the other input array has 4 or fewer channels.
The last case matches both the default cv::Scalar (which, as we have seen earlier, is a cv::Matx<double,4,1>), as well as cv::Mat(4,1,CF_64F).
As an intermission, let's test some of what we learned above.
Code:
cv::Scalar foo(1), bar(1);
cv::Mat result;
cv::bitwise_and(foo, bar, result);
std::cout << result << '\n';
std::cout << "size : " << result.size() << '\n';
std::cout << "type==CV_64FC1 : " << (result.type() == CV_64FC1 ? "yes" : "no") << '\n';
Output:
[1;
0;
0;
0]
size : [1 x 4]
type==CV_64FC1 : yes
Having covered the underlying C++ API, let's look at the Python bindings. The generator that creates the wrappers for Python API is fairly complex, so let's skip that, and instead inspect a relevant snippet of what it generates for bitwise_and:
using namespace cv;
{
PyObject* pyobj_src1 = NULL;
Mat src1;
PyObject* pyobj_src2 = NULL;
Mat src2;
PyObject* pyobj_dst = NULL;
Mat dst;
PyObject* pyobj_mask = NULL;
Mat mask;
const char* keywords[] = { "src1", "src2", "dst", "mask", NULL };
if( PyArg_ParseTupleAndKeywords(args, kw, "OO|OO:bitwise_and", (char**)keywords, &pyobj_src1, &pyobj_src2, &pyobj_dst, &pyobj_mask) &&
pyopencv_to(pyobj_src1, src1, ArgInfo("src1", 0)) &&
pyopencv_to(pyobj_src2, src2, ArgInfo("src2", 0)) &&
pyopencv_to(pyobj_dst, dst, ArgInfo("dst", 1)) &&
pyopencv_to(pyobj_mask, mask, ArgInfo("mask", 0)) )
{
ERRWRAP2(cv::bitwise_and(src1, src2, dst, mask));
return pyopencv_from(dst);
}
}
PyErr_Clear();
We can see that parameters that correspond to InputArray or OutputArray are loaded into a cv::Mat instance. Let's look at the part of pyopencv_to that corresponds to your scenario:
if( PyInt_Check(o) )
{
double v[] = {static_cast<double>(PyInt_AsLong((PyObject*)o)), 0., 0., 0.};
m = Mat(4, 1, CV_64F, v).clone();
return true;
}
A cv::Mat(4, 1, CV_64F) (recall from earlier that this fits the test for scalar) containing the input integer cast to double, with the remaining 3 position padded with zeros.
Since no destination is provided, a Mat will be allocated automatically, of the same size and type as inputs. On return to Python, the Mat will become a numpy array.

returning numpy arrays via pybind11

I have a C++ function computing a large tensor which I would like to return to Python as a NumPy array via pybind11.
From the documentation of pybind11, it seems like using STL unique_ptr is desirable.
In the following example, the commented out version works, whereas the given one compiles but fails at runtime ("Unable to convert function return value to a Python type!").
Why is the smartpointer version failing? What is the canonical way to create and return a NumPy array?
PS: Due to program structure and size of the array, it is desirable to not copy memory but create the array from a given pointer. Memory ownership should be taken by Python.
typedef typename py::array_t<double, py::array::c_style | py::array::forcecast> py_cdarray_t;
// py_cd_array_t _test()
std::unique_ptr<py_cdarray_t> _test()
{
double * memory = new double[3]; memory[0] = 11; memory[1] = 12; memory[2] = 13;
py::buffer_info bufinfo (
memory, // pointer to memory buffer
sizeof(double), // size of underlying scalar type
py::format_descriptor<double>::format(), // python struct-style format descriptor
1, // number of dimensions
{ 3 }, // buffer dimensions
{ sizeof(double) } // strides (in bytes) for each index
);
//return py_cdarray_t(bufinfo);
return std::unique_ptr<py_cdarray_t>( new py_cdarray_t(bufinfo) );
}
A few comments (then a working implementation).
pybind11's C++ object wrappers around Python types (like pybind11::object, pybind11::list, and, in this case, pybind11::array_t<T>) are really just wrappers around an underlying Python object pointer. In this respect there are already taking on the role of a shared pointer wrapper, and so there's no point in wrapping that in a unique_ptr: returning the py::array_t<T> object directly is already essentially just returning a glorified pointer.
pybind11::array_t can be constructed directly from a data pointer, so you can skip the py::buffer_info intermediate step and just give the shape and strides directly to the pybind11::array_t constructor. A numpy array constructed this way won't own its own data, it'll just reference it (that is, the numpy owndata flag will be set to false).
Memory ownership can be tied to the life of a Python object, but you're still on the hook for doing the deallocation properly. Pybind11 provides a py::capsule class to help you do exactly this. What you want to do is make the numpy array depend on this capsule as its parent class by specifying it as the base argument to array_t. That will make the numpy array reference it, keeping it alive as long as the array itself is alive, and invoke the cleanup function when it is no longer referenced.
The c_style flag in the older (pre-2.2) releases only had an effect on new arrays, i.e. when not passing a value pointer. That was fixed in the 2.2 release to also affect the automatic strides if you specify only shapes but not strides. It has no effect at all if you specify the strides directly yourself (as I do in the example below).
So, putting the pieces together, this code is a complete pybind11 module that demonstrates how you can accomplish what you're looking for (and includes some C++ output to demonstrate that is indeed working correctly):
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
PYBIND11_PLUGIN(numpywrap) {
py::module m("numpywrap");
m.def("f", []() {
// Allocate and initialize some data; make this big so
// we can see the impact on the process memory use:
constexpr size_t size = 100*1000*1000;
double *foo = new double[size];
for (size_t i = 0; i < size; i++) {
foo[i] = (double) i;
}
// Create a Python object that will free the allocated
// memory when destroyed:
py::capsule free_when_done(foo, [](void *f) {
double *foo = reinterpret_cast<double *>(f);
std::cerr << "Element [0] = " << foo[0] << "\n";
std::cerr << "freeing memory # " << f << "\n";
delete[] foo;
});
return py::array_t<double>(
{100, 1000, 1000}, // shape
{1000*1000*8, 1000*8, 8}, // C-style contiguous strides for double
foo, // the data pointer
free_when_done); // numpy array references this parent
});
return m.ptr();
}
Compiling that and invoking it from Python shows it working:
>>> import numpywrap
>>> z = numpywrap.f()
>>> # the python process is now taking up a bit more than 800MB memory
>>> z[1,1,1]
1001001.0
>>> z[0,0,100]
100.0
>>> z[99,999,999]
99999999.0
>>> z[0,0,0] = 3.141592
>>> del z
Element [0] = 3.14159
freeing memory # 0x7fd769f12010
>>> # python process memory size has dropped back down
I recommend using ndarray. A foundational principle is that the underlying data is never copied unless explicitly requested (or you quickly end up with huge inefficiencies). Below is an example of it in use, but there are other features I haven't shown, including conversion to Eigen arrays (ndarray::asEigen(array)), which makes it pretty powerful.
Header:
#ifndef MYTENSORCODE_H
#define MYTENSORCODE_H
#include "ndarray_fwd.h"
namespace myTensorNamespace {
ndarray::Array<double, 2, 1> myTensorFunction(int param1, double param2);
} // namespace myTensorNamespace
#endif // include guard
Lib:
#include "ndarray.h"
#include "myTensorCode.h"
namespace myTensorNamespace {
ndarray::Array<double, 2, 1> myTensorFunction(int param1, double param2) {
std::size_t const size = calculateSize();
ndarray::Array<double, 2, 1> array = ndarray::allocate(size, size);
array.deep() = 0; // initialise
for (std::size_t ii = 0; ii < size; ++ii) {
array[ii][ndarray::view(ii, ii + 1)] = 1.0;
}
return array;
}
} // namespace myTensorNamespace
Wrapper:
#include "pybind11/pybind11.h"
#include "ndarray.h"
#include "ndarray/pybind11.h"
#include "myTensorCode.h"
namespace py = pybind11;
using namespace pybind11::literals;
namespace myTensorNamespace {
namespace {
PYBIND11_MODULE(myTensorModule, mod) {
mod.def("myTensorFunction", &myTensorFunction, "param1"_a, "param2"_a);
}
} // anonymous namespace
} // namespace myTensorNamespace

Python to C for loop conversion

I have the following python code:
r = range(1,10)
r_squared = []
for item in r:
print item
r_squared.append(item*item)
How would I convert this code to C? Is there something like a mutable array in C or how would I do the equivalent of the python append?
simple array in c.Arrays in the C are Homogenous
int arr[10];
int i = 0;
for(i=0;i<sizeof(arr);i++)
{
arr[i] = i; // Initializing each element seperately
}
Try using vectors in C go through this link
/ vector-usage.c
#include <stdio.h>
#include "vector.h"
int main() {
// declare and initialize a new vector
Vector vector;
vector_init(&vector);
// fill it up with 150 arbitrary values
// this should expand capacity up to 200
int i;
for (i = 200; i > -50; i--) {
vector_append(&vector, i);
}
// set a value at an arbitrary index
// this will expand and zero-fill the vector to fit
vector_set(&vector, 4452, 21312984);
// print out an arbitrary value in the vector
printf("Heres the value at 27: %d\n", vector_get(&vector, 27));
// we're all done playing with our vector,
// so free its underlying data array
vector_free(&vector);
}
Arrays in C are mutable by default, in that you can write a[i] = 3, just like Python lists.
However, they're fixed-length, unlike Python lists.
For your problem, that should actually be fine. You know the final size you want; just create an array of that size, and assign to the members.
But of course there are problems for which you do need append.
Writing a simple library for appendable arrays (just like Python lists) is a pretty good learning project for C. You can also find plenty of ready-made implementations if that's what you want, but not in the standard library.
The key is to not use a stack array, but rather memory allocated on the heap with malloc. Keep track of the pointer to that memory, the capacity, and the used size. When the used size reaches the capacity, multiply it by some number (play with different numbers to get an idea of how they affect performance), then realloc. That's just about all there is to it. (And if you look at the CPython source for the list type, that's basically the same thing it's doing.)
Here's an example. You'll want to add some error handling (malloc and realloc can return NULL) and of course the rest of the API beyond append (especially a delete function, which will call free on the allocated memory), but this should be enough to show you the idea:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int *i;
size_t len;
size_t capacity;
} IntArray;
IntArray int_array_make() {
IntArray a = {
.i = malloc(10 * sizeof(int)),
.len = 0,
.capacity = 10
};
return a;
}
void int_array_append(IntArray *a, int value) {
if (a->len+1 == a->capacity) {
size_t new_capacity = (int)(a->capacity * 1.6);
a->i = realloc(a->i, new_capacity * sizeof(int));
a->capacity = new_capacity;
}
a->i[a->len++] = value;
}
int main(int argc, char *argv[]) {
IntArray a = int_array_make();
for (int i = 0; i != 50; i++)
int_array_append(&a, i);
for (int i = 0; i != a.len; ++i)
printf("%d ", a.i[i]);
printf("\n");
}
c doesnt have any way of dynamically increasing the size of the array like in python. arrays here are of fixed length
if you know the size of the array that you will be using, u can use this kind of declaration, like this
int arr[10];
or if you would want to add memery on the fly (in runtime), use malloc call along with structure (linked lists)

Categories

Resources