Commit cca87da7 authored by Davis King's avatar Davis King

Set svn:eol-style to native and added the discriminant_pca.cpp file to the normal makefile.

--HG--
extra : convert_revision : svn%3Afdd8eb12-d10e-0410-9acb-85c331704f74/trunk%403334
parent 22ced784
// Copyright (C) 2009 Davis E. King (davisking@users.sourceforge.net) // Copyright (C) 2009 Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License See LICENSE.txt for the full license. // License: Boost Software License See LICENSE.txt for the full license.
#include "tester.h" #include "tester.h"
#include <dlib/svm.h> #include <dlib/svm.h>
#include <dlib/rand.h> #include <dlib/rand.h>
#include <dlib/string.h> #include <dlib/string.h>
#include <vector> #include <vector>
#include <sstream> #include <sstream>
#include <ctime> #include <ctime>
namespace namespace
{ {
using namespace test; using namespace test;
using namespace dlib; using namespace dlib;
using namespace std; using namespace std;
dlib::logger dlog("test.discriminant_pca"); dlib::logger dlog("test.discriminant_pca");
class discriminant_pca_tester : public tester class discriminant_pca_tester : public tester
{ {
/*! /*!
WHAT THIS OBJECT REPRESENTS WHAT THIS OBJECT REPRESENTS
This object represents a unit test. When it is constructed This object represents a unit test. When it is constructed
it adds itself into the testing framework. it adds itself into the testing framework.
!*/ !*/
public: public:
discriminant_pca_tester ( discriminant_pca_tester (
) : ) :
tester ( tester (
"test_discriminant_pca", // the command line argument name for this test "test_discriminant_pca", // the command line argument name for this test
"Run tests on the discriminant_pca object.", // the command line argument description "Run tests on the discriminant_pca object.", // the command line argument description
0 // the number of command line arguments for this test 0 // the number of command line arguments for this test
) )
{ {
thetime = time(0); thetime = time(0);
} }
time_t thetime; time_t thetime;
dlib::rand::float_1a rnd; dlib::rand::float_1a rnd;
template <typename dpca_type> template <typename dpca_type>
void test1() void test1()
{ {
dpca_type dpca, dpca2, dpca3; dpca_type dpca, dpca2, dpca3;
DLIB_TEST(dpca.in_vector_size() == 0); DLIB_TEST(dpca.in_vector_size() == 0);
DLIB_TEST(dpca.between_class_weight() == 1); DLIB_TEST(dpca.between_class_weight() == 1);
DLIB_TEST(dpca.within_class_weight() == 1); DLIB_TEST(dpca.within_class_weight() == 1);
// generate a bunch of 4 dimensional vectors and compute the normal PCA transformation matrix // generate a bunch of 4 dimensional vectors and compute the normal PCA transformation matrix
// and just make sure it is a unitary matrix as it should be. // and just make sure it is a unitary matrix as it should be.
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
dpca.add_to_total_variance(randm(4,1,rnd)); dpca.add_to_total_variance(randm(4,1,rnd));
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
} }
matrix<double> mat = dpca.dpca_matrix(1); matrix<double> mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
mat = dpca.dpca_matrix(0.9); mat = dpca.dpca_matrix(0.9);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(mat.nr()))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(mat.nr())));
matrix<double> eig; matrix<double> eig;
dpca.dpca_matrix(mat, eig, 1); dpca.dpca_matrix(mat, eig, 1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
// check that all eigen values are grater than 0 // check that all eigen values are grater than 0
DLIB_TEST(min(eig > 0) == 1); DLIB_TEST(min(eig > 0) == 1);
DLIB_TEST(eig.size() == mat.nr()); DLIB_TEST(eig.size() == mat.nr());
DLIB_TEST(is_col_vector(eig)); DLIB_TEST(is_col_vector(eig));
// check that the eigenvalues are sorted // check that the eigenvalues are sorted
double last = eig(0); double last = eig(0);
for (long i = 1; i < eig.size(); ++i) for (long i = 1; i < eig.size(); ++i)
{ {
DLIB_TEST(last >= eig(i)); DLIB_TEST(last >= eig(i));
} }
dpca.set_within_class_weight(5); dpca.set_within_class_weight(5);
dpca.set_between_class_weight(6); dpca.set_between_class_weight(6);
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
DLIB_TEST(dpca.within_class_weight() == 5); DLIB_TEST(dpca.within_class_weight() == 5);
DLIB_TEST(dpca.between_class_weight() == 6); DLIB_TEST(dpca.between_class_weight() == 6);
ostringstream sout; ostringstream sout;
serialize(dpca, sout); serialize(dpca, sout);
istringstream sin(sout.str()); istringstream sin(sout.str());
deserialize(dpca2, sin); deserialize(dpca2, sin);
// now make sure the serialization worked // now make sure the serialization worked
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
DLIB_TEST(dpca.within_class_weight() == 5); DLIB_TEST(dpca.within_class_weight() == 5);
DLIB_TEST(dpca.between_class_weight() == 6); DLIB_TEST(dpca.between_class_weight() == 6);
DLIB_TEST(dpca2.in_vector_size() == 4); DLIB_TEST(dpca2.in_vector_size() == 4);
DLIB_TEST(dpca2.within_class_weight() == 5); DLIB_TEST(dpca2.within_class_weight() == 5);
DLIB_TEST(dpca2.between_class_weight() == 6); DLIB_TEST(dpca2.between_class_weight() == 6);
DLIB_TEST(equal(dpca.dpca_matrix(), dpca2.dpca_matrix())); DLIB_TEST(equal(dpca.dpca_matrix(), dpca2.dpca_matrix()));
DLIB_TEST(equal(mat, dpca2.dpca_matrix(1))); DLIB_TEST(equal(mat, dpca2.dpca_matrix(1)));
DLIB_TEST(equal(dpca.dpca_matrix(1), mat)); DLIB_TEST(equal(dpca.dpca_matrix(1), mat));
// now test swap // now test swap
dpca2.swap(dpca3); dpca2.swap(dpca3);
DLIB_TEST(dpca2.in_vector_size() == 0); DLIB_TEST(dpca2.in_vector_size() == 0);
DLIB_TEST(dpca2.between_class_weight() == 1); DLIB_TEST(dpca2.between_class_weight() == 1);
DLIB_TEST(dpca2.within_class_weight() == 1); DLIB_TEST(dpca2.within_class_weight() == 1);
DLIB_TEST(dpca3.in_vector_size() == 4); DLIB_TEST(dpca3.in_vector_size() == 4);
DLIB_TEST(dpca3.within_class_weight() == 5); DLIB_TEST(dpca3.within_class_weight() == 5);
DLIB_TEST(dpca3.between_class_weight() == 6); DLIB_TEST(dpca3.between_class_weight() == 6);
DLIB_TEST(equal(mat, dpca3.dpca_matrix(1))); DLIB_TEST(equal(mat, dpca3.dpca_matrix(1)));
DLIB_TEST((dpca3 + dpca3).in_vector_size() == 4); DLIB_TEST((dpca3 + dpca3).in_vector_size() == 4);
DLIB_TEST((dpca3 + dpca3).within_class_weight() == 5); DLIB_TEST((dpca3 + dpca3).within_class_weight() == 5);
DLIB_TEST((dpca3 + dpca3).between_class_weight() == 6); DLIB_TEST((dpca3 + dpca3).between_class_weight() == 6);
dpca.clear(); dpca.clear();
DLIB_TEST(dpca.in_vector_size() == 0); DLIB_TEST(dpca.in_vector_size() == 0);
DLIB_TEST(dpca.between_class_weight() == 1); DLIB_TEST(dpca.between_class_weight() == 1);
DLIB_TEST(dpca.within_class_weight() == 1); DLIB_TEST(dpca.within_class_weight() == 1);
} }
template <typename dpca_type> template <typename dpca_type>
void test2() void test2()
{ {
dpca_type dpca, dpca2, dpca3; dpca_type dpca, dpca2, dpca3;
typename dpca_type::column_matrix samp1(4), samp2(4); typename dpca_type::column_matrix samp1(4), samp2(4);
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
dpca.add_to_total_variance(randm(4,1,rnd)); dpca.add_to_total_variance(randm(4,1,rnd));
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
// do this to subtract out the variance along the 3rd axis // do this to subtract out the variance along the 3rd axis
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 0,0,1,0; samp2 = 0,0,1,0;
dpca.add_to_within_class_variance(samp1, samp2); dpca.add_to_within_class_variance(samp1, samp2);
} }
matrix<double> mat; matrix<double> mat;
dpca.set_within_class_weight(0); dpca.set_within_class_weight(0);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
DLIB_TEST(dpca.dpca_matrix(1).nr() == 4); DLIB_TEST(dpca.dpca_matrix(1).nr() == 4);
dpca.set_within_class_weight(1000); dpca.set_within_class_weight(1000);
DLIB_TEST(dpca.dpca_matrix(1).nr() == 3); DLIB_TEST(dpca.dpca_matrix(1).nr() == 3);
// the 3rd column of the transformation matrix should be all zero since // the 3rd column of the transformation matrix should be all zero since
// we killed all the variation long the 3rd axis // we killed all the variation long the 3rd axis
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-5); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-5);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(3))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(3)));
} }
template <typename dpca_type> template <typename dpca_type>
void test3() void test3()
{ {
dpca_type dpca, dpca2, dpca3; dpca_type dpca, dpca2, dpca3;
typename dpca_type::column_matrix samp1(4), samp2(4); typename dpca_type::column_matrix samp1(4), samp2(4);
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
dpca.add_to_total_variance(randm(4,1,rnd)); dpca.add_to_total_variance(randm(4,1,rnd));
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
// do this to subtract out the variance along the 3rd axis // do this to subtract out the variance along the 3rd axis
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 0,0,1,0; samp2 = 0,0,1,0;
dpca.add_to_within_class_variance(samp1, samp2); dpca.add_to_within_class_variance(samp1, samp2);
// do this to subtract out the variance along the 1st axis // do this to subtract out the variance along the 1st axis
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 1,0,0,0; samp2 = 1,0,0,0;
dpca.add_to_within_class_variance(samp1, samp2); dpca.add_to_within_class_variance(samp1, samp2);
} }
matrix<double> mat; matrix<double> mat;
dpca.set_within_class_weight(0); dpca.set_within_class_weight(0);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
DLIB_TEST(dpca.dpca_matrix(1).nr() == 4); DLIB_TEST(dpca.dpca_matrix(1).nr() == 4);
dpca.set_within_class_weight(10000); dpca.set_within_class_weight(10000);
DLIB_TEST(dpca.dpca_matrix(1).nr() == 2); DLIB_TEST(dpca.dpca_matrix(1).nr() == 2);
// the 1st and 3rd columns of the transformation matrix should be all zero since // the 1st and 3rd columns of the transformation matrix should be all zero since
// we killed all the variation long the 1st and 3rd axes // we killed all the variation long the 1st and 3rd axes
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-5); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-5);
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-5); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-5);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(2))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(2)));
} }
template <typename dpca_type> template <typename dpca_type>
void test4() void test4()
{ {
dpca_type dpca, dpca2, dpca3; dpca_type dpca, dpca2, dpca3;
dpca_type add_dpca1, add_dpca2, add_dpca3, add_dpca4, sum_dpca; dpca_type add_dpca1, add_dpca2, add_dpca3, add_dpca4, sum_dpca;
typename dpca_type::column_matrix samp1(4), samp2(4), samp; typename dpca_type::column_matrix samp1(4), samp2(4), samp;
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
samp = randm(4,1,rnd); samp = randm(4,1,rnd);
dpca.add_to_total_variance(samp); dpca.add_to_total_variance(samp);
add_dpca4.add_to_total_variance(samp); add_dpca4.add_to_total_variance(samp);
DLIB_TEST(dpca.in_vector_size() == 4); DLIB_TEST(dpca.in_vector_size() == 4);
// do this to subtract out the variance along the 3rd axis // do this to subtract out the variance along the 3rd axis
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 0,0,1,0; samp2 = 0,0,1,0;
dpca.add_to_within_class_variance(samp1, samp2); dpca.add_to_within_class_variance(samp1, samp2);
add_dpca1.add_to_within_class_variance(samp1, samp2); add_dpca1.add_to_within_class_variance(samp1, samp2);
// do this to subtract out the variance along the 1st axis // do this to subtract out the variance along the 1st axis
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 1,0,0,0; samp2 = 1,0,0,0;
dpca.add_to_within_class_variance(samp1, samp2); dpca.add_to_within_class_variance(samp1, samp2);
add_dpca2.add_to_within_class_variance(samp1, samp2); add_dpca2.add_to_within_class_variance(samp1, samp2);
// do this to add the variance along the 3rd axis back in // do this to add the variance along the 3rd axis back in
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 0,0,1,0; samp2 = 0,0,1,0;
dpca.add_to_between_class_variance(samp1, samp2); dpca.add_to_between_class_variance(samp1, samp2);
add_dpca3.add_to_between_class_variance(samp1, samp2); add_dpca3.add_to_between_class_variance(samp1, samp2);
} }
matrix<double> mat; matrix<double> mat;
sum_dpca = dpca_type() + dpca_type() + add_dpca1 + dpca_type() + add_dpca2 + add_dpca3 + add_dpca4; sum_dpca = dpca_type() + dpca_type() + add_dpca1 + dpca_type() + add_dpca2 + add_dpca3 + add_dpca4;
dpca.set_within_class_weight(0); dpca.set_within_class_weight(0);
dpca.set_between_class_weight(0); dpca.set_between_class_weight(0);
sum_dpca.set_within_class_weight(0); sum_dpca.set_within_class_weight(0);
sum_dpca.set_between_class_weight(0); sum_dpca.set_between_class_weight(0);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1))); DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1)));
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
DLIB_TEST(dpca.dpca_matrix(1).nr() == 4); DLIB_TEST(dpca.dpca_matrix(1).nr() == 4);
dpca.set_within_class_weight(10000); dpca.set_within_class_weight(10000);
sum_dpca.set_within_class_weight(10000); sum_dpca.set_within_class_weight(10000);
DLIB_TEST(dpca.dpca_matrix(1).nr() == 2); DLIB_TEST(dpca.dpca_matrix(1).nr() == 2);
// the 1st and 3rd columns of the transformation matrix should be all zero since // the 1st and 3rd columns of the transformation matrix should be all zero since
// we killed all the variation long the 1st and 3rd axes // we killed all the variation long the 1st and 3rd axes
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-4); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),2))) < 1e-4);
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-4); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-4);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(2))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(2)));
DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1))); DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1)));
// now add the variance back in using the between class weight // now add the variance back in using the between class weight
dpca.set_within_class_weight(0); dpca.set_within_class_weight(0);
dpca.set_between_class_weight(1); dpca.set_between_class_weight(1);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(4)));
DLIB_TEST(dpca.dpca_matrix(1).nr() == 4); DLIB_TEST(dpca.dpca_matrix(1).nr() == 4);
dpca.set_within_class_weight (10000); dpca.set_within_class_weight (10000);
dpca.set_between_class_weight(100000); dpca.set_between_class_weight(100000);
sum_dpca.set_within_class_weight (10000); sum_dpca.set_within_class_weight (10000);
sum_dpca.set_between_class_weight(100000); sum_dpca.set_between_class_weight(100000);
DLIB_TEST(dpca.dpca_matrix(1).nr() == 3); DLIB_TEST(dpca.dpca_matrix(1).nr() == 3);
// the first column should be all zeros // the first column should be all zeros
DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-5); DLIB_TEST(sum(abs(colm(dpca.dpca_matrix(1),0))) < 1e-5);
mat = dpca.dpca_matrix(1); mat = dpca.dpca_matrix(1);
DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(3))); DLIB_TEST(equal(mat*trans(mat), identity_matrix<double>(3)));
DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1))); DLIB_TEST(equal(mat, sum_dpca.dpca_matrix(1)));
} }
template <typename dpca_type> template <typename dpca_type>
void test5() void test5()
{ {
dpca_type dpca, dpca2; dpca_type dpca, dpca2;
typename dpca_type::column_matrix samp1(4), samp2(4); typename dpca_type::column_matrix samp1(4), samp2(4);
samp1 = 0,0,0,0; samp1 = 0,0,0,0;
samp2 = 0,0,1,0; samp2 = 0,0,1,0;
for (int i = 0; i < 5000; ++i) for (int i = 0; i < 5000; ++i)
{ {
dpca.add_to_between_class_variance(samp1, samp2); dpca.add_to_between_class_variance(samp1, samp2);
dpca2.add_to_total_variance(samp1); dpca2.add_to_total_variance(samp1);
dpca2.add_to_total_variance(samp2); dpca2.add_to_total_variance(samp2);
} }
matrix<double> mat, eig; matrix<double> mat, eig;
dpca.dpca_matrix(mat, eig, 1); dpca.dpca_matrix(mat, eig, 1);
// make sure the eigenvalues come out the way they should for this simple data set // make sure the eigenvalues come out the way they should for this simple data set
DLIB_TEST(eig.size() == 1); DLIB_TEST(eig.size() == 1);
DLIB_TEST_MSG(abs(eig(0) - 1) < 1e-10, abs(eig(0) - 1)); DLIB_TEST_MSG(abs(eig(0) - 1) < 1e-10, abs(eig(0) - 1));
dpca2.dpca_matrix(mat, eig, 1); dpca2.dpca_matrix(mat, eig, 1);
// make sure the eigenvalues come out the way they should for this simple data set // make sure the eigenvalues come out the way they should for this simple data set
DLIB_TEST(eig.size() == 1); DLIB_TEST(eig.size() == 1);
DLIB_TEST(abs(eig(0) - 0.25) < 1e-10); DLIB_TEST(abs(eig(0) - 0.25) < 1e-10);
} }
void perform_test ( void perform_test (
) )
{ {
++thetime; ++thetime;
typedef matrix<double,0,1> sample_type; typedef matrix<double,0,1> sample_type;
typedef discriminant_pca<sample_type> dpca_type; typedef discriminant_pca<sample_type> dpca_type;
dlog << LINFO << "time seed: " << thetime; dlog << LINFO << "time seed: " << thetime;
rnd.set_seed(cast_to_string(thetime)); rnd.set_seed(cast_to_string(thetime));
test5<dpca_type>(); test5<dpca_type>();
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
{ {
print_spinner(); print_spinner();
test1<dpca_type>(); test1<dpca_type>();
print_spinner(); print_spinner();
test2<dpca_type>(); test2<dpca_type>();
print_spinner(); print_spinner();
test3<dpca_type>(); test3<dpca_type>();
print_spinner(); print_spinner();
test4<dpca_type>(); test4<dpca_type>();
} }
} }
}; };
// Create an instance of this object. Doing this causes this test // Create an instance of this object. Doing this causes this test
// to be automatically inserted into the testing framework whenever this cpp file // to be automatically inserted into the testing framework whenever this cpp file
// is linked into the project. Note that since we are inside an unnamed-namespace // is linked into the project. Note that since we are inside an unnamed-namespace
// we won't get any linker errors about the symbol a being defined multple times. // we won't get any linker errors about the symbol a being defined multple times.
discriminant_pca_tester a; discriminant_pca_tester a;
} }
...@@ -41,6 +41,7 @@ SRC += conditioning_class_c.cpp ...@@ -41,6 +41,7 @@ SRC += conditioning_class_c.cpp
SRC += conditioning_class.cpp SRC += conditioning_class.cpp
SRC += config_reader.cpp SRC += config_reader.cpp
SRC += directed_graph.cpp SRC += directed_graph.cpp
SRC += discriminant_pca.cpp
SRC += empirical_kernel_map.cpp SRC += empirical_kernel_map.cpp
SRC += entropy_coder.cpp SRC += entropy_coder.cpp
SRC += entropy_encoder_model.cpp SRC += entropy_encoder_model.cpp
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment