참고할만한 사이트

기본 개념

http://takinginitiative.wordpress.com/2008/04/03/basic-neural-network-tutorial-theory/

기본 구현

http://www.cs.bham.ac.uk/~jxb/NN/nn.html
http://takinginitiative.wordpress.com/2008/04/23/basic-neural-network-tutorial-c-implementation-and-source-code/

돌아가는거랑 기본 개념 참고..

http://www.ibm.com/developerworks/library/l-neural/

그리고 위키피디아

http://en.wikipedia.org/wiki/Back-propagation
이올린에 북마크하기(0) 이올린에 추천하기(0)

Fraction클래스에 대한 오퍼레이터 설정 방법..

그냥 이게 핵심이지..

    friend bool operator == (const Fraction & f1, const Fraction & f2);
    Fraction operator+(const Fraction &f1) const;
    Fraction operator-(const Fraction &f1) const;
    Fraction operator*(const Fraction &f1) const;
    const Fraction& operator=(const Fraction &f);

    // friend functions 
    friend std::istream& operator>>(std::istream& in, Fraction & f);
    friend std::ostream& operator<< (std::ostream& out, const Fraction& f);

알기 쉽게 설명된 자료..

http://www.acm.org/crossroads/xrds2-1/ovp.html


이올린에 북마크하기(0) 이올린에 추천하기(0)



사용법..

typedef 기존타입 명명할타입;

typedef ConditionalProbabilityTable CPT;

이런식으로..

typedef int *pint;

는 pint라는 타입명을 선언, 내용은 int*

int *pn;
pint pn;

은 같은거..

링크드리스트에서 자주 쓰던..

typedef struct _node *node;

struct _node *varNode;
node *varNode;

두개가 같은거..

간만에 쓰려니 헷갈리는구만..
이올린에 북마크하기(0) 이올린에 추천하기(0)


필요한건..노드..링크..

노드에는 확률 테이블이 들어있고..

부모노드에 따라 확률 테이블은 가변적으로 변한다..

작동이 어떻게 돌아가는지는 나중에 생각하자..

일단은 틀을 만들고 나중에 수정하지뭐..

클래스는

Node, Link, ConditionalProbabilityTable

3개..

typedef로 ConditionalProbabilityTable을 CPT로 정의..

클래스 관계는..

노드 -> 링크 -> 노드 의 레퍼런스 참조..(NN)

나머지는 확률테이블이 있으니 ParentNodeList와 ChildNodeList로..

노드는 여러개의 상태를..

링크다수, 하나의 확률테이블을 가지고 있다.

일단 그런 관계만 설정하고..

Node는 AddLink(Node*), DelLink(Node*)이런것도 쓰자..

Node는 evidence명을 가져야하므로 std::string 타입으로 저장..

IsEvidence(std::string)과 IsEvidence(const char*) 편의를 위해 구현하고..

CPT는 이전 단계에 따라서 차원이 늘어나면서 커진다..

이거는 골치 아프네..가변차원배열을 만들기는 귀찮고..

그냥 2차원배열로 표현하고..

부모노드의 리스트는 가지고 있긴하지만...

Node안에 있지 CPT안에는 없으니..CPT에는 Node의 ParentNodeList를 참조하도록 설정..

일단 여기까지 구현하자..


이올린에 북마크하기(0) 이올린에 추천하기(0)


일단 기본 루틴에 대해서 살펴봤었다...

1. centroid를 계산하고(초기는 랜덤 등..)
2. 모든 데이터에 대해서 최소 거리의 centroid로 할당하고
3. 2번에서 더이상 변하는 데이터가 없을 경우 종료

간단하다.. 중요한 것이 centroid를 구하는 것, 최소 거리의 centroid를 구하는 것 정도일 것이다.

또 중요한 점이 있다면 벡터 공간상에서 가능할 것이라는 것, 1d의 스칼라 값 뿐만 아니라 2d의 벡터 값에서도 클러스터링을 지원해줄 수 있도록 좀 범용적으로 활용할 수 있도록 하면 좋을 것이다.

따라서, 초기 입력을 float*의 데이터를 받고, 클래스 내부에서는 vector<float>형으로 다루기로 하자.

필요한 변수는

k개의 클러스터 : m_nK
centroid의 정보 : vector<vector<float>> m_vecCentroids
데이터가 분류된 cluster의 정보 : vector<int> m_vecCluster
data정보 : vector<vector<float>> m_vecData

필요한 함수는

0. 데이터를 설정하는 함수
1. KMeansClustering을 실행하는 함수(iterate함수)
2. centroid를 계산하는 함수
3. centroid에 대하여 data를 재배치 하는 함수

그래서 0 번 호출-> 1번 호출하면..

1번에서는

1. centroid를 적당하게 init을 해주고
2. data를 재배치(3번 함수)해서 재배치가 없다면 결과 리턴
3. 있다면 2번 함수 호출, 다시 2번으로 루프

이렇게 돌아갈 것이다.

그리고 편의를 위한 함수로 유클리디안 거리를 계산하는 함수가 있으면 편할 것이고, 특정한 한 점과 centroid사이에서 최소의 centroid를 구하는 함수도 따로 분리하면 편할 것이다.

- 필요한 include
#include <vector>//vector
#include <math.h> //sqrt(for euclidean distance) 꼭 필요한 것은 아님..

그래서 구현한 소스가 아래와 같다..

// centroid를 리턴하는 함수
int KMeansClustering::GetCentroids(std::vector<std::vector<float>>& centroids)
{
 centroids = this->m_vecCentroids;
 return 0;
}
// k means 클러스터링을 실행한다.
int KMeansClustering::DoKMeansClustering(int kMeans , std::vector<int>& cluster)
{
 //랜덤하게 centroid들을 초기화
 for(unsigned int loop = 0 ; loop < this->m_vecData.size() ; loop++)
 {
  //cluster를 랜덤하게 배치해서 centroid를 지정하도록 한다.
  this->m_vecCluster[loop] = rand() % kMeans;
 }
 
 int nModifiedNodes = 0;
 do
 {
  this->CalculateCentroids();
  this->SetMinimumDistances(nModifiedNodes);
 }while(nModifiedNodes > 0);
 return 0;
}
// 최소 거리를 설정한다(파라미터 리턴 : 수정된 노드의 수)
int KMeansClustering::SetMinimumDistances(int& modifiedNodes)
{
 int nModifiedNodeCount = 0;
 int nClosestCentroid = 0;
 for(unsigned int loop = 0 ; loop < this->m_vecData.size() ; loop++)
 {
  this->GetClosestCentroid(this->m_vecData[loop] , nClosestCentroid);
  if(this->m_vecCluster.at(loop) != nClosestCentroid)
  {
   this->m_vecCluster.at(loop) = nClosestCentroid;
   nModifiedNodeCount++;
  }
 }
 modifiedNodes = nModifiedNodeCount;
 return 0;
}
// Centroids들을 계산해서 저장하는 함수
int KMeansClustering::CalculateCentroids(void)
{
 std::vector<std::vector<float>> vecTotalPoints; //모든 포인트들의 총합 - k개 만큼의 데이터
 std::vector<int> vecPointCount; //각 클러스터에 있는 점의 카운트

 //총합, 평균을 구하기 위해 0으로 초기화
 for(int loop = 0 ; loop < this->m_nK ; loop++)
 {
  std::vector<float> totVector;
  for(int loopVec = 0 ; loopVec < this->m_nVectorSize ; loopVec++)
   totVector.push_back(0);
  vecTotalPoints.push_back(totVector);
  vecPointCount.push_back(0);
 }

 //총합과 각 클러스터에 들어있는 점의 수를 구한다.
 for(unsigned int loop = 0 ; loop < this->m_vecData.size() ; loop++)
 {
  for(int loopVec = 0 ; loopVec < this->m_nVectorSize ; loopVec++)
  {
   int nCluster = this->m_vecCluster.at(loop);
   vecTotalPoints[nCluster][loopVec] += this->m_vecData[loop][loopVec];
   vecPointCount[nCluster]++;
  }
 }

 //Centroid에 평균점을 넣는다.
 for(int loop = 0 ; loop < this->m_nK ; loop++)
 {
  for(int loopVec = 0 ; loopVec < this->m_nVectorSize ; loopVec++)
  {
   this->m_vecCentroids[loop][loopVec] = vecTotalPoints[loop][loopVec] / (float)vecPointCount[loop];
  }
 }
 
 return 0;
}
// 데이터를 설정하는 함수
int KMeansClustering::SetData(std::vector<std::vector<float>> data)
{
 this->m_vecData = data;
 return 0;
}
int KMeansClustering::SetData(float** data, int dataSize, int vectorSize)
{
 this->m_vecData.clear();
 for(int loopRow = 0 ; loopRow < dataSize ; loopRow++)
 {
  std::vector<float> inputData;
  for(int loopCol = 0 ;loopCol < vectorSize ; loopCol++)
  {
   inputData.push_back(data[dataSize][vectorSize]);
  }
  this->m_vecData.push_back(inputData);
 }
 return 0;
}
// 한 점에서 최소의 centroid를 구하는 함수
int KMeansClustering::GetClosestCentroid(std::vector<float> point, int& closestCentroidIndex)
{
 float fMinDistance = -1;
 float fDistance = 0;
 int nMinCentroid = -1;
 for(unsigned int loop = 0 ; loop < this->m_vecCentroids.size() ; loop++)
 {
  this->GetEuclideanDistance(this->m_vecCentroids.at(loop) , point , fDistance);
  if(nMinCentroid == -1 || fDistance < fMinDistance)
  {
   fMinDistance = fDistance;
   nMinCentroid = loop;
  }
 }
 closestCentroidIndex = nMinCentroid;
 return 0;
}
// 유클리디안 거리를 구하는 함수
int KMeansClustering::GetEuclideanDistance(std::vector<float> x, std::vector<float> y, float& distance)
{
 float fTotalSquareError = 0;
 for(unsigned int loopx = 0 ; loopx < x.size() && loopx < y.size() ; loopx++)
 {
  fTotalSquareError += (x[loopx] - y[loopx]) * (x[loopx] - y[loopx]);
 }
 
 distance = sqrtf(fTotalSquareError);
 return 0;
}
// float*의 오버로딩
int KMeansClustering::GetCluster(std::vector<float> data , int& clusterNum)
{
 return this->GetClosestCentroid(data , clusterNum);
}
int KMeansClustering::GetCluster(float* data, int dataSize , int& clusterNum)
{
 std::vector<float> vecData;
 
 for(int loop = 0 ; loop < dataSize ; loop++)
 {
  vecData.push_back(data[loop]);
 }
 return this->GetClosestCentroid(vecData , clusterNum);
}
// 파일에서 데이터를 로드해서 클러스터링을 한다
int KMeansClustering::LoadDataFromFile(const char* filename)
{
/*
 * 파일포맷
 *
 * int : vectorSize;
 * float float .... (vectorSize 만큼)
 * float float .... (vectorSize 만큼)
 * ...
 */
 std::ifstream inputFile(filename);
 int vectorSize = 0;
 inputFile >> vectorSize;
 while(inputFile.eof())
 {
  float inputData;
  std::vector<float> vecData;
  for(int loop = 0 ; loop < vectorSize && inputFile.eof() ; loop++)
  {
   inputFile >> inputData;
   vecData.push_back(inputData);
  }
  this->m_vecData.push_back(vecData);
 }
 return 0;
}

뭐, 소스의 설명은 필요없을 정도로 직관적이다...

단지 변수들을 주의할 필요가 있을 것이다.

각 vec 변수들에 대하여 갯수와 용도를 잘 구분해서 파악해야할 것이다.

뭐, 이거는 테스트 없이 그냥 스크래치로 쭉~ 짠거라 잘 돌아갈지는 아직 모른다-_-;;

테스트를 위해 파일에서 로드하는 것도 했고,

그럼 파일로 테스트를 해보자.. 코딩하고 한번에 잘 되면 오히려 뭔가 찝찝하고 이상하다.. 파일로 테스트 하면서 몇 가지 초기화를 해야하는 문제라던가 루프를 돌 때 vector의 크기 문제 등을 간단하게 고치고 나면..

사용자 삽입 이미지

3단계로 나눴는데 얼추 되는것 같다..

클러스터0은 5이상인거 같고, 2는 -1 이하인것 같다..

사용자 삽입 이미지

한번 클러스터의 평균점을 출력해보도록 했다. 두 클러스터의 중간 지점이 경계가 될 것이니 위에서 얼추 예상한 경계값과 맞는다. 아래는 k=5로 할 경우의 평균점 결과..

사용자 삽입 이미지

여기서 주의할 점을 발견했다! 순서가 저절로 소팅이 되는 것이 아니라 자기 멋대로 움직인다는 것! 나중에 클러스터링 후 처리로 소팅도 넣어주면 될 것이다. 근데 k=3에서 k=5로 바꾸니까 시간이 너무 오래 걸린다...-_- 고작 7319개 샘플 데이터 뿐인데...흑.. 차마 k=7을 돌려보기가 무섭다;; 최적화는 초기 상태의 선택이 얼마나 효과적으로 되었는지에 따라 달라질 것이다.. 그거에 대한 고민은 생략!

이올린에 북마크하기(0) 이올린에 추천하기(0)


K-means algorithm

K means 알고리즘에 대해서 이야기는 많이 들어왔는데, 그 돌아가는 방법에 대해서 알아보면 좋을 것 같다...

참고 자료는 위키피디아...

http://en.wikipedia.org/wiki/K-means_algorithm

- K-means algorithm은 k 파티션으로 클러스터링을 하는 것으로 expectation-maximization 알고리즘과 비슷하다고 한다. 왜냐하면 둘다 데이터의 안에 있는 중심을 찾으려고 하기 때문이라고 한다. Object attribute들이 벡터 공간을 형성한다고 가정하고 있고, 그 목적이 클러스터 내의 분산의 총합을 최소화하는 것이라고 한다.



squared error function V 는 다음 식과 같은데, k 클러스터로 나누는 것이고, S_i는 해당 클러스터, i = 1 ~ k, μ_i는 x_j∈S_i인 모든 값들에 대하여 centroid 또는 평균점이다.

// 여기까지는 뭐, 뻔한 내용.. k 개의 클러스터링을 목표로 하는 점.. 벡터 공간을 형성한다는 점, squared error 구하는 식이 보면 알겠지만, 각 클러스터의 데이터의 표준편차의 제곱, 즉 분산을 전부 합한 값이다. 쉬운 내용.. centroid와 평균점은 내용이 조금 다른데, centroid는 특정한 점이고, 평균 점은 특저한 점이 아닐 수 있다는게 다른 점이다.(아마도-_-;)

- 가장 잘 알려진 알고리즘의 형태는 iterative refinement heuristic으로 알려진 Lloyd's 알고리즘이다. 로이드의 알고리즘은 k개의 초기 상태로 나누는데, 초기에 랜덤으로 나누거나, 어떠한 heuristic한 데이터를 이용한다. 그래서 각 집합의 평균점이나 centroid를 계산한다. 그것은 각 지점을 가장 가까운 centroid에 할당함으로써 새로운 파티션이 생기게 되고, centroid들은 다시 새로운 클러스터를 위해서 계산되고, 알고리즘은 이 두가지 단계를 더 이상 점들이 클러스터를 바꾸게 되지 않을 때까지 계속적으로 반복한다.

//랜덤하게 나눈 set에 대해 중심점을 구하고 가장 가까운 노드들을 할당하는 것, 문제가 있다면 두 개의 centroid가 우연하게 같을 경우일 것 같다. 그 외에는 centroid는 점점 특정한 중심지점을 향해서 다가갈 것이다.

- Lloyd의 알고리즘과 k-means는 동일하게 사용되기도 하지만, Lloyd의 알고리즘은 사실적으로도 k-means 방법의 heuristic한 방법이기도 하다. 하지만 특정한 시작 지점과 centroid의 조합으로 인해 가끔씩 틀린 답으로 가는 경우가 발생한다(위의 최소화 함수의 optimal 답과 다른 경우로..)

//바로 위에서 말한 것과 같은 로이드 알고리즘이 문제가 있을 수 있다고 한다..

- 다른 방법도 존재한다. 로이드 알고리즘은 여전히 유용한 점이 접근을 매우 빨리 할 수 있다는 점이다. 사실, 검사하는 횟수가 점의 수보다 훨씬 적다는 것도 사실이다. 어쨋든, 최근에는  David Arthur and Sergei Vassilvitskii가 coreset에 대한 활용을 제안했다(coreset : 원래 데이터의 작은 subset)

- 성능에 대하여 알고리즘은 최적의 optimum을 보장하지는 못한다. 마지막 답은 초반의 클러스터링 셋에 많이 의존되며, 이 초기셋은 global optimum에 비해 많이 성능이 떨어져도 된다. 이 알고리즘이 매우 빠르므로, 일반적으로 이 알고리즘을 몇번 호출하고 찾아낸 최적의 클러스터링을 리턴하는 것이 일반적이다.

- 다른 주된 단점은 찾아야하는 클러스터의 수를 미리 정해줘야한다. 만약 데이터가 자연적으로 클러스터링 되어있지 않다면 이상한 결과를 얻을 수 있다. 또한, 알고리즘은 구면의 클러스터가 데이터에서 가능해야 잘 작동한다.


Demonstration of the algorithm


사용자 삽입 이미지
.

//방법만 봐도 대충 이해가 될 것이다. 아주 쉽다. k-means라고 하는 이유도 알 수 있고, 구현도 centroid를 구하는 함수, 각 최소 지점에 associate하는 함수(변환 여부를 bool로 리턴)만 있으면 될 것이다.


이올린에 북마크하기(0) 이올린에 추천하기(0)


이전에 동적인 그래프를 그렸으니 이번에는 정적인 그래프를 그려보자..

동적인 그래프와 다른 점은 단지 데이터가 계속 추가되는 것이 아니라 한번 설저앟면 그대로 보여준다는 것..

즉, AddData가 아니라 SetData정도가 되어서 데이터의 배열을 받고 출력하고, OnPaint에만 추가 시키면 되는 간단한 방식이 될 것이다.

CGraph를 만들고 기존의 CAnimatedGraph의 내용들을 살펴하자...

public:
 // 로그 데이터 배열
 CArray<float> m_arrLogData;
public:
 // 그래프를 그릴 영역
 RECT m_rectGraphArea;
public:
 // x축 그리드 그릴 수
 int m_nXGridNumber;
public:
 // y축 그리드 그릴 수
 int m_nYGridNumber;
public:
 // y축 최대값
 float m_fYMax;
public:
 // y축 최소값
 float m_fYMin;
public:
 // 데이터를 추가하는 함수
 int AddData(float data);
public:
 // 현재 데이터 크기를 리턴하는 함수
 int GetDataSize(void);
public:
 // 그리기 함수
 int Draw(CDC* pDC);
public:
 // 그래프를 그릴 영역을 설정
 int SetArea(LONG x1, LONG y1, LONG x2, LONG y2);
public:
 // 바로 그래프 그리기에 사용할 수 있도록 저장하는 배열(m_arrData를 정교화한 내용)
 CArray<CPoint> m_arrPoints;
public:
 // 한번에 뿌려줄 데이터 출력
 int SetViewDataSize(int size);
public:
 // 한번에 보여줄 데이터의 크기
 int m_nViewDataSize;
public:
 // 최대 최소값 설정
 int SetYMinMax(float YMin, float YMax);

여기서 필요없는 내용은...뭐, 굳이 따지자면 AddData정도일 것이다.

나머지는 그냥 그대로 가져다가 써도 괜찮고, 단지 정적인 그래프기 때문에 데이터를 위에서 말한 것 처럼 SetData 함수를 추가하면 될 것이다.

음....이렇게 되면 가장 좋은 방법은 역시 CAnimatedGraph를 상속 받는 것이겠다..상속받아서 전부 함수들을 그대로 활용하고, SetData함수만 우리가 추가하면 되는 것이다.

// 입력할 데이터, 데이터 크기
int CGraph::SetData(float* data, int dataSize)
{
 for(int loop = 0 ; loop < dataSize ; loop++)
 {
  this->m_arrLogData.Add(data[loop]);
 }

 return 0;
}


그럼 활용법은....간단하다..

기존의 AnimatedGraph의 예제와 같이 사용하되, Timer에서 계속 AddData를 하는 것이 아니라 그냥 어떠한 이벤트를 통해서 SetData(float* , int) 함수를 호출하면 되는 것이다. 물론, 호출하고 나서 InvalidateRect(&graph.m_rectGarphArea)로 다시 그려주는 것도 잊지 말아야한다. 간단하게 보여주면..

 this->m_cRawDataGraph.SetArea(10 , 10 , 210 , 110);
 this->m_cRawDataGraph.SetViewDataSize(100);

 this->m_cRawDataGraph.SetYMinMax(-10,10);

 this->m_cRawDataGraph.SetData(data , dataSize);

 this->InvalidateRect(m_cRawDataGraph.m_rectGraphArea);

이런 소스를 넣고 AnimatedGraph와 똑같이 OnPaint에 그래프.draw()호출을 넣으면 그래프가 그려진다!


 

이올린에 북마크하기(0) 이올린에 추천하기(0)


이번에 하고자 하는 것은 ListControl에 특정한 칸을 더블 클릭하면

특정한 다이얼로그를 띄우는 것이다.

다이얼로그에는 이전에 만들었던 동적으로 그려지는 데이터이고..

이 다이얼로그에도 따로 timer가 들어가서 센서 데이터를 가져와서 뿌려주는 역할을 할 것이다.

ListControl의 property 창에서 메세지 명이 조금 독특하다..

HDN_ITEM_DBL_cLICK 메세지인줄 알았는데...

테스트해보니 이거는 리스트 컨트롤의 헤더에 해당하는 더블 클릭 이벤트를 캐치하는거고..

NM_DBL_CLICK 뭐 대충 이런 이름의 이벤트를 선택하면 된다..

함수를 생성하면 다음과 같은 프로토 타입이 생긴다..

void CSensorMonitoringDlg::OnNMDblclkSensorlist(NMHDR *pNMHDR, LRESULT *pResult)
{
//TODO : 어쩌고
 *pResult = 0;
}

그럼 여기서 어떠한 아이템이 선택 되었는지 어떻게 알아낼까...

방법은 두가지이다..

POSITION pos = m_ctlList.GetFirstSelectedItemPosition();


이란 함수와

LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

int row = pNMIA->iitem;
int col = pNMIA->iSubItem;

이란 방법이 있다..뭐, 더블클릭 이벤트 함수 안에서 아래쪽에 있는 것이 더 편할 것 같다..

그럼 새로운 창 띄우는거는 어떻게 할까...

그냥 local variable로 선언하면 창이 그 함수를 빠져 나오면서 닫히게 된다...

그러므로 동적 할당으로 배열에 추가해 놓고 필요할 때 수정하는 방식으로 하자...

CArray<CGraphDialog*> m_arrpGraphDlg 변수를 추가하고

::OnDestroy에서 메모리 해제하는 부분도 미리 추가하자..생성하면서 메모리 해제도 미리 짜는건 나중에 귀차니즘을 방지할 수 있는 좋은 습관이다..

 for(int loop = 0 ; loop < this->m_arrpGraphDialog.GetSize() ; loop++)
 {
  this->m_arrpGraphDialog.GetAt(loop)->DestroyWindow();
  delete this->m_arrpGraphDialog.GetAt(loop);
 }

그럼 이제 더블클릭 이벤트로 생성된 함수 안에서 다이얼로그를 생성하자..


void CSensorMonitoringDlg::OnNMDblclkSensorlist(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMITEMACTIVATE pnmia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);


 if(pnmia->iItem >= 0)
 {
  CGraphDialog* pGraphDlg = new CGraphDialog();

  pGraphDlg->m_nSensorDataIndex = pnmia->iItem;
  pGraphDlg->Create(CGraphDialog::IDD , this);
  pGraphDlg->ShowWindow(SW_SHOW);

  this->m_arrpGraphDialog.Add(pGraphDlg);
 }
 *pResult = 0;
}

결과적으로는 이렇게 되었다..

pnmia->iItem가 -1이 될 경우에는 어떠한 선택된 아이템 없이 더블클릭 된 것이니까 특정한 리스트가 클릭 되었을 때에만 추가하면 된다.

Create함수의 경우는 첫번째가 참조할 resource id로 resource에서 생성한 IDD_???를 넣어도 되고 위와 같이 다이얼로그에 해당하는 클래스를 생성한 경우 Dlg::IDD 로 얻어올 수 있다. IDD로 얻어오는 것이 좀더 멋져 보이기도 하고;; ID가 수정 될 경우를 대비해서 매번 수정안해줘도 된다는 편리함이 있다. 두번째 인자는 parent를 설정하는 것으로 일단은 parent가 닫히면 같이 닫히는 정도로 이해해도 될 것이다.

그리고 ShowWindow함수를 통해서 윈도우를 보여주게 되는 것이고, 아까 생성한 배열에 이 다이얼로그의 포인터를 추가해서 메모리 해제도 편리하게 해줄 수 있도록 하는 것이다. 그럼 테스트를 해보자!

사용자 삽입 이미지

더블클릭하니 잘 뜬다! 이제 할거는 자식창과 부모창 사이의 통신을 통해서 그래프 등의 정보를 넣는것! 뭐, 그런거는 포인터의 함수를 통해서 쉽게 할 수 있을 거라 생각한다..










 

이올린에 북마크하기(0) 이올린에 추천하기(0)


C언어 중 한 가지 재밌는 기능이 바로 이 가변 인자 함수이다.

처음에는 이러한 것에 대해서 전혀 의문을 품고 있지 않다가 나중에 한참 C를 배우다가

'어? 이거 어떻게 구현했지?'

라는 의문을 품게 되는 재미있는 요소이다.

C언어를 처음으로 하게 되면 배우게 되는 구문..

void main()
{
  printf("Hello World!");
}

뭐, 여기서는 그렇게 의문을 가지진 않을 것이다. 헬로월드는 뻔한 내용이기도 하니깐. 그런데 이것을 이렇게 바꿀 수도 있다..

void main()
{
  printf("%s" , "Hello World!");
}

그래..여기까진 좋다 이거지...그런데 printf를 아무 생각없이 이렇게 쓰다가 문득 앞에 있는 format string("%s")이 %d, %c, %s 등등 의 무한하게 설정이 가능하고 그 수에 따라 뒤에 따라오는 인자의 수가 매번 달라지는 것을 깨닫게 되는 순간 뒤통수를 딱 맞는 기분이 들 것이다.

어떻게 구현한 것일까?

처음엔 이렇게 생각했다..인자를 한 255개까지 제한하고 있지 않을까..-_-

int printf(char*,int) , int printf(char* , int , int); ..... int printf(char* , int * 255개);

뭐...그냥 이렇게 생각하고 보니 인자가 int만 들어오는게 아니잖아! char, float, double등등 기본형의 조합을 255가지 하려면...header파일의 용량이 몇 메가는 될듯 하다...

그럼 도대체 아무 생각없이 막 써왔던 이런 printf, scanf 등은 어떻게 구현했을까?

툴을 이용해서 이들 인자를 살펴보게 되면

int printf(const char* , ...);

이라고 되어있다..도대체 '...'이 뭐길래?

이 '...'은 바로 '가변 인자 함수(Variable argument function)'을 나타내는 것이다.

그럼 어떻게 쓰느냐? 간단하게 살펴보자...

선언은 위와 같이 ...으로 하고..예제 소스를 보자..(CodeProject 참고)

 int add(int x, ...)
{
    va_list list;
   //initialize the va_list i.e char*
   // variable 'list' by the address
   // of first unknown variable argument
   // by a call of va_start() macro
    va_start(list,x);
    int result=0;

    for(;;)
    { // in loop, retreive each argument
      // Second argument to va_arg is datatype
      // of expected argument
        int p=va_arg(list,int);
    if(p==0)
       break;
    result+=p;
     }
    va_end(list); // cleanup , set 'lsit' to NULL
    return result;
  }

딱 봐도 간단하다...

변수 va_list가 있고
사용하는 함수들은 va_start , va_arg, va_end가 있다..

va_list는 stdio.h에 정의 되어있고..

typedef char* va_list

으로 정의 되어있다. va_start는 매크로로 설정되어서 아래와 같다..

#define va_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))

이거의 의미는 va_list를 인자로 받고, &v로 v의 주소를 구해서 _INTSIZEOF(v)를 더함으로써 인자로 넘어온 v의 다음인자의 주소값을 가져오는 것이다. _INTSIZEOF(v)는 주소값만큼 나누어 떨어지게 하는 것으로, 아래와 같이 정의되어있다.

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

주소는 4바이트씩 끊어서 계산하므로(32bit OS 상에서) 그것을 논리 연산자를 통해서 나누어 떨어지게 하는 것이다.

#define va_arg(ap,t)(*(t*)((ap +=_INTSIZEOF(t))-_INTSIZEOF(t)))

va_arg는 ap에 t의 타입 크기만큼 ap를 옮기고, 그 결과에 다시 t의 크기만큼 뺀다. 이 말은 ap를 다음 인자의 포인터로 넘기고 다시 앞으로와서 그 값을 리턴하는 것이다. 타입의 종류를 잘 설정해야겠다.

#define va_end(ap) ( ap = (va_list)0)

그리고 va_end는 ap를 초기화하게 된다.

이용의 예는 위에 예제소스만 가지고도 충분히 구현이 가능할 것이다.그럼 아래는 MSDN에서 제시하고 있는 requirements이다..


Routine Required header Optional headers Compatibility

va_arg

<stdio.h> and <stdarg.h>

<varargs.h>*

ANSI, Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition, Windows Millennium Edition, Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003

va_end

<stdio.h> and <stdarg.h>

<varargs.h>*

ANSI, Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition, Windows Millennium Edition, Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003

va_start

<stdio.h> and <stdarg.h>

<varargs.h>*

ANSI, Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition, Windows Millennium Edition, Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows Server 2003





아래는 참고한 사이트...여기 다 나와있다! - 마지막인자가 call by reference일 경우의 문제 해결도 있다..
http://www.codeproject.com/KB/cpp/argfunctions.aspx?df=100&forumid=15556&exp=0&select=503481


위키피디아 참고할만한 내용..다른 언어들도 많이 나와있다!
http://en.wikipedia.org/wiki/Varargs

이올린에 북마크하기(0) 이올린에 추천하기(0)


일단 입력과 출력을 정의 하고...

목표
- 손으로 숫자 쓰는 것을 인식

입력(float[3] -> 좀더 유동적으로 하기 위해 vector<>로..)
- 3축 가속도
: m_fAccX
: m_fAccY
: m_fAccZ

출력(int)
: 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9
= 각 숫자에 해당하는 리턴 값
: -1
= 맞는 것이 없다..


class DynamicTimeWarping 이고

멤버는

vector<DTWElement> m_vecPatternSequence

DTWElement는 증거변수가 변할 때는 대비해서 좀더 유동적으로 하기 위해서 하는 것이다.

DTWElement에서는 기본적으로 있어야하는 것이

GetDistance(const DTWElement) //두개의 DTW간 거리를 구하는 함수..
operator=(const DTWElement&)  //= 오버로딩, =는 해놔야 속 편하더라고..
vector<float> m_vecVector;  //증거 벡터
SetVector(float x , ...);  // 한번에 증거 벡터를 설정할 경우
AppendVector(float x);  //벡터의 뒤에 증거를 추가(loop돌면서 추가할 경우)

일단 float형으로 하자..다른 클래스를 가져다가 쓸일은 없을 듯 하다..

그리고 SetVector의 ...형은 가변 인자로 잠깐 살펴보고 가자..

이올린에 북마크하기(0) 이올린에 추천하기(0)