C# 알고리즘 연습용입니다.

문제1. 임의의 문자열을 입력받아 유일한지 검사하라.
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TestCode
{
    class Program
    {
        static Hashtable _UniqueTable;

        static void Main(string[] args)
        {

            string _InputString = Console.ReadLine();
            _UniqueTable = new Hashtable();

            for (int i=0; i < _InputString.Length; i++)
            {
                AddTable(_InputString[i]);
            }
            CheckTable();
            Console.ReadLine();
        }

        static void AddTable(char _x)
        {
            if (_UniqueTable != null && _UniqueTable.Contains(_x))
                _UniqueTable[_x] = (int)_UniqueTable[_x] + 1;
            else
                _UniqueTable.Add(_x, 1);
        }

        static void CheckTable()
        {
            foreach (DictionaryEntry _dt in _UniqueTable)
            {
                if ((int)_dt.Value > 1) Console.WriteLine("Error! No unique key found! {0}, Count {1}", _dt.Key, _dt.Value);
            }

        }
    }
}


결과는 Count가 2개 이상인 경우 문자메시지를 출력합니다.

Crack
1. 입력받은 문자열을 Char로 변환하여 HashTable에 넣는게 관건.
2. 이때 중복되는 키가 있으면 Value증가시킴
3. 중복되는 키가 없으면 Add시킴
4. 모든 입력과정 종료후 DictionaryEntry형으로 변환하여 Foreach날림

만약 C#의 Linq를 사용한다면 더 짧게 코딩이 가능합니다.
        static void Main(string[] args)
        {
            string InputString = "cvdss";
            Console.WriteLine(GetSameUniqueText(InputString).ToString());
            Console.ReadLine();
        }

        static bool GetSameUniqueText(string _x)
        {
            char[] ArrayChar = _x.ToCharArray();
            Array.Sort(ArrayChar);
            var Result = (from x in ArrayChar select x).Distinct().OrderBy(x => x).ToArray();
            if (new string(ArrayChar) == new string(Result)) return true;
            return false;
        }



문제2. 임의의 문자열을 받아서 역순으로 배열하여 출력하라.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            string _TempString = "";
            string _InputString = Console.ReadLine();

            for (int i= _InputString.Length; i > 0; i--)
            {
                _TempString += _InputString[i-1];
            }
            Console.WriteLine(_TempString);
            Console.ReadLine();
        }

    }
}


Crack. 이건 간단해서 패스...
만약 C#이라면 역시 Linq로...
        static void Main(string[] args)
        {
            string InputString = "cvdss";
            Console.WriteLine(SetReverse(InputString));
            Console.ReadLine();
        }

        static string SetReverse(string _x)
        {
            char[] ArrayChar = _x.ToCharArray();
            var Result = (from x in ArrayChar select x).Reverse().ToArray();
            return new string(Result);
        }



문제3. 임의의 문자열을 입력받아 공백을 %20으로 교체하라.
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            string _InputString = Console.ReadLine();
            Console.WriteLine(_InputString.Replace(" ", "%20"));
            Console.ReadLine();
        }
    }
}


Crack : 이것도 간단해서 생략

문제4. 반복되는 임의의 문자열 예를 들어 "aaaabbbccaa"를 입력받으면 "a4b3c2a2"로 압축하라.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            char _OldChar = '\x0000';
            string _OutputStr = "";
            string _InputString = Console.ReadLine();

            int _count = 0;
            for (int i = 0; i < _InputString.Length; i++)
            {
                char _NewChar = _InputString[i];
                if (_NewChar == _OldChar) {
                    if (_count < 2) _OutputStr += _NewChar.ToString();
                    _count++;
                } 
                else
                {
                    if (_count > 0) _OutputStr += _count.ToString();
                    _OldChar = _NewChar;
                    _count = 1;
                }
            }

            Console.WriteLine(_OutputStr + _count.ToString());
            Console.ReadLine();
        }
    }
}


Crack : 이것도 간단해서 패스

문제5. 앞으로 읽어도 뒤로 읽어도 똑같은 문자열인지 Bool값으로 리턴하라. 단, Letter만 확인하라.
using System;
using System.Text.RegularExpressions;

public class Palindrome
{
     public static bool IsPalindrome(string str) {
          //먼저 소문자로 치환하고 특수문자는 제거합니다.
          string _x = JustLetter(str);
          //만약 3글자라면 1번만, 4글자라면 2번만 루프돌면서 가운데를 기준으로 대칭에 있는 문자열을 비교합니다.
          //만약 틀리면 false 를 리턴하고 빠집니다.
           int lastidx = _x.Length - 1;
           for (int i = 0; i < _x.Length / 2; i++){
                     if (_x[i] != _x[lastidx]) return false;
                     lastidx--;
           }
          //아무런 문제가 없으면 true;
           return true;
     }

     public static string JustLetter(string _str)
     {
           string _x = "";
           string returnVal = _str.ToLower();
           string pattern = @"[a-z]";

          //요부분이 좀 애먹은 부분인데 C#의 정규표현식으로 MatchCollection을 만든다음에 한글자씩 따는 부분입니다.
           MatchCollection matches = Regex.Matches(returnVal, pattern);
           foreach (Match match in matches)
                     _x += match.Value;
           return _x;
      }

     public static void Main(string[] args)
     {
           Console.WriteLine(IsPalindrome("Level."));
     }
}


Crack.
1. 글자를 비교할 수 있도록 표준화하는게 관건
   - 소문자로 만들기
   - 특수문자제거
2. 처음과 끝에서 부터 한단계씩 비교

문제 6. momdad와 dadmon은 단어의 순서가 바뀐 문자열이다. 이처럼 순서가 바뀐 단어가 있으면 true, 아니라면 false를 반환하라.

using System;

public class AreAnagrams
{
    public static bool AreStringsAnagrams(string a, string b)
    {
        if (CharSort(a) == CharSort(b)) return true;
        return false;
    }
    
    public static string CharSort(string _x)
    {
        char[] x = _x.ToCharArray();
        Array.Sort(x);
        return new string(x);
    }

    public static void Main(string[] args)
    {
        Console.WriteLine(AreStringsAnagrams("momdad", "dadmom"));
    }
}


Crack.
1. 두개의 단어가 정확하게 쓰여졌다는 가정하에 2개의 문자열을 Char로 치환후 Sort하면 동일한 결과를 얻을 수 있어야 합니다.
2. 따라서, string을 char로 변환후 sort해서 비교하는 루틴으로 간단하게 처리됩니다.


*문제7. Linked list Class를 만들고 특정 문자열을 지우면 나머지 단계도 삭제되는 루틴을 만드시오.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            List<NodeClass> _LinkedNode = new List<NodeClass>();
            SetNode(_LinkedNode);
            #region For Debug (Input data check)
            //foreach (NodeClass _n in _LinkedNode)
            //    Console.WriteLine("Parent:{0}, Value:{1}",_n._Parent, _n._value);
            #endregion
 
            RemoveValue(_LinkedNode, "A");
 
            #region For Debug (Input data check)
            foreach (NodeClass _n in _LinkedNode)
                Console.WriteLine("Parent:{0}, Value:{1}", _n._Parent, _n._value);
            #endregion
            Console.ReadLine();
        }
 
        static void SetNode(List<NodeClass> _LinkedNode)
        {
            string[] _Values = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" };
            foreach (string _value in _Values)
                _LinkedNode.Add(new NodeClass
                {
                    _Parent =
                    ((_value == "B") ? "A" :
                    (_value == "C") ? "A" :
                    (_value == "D") ? "A" :
                    (_value == "E") ? "B" :
                    (_value == "F") ? "C" :
                    (_value == "G") ? "C" :
                    (_value == "H") ? "D" :
                    (_value == "I") ? "F" :
                    (_value == "J") ? "F" :
                    ""),
                    _value = _value
                });
 
        }
        static void RemoveValue(List<NodeClass> _LinkedNode, string _X)
        {
            foreach (NodeClass _n in _LinkedNode)
            {
                if (_n._value == _X) _n._value = "";
                if (_n._Parent == _X) RemoveValue(_LinkedNode, _n._value);  
            }
        }
    }
 
    public class NodeClass
    {
        public string _Parent { get; set; }
        public string _value { get; set; }
    }
 
}


Crack.
1. 클래스 생성은 Parent와 value구조만 있으면 문제가 없습니다. 만약 node 탐색이 들어간다면 또 다른 이야기가 되겠지만, 현재 Tree구조로는 해당 문제를 푸는데 문제없습니다.
2. 먼저 노드를 생성합니다. SetNode
3. 임의의 문자를 입력하여 하위 노드를 삭제합니다. 이때 재귀함수 (Recursion Function)을 사용해서 하위레벨도 삭제되도록 합니다.

추가로 노드를 삭제하려면 아래의 메소드를 호출해주면 되겠습니다.
static void TruncItem(ref List<NodeClass> _LinkedNode) {
     _LinkedNode = _LinkedNode.FindAll(x => x._value != "");
}
ref 키워드이니까, 당연히 TruncItem(ref _LinkNode); 로 호출해야 겠죠?


문제8. 임의의 배열이 주어졌을때 이를 Sort하라.

using System;
using System.Linq;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arrays = new int[] { 1, 5, 6, 2, 4, 7, 9 };
            for (int i = 0; i < arrays.Count(); i++)
            {
                for (int y = 0; y < arrays.Count() - 1; y++)
                {
                    if (arrays[y] <= arrays[y + 1]) continue;
                    int x = arrays[y];
                    arrays[y] = arrays[y + 1];
                    arrays[y + 1] = x;
                }
            }

            foreach (int z in arrays)
                Console.WriteLine(z);
            Console.ReadLine();
        }
    }
}


Crack.
1. 간단한 Bubble sort입니다.
2. x개의 원소가 주어졌을 때, 총 x * (x-1)번의 Loop를 돌면서 맨 앞에서부터 차례로 데이터를 Swap 합니다.

보다 C#에 가깝게 고치면 ref 키워드를 사용해서 값 참조합니다.
using System;
using System.Linq;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arrays = new int[] { 1, 5, 6, 2, 4, 7, 9 };
            for (int i = 0; i < arrays.Count(); i++)
            {
                for (int y = 0; y < arrays.Count() - 1; y++)
                {
                    if (arrays[y] <= arrays[y + 1]) continue;
                    SwapInt(ref arrays[y], ref arrays[y + 1]);
                }
            }
            foreach (int z in arrays)
                Console.WriteLine(z);
            Console.ReadLine();
        }

        public static void SwapInt(ref int _x, ref int _y)
        {
            int x = _x;
            _x = _y;
            _y = x;
        }
    }
}


ref 키워드와 비슷한 녀석이 out 키워드인데, ref는 반드시 선언되고 정의되어야 하지만, out은 선언만 되면 사용이 가능합니다.
만약, 여러개의 인자를 한번에 정의하려면 out 키워드가 더 적합합니다.
using System;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            string a, b;
            SetString(out a, out b);

            Console.WriteLine(a);
            Console.ReadLine();
        }

        static void SetString(out string x, out string y)
        {
            x = "A";
            y = "B";
        }
    }
}


하지만, 역시 Just C#이라면....
        static void Main(string[] args)
        {
            int[] arrays = new int[] { 1, 5, 6, 2, 4, 7, 9 };
            Array.Sort(arrays);
            foreach (int _x in arrays) {
                Console.WriteLine(_x.ToString());
            }
            Console.ReadLine();
        }

역순으로 Sort하는건, Sort 한뒤에 Method 하나더 불러줍니다.
 Array.Sort(arrays);
 Array.Reverse(arrays); //추가


문제 9.abcdefg 라는 문자열이 주어졌을때 n = 4 라고 할때
a c e g 
b d f
로 출력하시오.
Crack First. 이건 순열 문제로 문제의 의도를 파악하기 힘들군요. 만약 피시험자였다면 출제자에게 더 자세히 물어봤겠지만, 인터넷에 있는 후기를 퍼온 덕에 추가 정보를 얻을 수 없습니다만. 일단 기대값을 볼때 2자리마다 끝어서 출력하면 되지 않을까 싶네요.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TestCode
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = 4;
            string InputStr = "abcdefg";
            string[] Row = new string[2];

            char[] ArrayChar = InputStr.ToCharArray();

            int indexNum = 0;
            foreach (char c in ArrayChar)
            {
                if (indexNum % (n / 2) == 0) Row[0] += ArrayChar[indexNum];
                else Row[1] += ArrayChar[indexNum];
                indexNum++;
            }

            foreach (string s in Row)
            {
                Console.WriteLine(s);
            }

            Console.ReadLine();
        }
    }
}





문제 10. 예를들어 ccbbdddbcccaabbaaeeefff 가 주어졌을때 -> cbdaef 처음으로 등장한 알파벳만 모아서 보여주시오. 
using System;
using System.Linq;

namespace TestCode
{
     class Program
     {
          static void Main(string[] args)
          {
               string _x = "ccbbdddbcccaabbaaeeefff";
               char[] _arrChar = _x.ToCharArray();
               var query = (from x in _arrChar select x).Distinct().ToArray();
               Console.WriteLine(new string(query));
               Console.ReadLine();
          }
     }
}


Crack. 

간단한 Linq 문제입니다. =)



문제 11. 2개의 임의의 배열이 주어졌을때, Merge하고 Sort하라 

using System;
using System.Linq;

namespace TestCode
{
     class Program
     {
          static void Main(string[] args)
          {
               int[] One = new int[] { 1, 4, 6, 3, 9 };
               int[] Two = new int[] { 8, 2, 5 };
               int[] Merge = new int[One.Count() + Two.Count()];

               SetMerge(ref One, ref Two, ref Merge);
               Array.Sort(Merge);

               foreach(int x in Merge)
               {
                    Console.WriteLine(x);
               }
               Console.ReadLine();
          }

          static void SetMerge (ref int[] _x, ref int[] _y, ref int[] Result)
          {
               int MergeIdx = 0;
               for (int i = 0; i < _x.Count(); i++)
               {
                    Result[MergeIdx] = _x[i];
                    MergeIdx++;
                }
                for (int i = 0; i < _y.Count(); i++)
                {
                    Result[MergeIdx] = _y[i];
                    MergeIdx++;
                }
           }
     }
}

Crack. 
전통적인 방법으로는 위와 같이 배열 2개를 합한 크기의 배열을 만들고 For문 2번 돌려서 합치겠지만, C# 이라는 Linq가 있습니다.

using System;
using System.Linq;

namespace TestCode
{
     class Program
     {
          static void Main(string[] args)
          {
               int[] One = new int[] { 1, 4, 6, 3, 9 };
               int[] Two = new int[] { 8, 2, 5 };
               int[] Merge = new int[One.Count() + Two.Count()];

               SetMerge(ref One, ref Two, ref Merge);

               foreach (int x in Merge)
               {
                    Console.WriteLine(x);
               }
               Console.ReadLine();
           }

          static void SetMerge (ref int[] _x, ref int[] _y, ref int[] Result)
          {
               Result = _x.Concat(_y).OrderBy(x => x).ToArray(); //바로 이렇게 한방에 처리항 수 있습니다.
          }
     }
}



문제 12. 사용자 input 을 문자열로 받아서 숫자로 인식하는 프로그램을 만들어라.input에는 어떤 문자든지 올 수 있고, 어떤 규칙까지 허용하고 어떤 숫자까지 지원할지는 자유이다. 
Crack.
실제 MS사에서 면접 보신분의 후기에서 가져온 문제입니다. 이 경우 저라면 Hash 테이블에 단어장을 먼저 만들겠습니다.
먼저 필요한 테이블은 숫자만 카운팅 하는 테이블
1-one, 2-two, 3-three, 4-four, 5-five, 6-six, 7-seven, 8-eight, 9-nine, 10- ten, 11- eleven, 12-twelve, 13-thirteen, 14-fouteen, 15-fivteen .... 999-ninehundredniteennine
그리고 자릿수 3자리씩 잘라서 인식할 테이블을 만듭니다.
1000-thousand, 1000000-million, 1000000000-billion, 1000000000000-trillion
linq lambda 식으로 조회합니다.

//코드는 나중에 정리 예정.



문제 13.  두번째 문제는 Binary Tree가 있다고 가정하고 그 트리에 있는 숫자를 root에서 leaf까지 해서 도달하는 숫자의 합을 구하시오.
1
/ \
2  3
/\ /
4 5 6
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace TestCode
{
     class Program
     {
          static void Main(string[] args)
          {
               BinaryTree<int> broot = new BinaryTree<int>();
               broot.Root = new Node<int>(1);
               broot.Root.LNode = new Node<int>(2);
               broot.Root.RNode = new Node<int>(3);
               broot.Root.LNode.LNode = new Node<int>(4);
               broot.Root.LNode.RNode = new Node<int>(5);
               broot.Root.RNode.LNode = new Node<int>(6);

               ArrayList result = new ArrayList();
               broot.PrintNodeValue(broot.Root, 0, ref result);

               foreach (int i in result)
               {
                     Console.WriteLine(i);
               }

                Console.ReadLine();
           }
     }

      public class Node<T>
     {
          public Node<T> LNode;
          public Node<T> RNode;
          public int Value;
          public Node(int data)
          {
                this.Value = data;
          }
     }

     public class BinaryTree<T>
     {
          public Node<T> Root;
          public void PrintNodeValue(Node<T> node, int ParentValue, ref ArrayList result)
          {
                if (node.LNode != null) PrintNodeValue(node.LNode, node.Value + ParentValue, ref result);
                if (node.RNode != null) PrintNodeValue(node.RNode, node.Value + ParentValue, ref result);
                if (node.LNode == null && node.RNode == null)
                {
                     result.Add(node.Value + ParentValue);
                }
          }
     }
}


Crack.
2진트리는 1개의 노드가 최대 2개의 자식 노드를 가진 트리형입니다.
반듯이 왼쪽부터 채워져야 하는 규칙을 가지고 있지요.
아마도 이 문제 응용문제로 2진 트리에서 공통 조상을 찾아라 든가... 혹은 2진트리를 반전 시켜라와 2진트리 Leaf를 찾아라와 같은 응용 문제가 있을 수 있습니다.
중요한건 재귀함수를 쓸 줄 아느냐 일것 같은데, C#에서는 2진트리 쓸일이 사실 별로 없어서... 쿨럭...
2016/03/02 17:11 2016/03/02 17:11
일전에 Mesh생성하는 방법에 대해 써 놓은 적이 있는데,

Link : http://www.wolfpack.pe.kr/853

문득 Hexa로 만드는것도 가능하지 않을까? 고민했습니다.
그래서, 만들어 봤습니다.

먼저 Mesh 만든 절차를 그대로 따라 했습니다.
1. 좌표를 Vector3 array로 만든다.
* Mesh의 경우는 단 4개의 Vertex로 구성되면 되기 때문에, 좌표는 총 4개를 만들면 되죠.
예를 들어, pivot point가 Center에 있는 4각의 Mesh라면,
(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0) >> 각각 0, 1, 2, 3 번 이라고 합시다.

2. Polygon Array를 만듭니다. 하나 중요한건 Unity3D는 왼손 좌표계이므로 Surface가 -Z축에서 보여지려면 시계방향으로 Array를 생성해야 합니다.
* 0, 1, 2, 0, 2, 3 / 3개씩 잘라서 보면 0,1,2 폴리곤과 0, 2, 3 폴리곤 2개가 있어, 4각으로 보이죠.

자세한건 위의 Link 참조바랍니다.

Hexa의 경우는 간단히 다음과 같이 하면 되겠지하고 덤볐다가.. -_-;; 개피봤습니다.
사용자 삽입 이미지
1개 일때는 문제없이 0, 1, 3, 1, 2, 3, 0, 3, 4, 0, 4, 5 로 폴리곤 셋을 잘라주면 되는데.. 2개 이상만 되도 일정한 규칙을 발견하기 어려웠습니다.
사용자 삽입 이미지
슬슬 멘붕오기 시작하지요. 여기에 Y축까지 추가되면...
사용자 삽입 이미지
규칙은 사라지고 Chaos 만 남습니다. -_-;;
만약, Pivot이 Center에 있다면? 이라는 질문을 하게 되었고 Vetex를 정리해보니 다음과 같이 그런대로 규칙이 생깁니다.
사용자 삽입 이미지
정리하자면 2*2짜리 Hexa는 가로로 n개의 Vertex를 가집니다.
이걸 수식으로 정리하면 Hexa갯수가 x라면 Vertex수는
1 - 3
2 - 5
3 - 7
4 - 9
n - 2 * 헥사갯수 + 1

입니다만, Hexa는 항상 위에서 보는바와 같이 왔다 갔다 하므로 Y축 확장을 위해 우로 Vertex열이 1줄 더 있어야 하고 좌로도 1줄 더 있어야 하는 상황이 됩니다.

그리고, 또 하나 만난 문제는 저렇게 Vertex를 배열하는게 문제였습니다.
6각형은 60도로 나누어져 있는데 소숫점 오차로 인해 제대로 배열이 안되는것도 문제여서...
그냥 다음과 같이 간단히 일렬로 배열한뒤에...
사용자 삽입 이미지
홀수 열만 0.5정도 올려주면...
사용자 삽입 이미지
근사하게 육각으로 바뀌는 군욤. ㅋ

* 여기까지 코드는 다음과 같습니다.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Hexa : MonoBehaviour {

    private Vector3[] vertexs; 

    void Awake()
    {
        gameObject.AddComponent<MeshFilter>();
        gameObject.AddComponent<MeshRenderer>();
    }

    void Start () {
        SetVertex(5, 6);
    }
    
    void SetVertex(int xSize, int ySize)
    {
        //사전처리 부분입니다. 예외처리를 위해 가로 헥사수는 2이상, 세로 헥사수는 짝수로 고정합니다.
        if (xSize < 2) xSize++; //가로 헥사수, 예외 처리를 위해 2이상으로 강제 처리
        if (ySize % 2 == 1) ySize++; //세로 헥사수  //예외 처리를 위해 짝수로 강제 처리

        // 가로와 세로의 버텍스 수를 구합니다.
        int xDotSize = 2 * xSize + 2; //가로 버텍스 수
        int yDotSize = (ySize % 2 == 0) ? (3 * ySize / 2) + 1 : 3 * (ySize / 2 + 1); //세로 버텍스 수

        //For 문으로 자동으로 찍되 홀수 줄은 0.5f만큼 올립니다.
        MeshFilter MF = GetComponent<MeshFilter>();
        vertexs = new Vector3[xDotSize * yDotSize];
        for (int i = 0, y = 0; y > yDotSize * -1 ; y--)
        {
            for (int x = 0; x < xDotSize; x++, i++)
            {
                vertexs[i] = (x % 2 == 0) ? new Vector3(x, 0f, y) : new Vector3(x, 0f, y + 0.5f);
            }
        }
        MF.mesh.vertices = vertexs;
    }

    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);
    }
}


이제 문제는 Center를 찾는 겁니다. 의외로 어렵게 해결된 부분인데,
먼저 좌변의 Center Point들을 먼저 찾고 거기에 가로로 +2를 반복하였습니다.
그 이유는 다음의 그림에서 처럼 첫 Center는 2번째, 2번째 Center는 5번째... 이런식으로 나타 났고 이를 위해 수열 공식 찾느라 오래 걸렸습니다.
사용자 삽입 이미지
암튼 위의 결과를 보인 소스는 다음과 같습니다.

        //SetVertex() 함수 밑부분에 넣었습니다.
        //Find Center
        CenterP = new Vector3[xSize * ySize];
        for (int idx =0, y = 0; y < ySize; y++) {
            int verticesIdx = 0;
            verticesIdx = xDotSize * ((y / 2) + 1 + y) + 1 + (y % 2); //버텍스인덱스는 가로크기* ((y / 2) + 1 + y) + 1 + (y % 2), y는 세로변수 y
            CenterP[idx] = vertexs[verticesIdx];
            CenterIdx.Add(verticesIdx);
            idx++;
            for (int x = 1; x < xSize; x++)
            {
                CenterP[idx] = vertexs[verticesIdx + 2 * x]; //세로에서 구한 센터값에 +2 반복
                CenterIdx.Add(verticesIdx + 2 * x);
                idx++;
            }
        }

좀 복잡하지만, CenterP는 OnGizmo에서 디버깅 용으로 만든 변수이고, 실재 사용하는 변수명은 CenterIdx입니다.
클래스 변수는 다음과 같이 위의 2개 변수를 추가했구요.

    private Vector3[] vertexs;
    private Vector3[] CenterP;
    private List<int> CenterIdx = new List<int>();


마지막으로 OnGizmo를 다음과 같은 최종형태로 만들었습니다.
    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);

        if (CenterP == null) return;
        Gizmos.color = Color.yellow;
        for (int i = 0; i < CenterP.Length; i ++ )
            Gizmos.DrawSphere(CenterP[i], 0.1f);

    }


마지막으로 Vertex 연결 순서는 짝수 열과 홀수 열이 조금 다르지만, 정리하면,

                //총 6개의 폴리곤이 있어야 6각형이 나오므로...
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;
                
                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] + 1;
                
                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = CenterIdx[x] + xDotSize + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;
                
                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] + xDotSize - 1;
                
                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;
                triangles[idx + 17] = CenterIdx[x] - 1;

위의 코드를 좀 짧게 축약하면 다음과 같습니다.

                triangles[idx] = triangles[idx + 3] = triangles[idx + 6] = triangles[idx + 9] = triangles[idx + 12] = triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 1] = triangles[idx + 17] = CenterIdx[x] - 1;
                triangles[idx + 2] = triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = triangles[idx + 13] =  CenterIdx[x] + xDotSize;
                triangles[idx + 14] = triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;


이제 이걸 돌려 보면... 두둥..
사용자 삽입 이미지
전체 코드는 다음과 같습니다.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Hexa : MonoBehaviour {

    private Vector3[] vertexs;
    private Vector3[] CenterP;
    private List<int> CenterIdx = new List<int>();

    void Awake()
    {
        gameObject.AddComponent<MeshFilter>();
        gameObject.AddComponent<MeshRenderer>();
    }

    // Use this for initialization
    void Start () {
        SetVertex(5, 6);
    }
    
    void SetVertex(int xSize, int ySize)
    {
        if (xSize < 2) xSize++; //x 헥사수
        if (ySize % 2 == 1) ySize++; //y 헥사수
        int xDotSize = 2 * xSize + 2; //가로 버텍스 수
        int yDotSize = (ySize % 2 == 0) ? (3 * ySize / 2) + 1 : 3 * (ySize / 2 + 1); //세로 버텍스 수
        MeshFilter MF = GetComponent<MeshFilter>();
        vertexs = new Vector3[xDotSize * yDotSize];
        for (int i = 0, y = 0; y > yDotSize * -1 ; y--)
        {
            for (int x = 0; x < xDotSize; x++, i++)
            {
                vertexs[i] = (x % 2 == 0) ? new Vector3(x, 0f, y) : new Vector3(x, 0f, y + 0.5f);
            }
        }
        MF.mesh.vertices = vertexs;

        //Find Center
        CenterP = new Vector3[xSize * ySize];
        for (int idx =0, y = 0; y < ySize; y++) {
            int verticesIdx = 0;
            verticesIdx = xDotSize * ((y / 2) + 1 + y) + 1 + (y % 2);
            CenterP[idx] = vertexs[verticesIdx];
            CenterIdx.Add(verticesIdx);
            idx++;
            for (int x = 1; x < xSize; x++)
            {
                CenterP[idx] = vertexs[verticesIdx + 2 * x];
                CenterIdx.Add(verticesIdx + 2 * x);
                idx++;
            }
        }

        int[] triangles = new int[18 * (xSize * ySize)];

        for (int idx = 0, x = 0; x < CenterIdx.Count; x++)
        {
            if ((x / xSize) % 2 == 0)
            {
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - xDotSize - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;

                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] - xDotSize + 1;

                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] - xDotSize + 1;
                triangles[idx + 8] = CenterIdx[x] + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;

                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] - 1;

                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] - 1;
                triangles[idx + 17] = CenterIdx[x] - xDotSize - 1;

            }
            else
            {
                triangles[idx] = CenterIdx[x];
                triangles[idx + 1] = CenterIdx[x] - 1;
                triangles[idx + 2] = CenterIdx[x] - xDotSize;
                
                triangles[idx + 3] = CenterIdx[x];
                triangles[idx + 4] = CenterIdx[x] - xDotSize;
                triangles[idx + 5] = CenterIdx[x] + 1;
                
                triangles[idx + 6] = CenterIdx[x];
                triangles[idx + 7] = CenterIdx[x] + 1;
                triangles[idx + 8] = CenterIdx[x] + xDotSize + 1;

                triangles[idx + 9] = CenterIdx[x];
                triangles[idx + 10] = CenterIdx[x] + xDotSize + 1;
                triangles[idx + 11] = CenterIdx[x] + xDotSize;
                
                triangles[idx + 12] = CenterIdx[x];
                triangles[idx + 13] = CenterIdx[x] + xDotSize;
                triangles[idx + 14] = CenterIdx[x] + xDotSize - 1;
                
                triangles[idx + 15] = CenterIdx[x];
                triangles[idx + 16] = CenterIdx[x] + xDotSize - 1;
                triangles[idx + 17] = CenterIdx[x] - 1;
            }
            idx += 18;
        }

        MF.mesh.triangles = triangles;
        MF.mesh.RecalculateNormals();

    }

    private void OnDrawGizmos()
    {
        if (vertexs == null) return;
        Gizmos.color = Color.red;
        for (int i = 0; i < vertexs.Length; i++)
            Gizmos.DrawSphere(vertexs[i], 0.1f);

        if (CenterP == null) return;
        Gizmos.color = Color.yellow;
        for (int i = 0; i < CenterP.Length; i ++ )
            Gizmos.DrawSphere(CenterP[i], 0.1f);

    }
}


그럼~ 새해 복 많이 받으세요!
2016/02/09 17:17 2016/02/09 17:17

Go 언어 강좌 2

Developer/GO 2016/02/02 17:49
이번 편에는 Golang의 Essencial이 될만한 내용만 추려서 예제를 만들어 보았습니다.
먼저 struct 입니다.
C#이나 Java, C/C++ 등에서 자료구조로 많이 쓰이는 자료구조이죠?

이걸 설명하려면, 왜? Struct구조가 필요한지 알고 넘어가야 합니다.

일단 변하지 않는 "상수" const 부터 살펴보죠.
이넘은 연산하거나 변수로 사용할 수 없습니다.
예전에 계산기 두들길때 1+2 = 3 이 나온다 치면 1과 2가 상수입니다. 항상 정해진 값을 가지고 있지요.

package main

import (
    "fmt"
)

func main() {
    const x int = 1
    x += 1
    fmt.Println(x)
}


당연히 위의 소스를 실행하면 다음과 같이 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
# command-line-arguments
.\testVal.go:10: cannot assign to x
처음 한번 할당하면 바꿀수 없습니다. 그러다보니 개발자 입장에서 한번 선언해두고 다시 불러와서 재활용할 수 있는 개념이 필요했는데, 이것이 바로 변수입니다.

package main

import (
    "fmt"
)

func main() {
    var x int = 1
    x += 1
    fmt.Println(x)
}


위의 코드를 실행하면 다음과 같이 변수가 변경된 채로 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
2

하지만, 이것도 한계가 있었습니다. 변수 하나 하나는 재활용 할 수 있지만, 여러개의 숫자를 동시에 넣고 연산하는걸 한방에 하고 싶었습니다.
그래서 만든 개념이 배열입니다.

예를 들어, 한 팀에 5명이 있다고 치고, 5명의 점수를 숫자로 입력하면 한번에 처리하고 싶다면 변수 5개를 쓰는게 아니라, 배열 1번 선언으로 모든걸 해치울 수가 있죠.

package main

import (
    "fmt"
)

func main() {
    var x []int = []int{1, 2, 3, 4, 5}
    x[0] += 1
    fmt.Println(x[0])
}

위의 코드를 실행하면 첫번째 1이 2로 바뀌어 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
2

문제는 여기서 부터입니다.
점수와 이름을 같이 쓰고 싶습니다. 그럼, struct 개념이 없다면 이렇게 사용했겠죠?

package main

import (
    "fmt"
)

func main() {
    var x []string = []string{"a", "b", "c", "e", "f"}
    var y []int = []int{1, 2, 3, 4, 5}
    y[0] += 1
    fmt.Println(x[0], y[0])
}

위의 코드를 실행하면 각 배열의 첫번째 값만 출력이 됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
a 2
하지만, 역시 개발자는 줄이고 싶어합니다.
그래서, 만든 개념이 struct 입니다.
다양한 변수 Type를 한번에 저장할 수 있지요.
package main

import (
    "fmt"
)

func main() {
    s := Scoreboard{"a", 1}
    s.y += 1
    fmt.Println(s.x, s.y)
}

type Scoreboard struct { //대문자로 쓰면 public입니다. 소문자면 private
    x string
    y int
}


위코드를 실행하면 다음과 같이 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
a 2
짜잔..

여기에, 자주쓰는건 아에 struct method로 넣어버릴 수 있습니다. 
* 다음 예제는 난이도가 위의 예제에 비해 많이 상승해서 Add method 하나만 잘 익혀두면 좋겠네요.

package main

import (
    "fmt"
)

func main() {

    s := Scoreboard{
        []string{"a", "b", "c"},
        []int{2, 3, 4},
    }
    fmt.Println(s.Avg())
    fmt.Println(s.Add(0, 100))
    fmt.Println(s.y[0])
}

type Scoreboard struct {
    x []string
    y []int
}

func (s Scoreboard) Add(num int, sco int) int {
    s.y[num] += sco
    return s.y[num]
}

func (s Scoreboard) Avg() float32 {
    var _sum int = 0
    for i := 0; i < len(s.y); i++ {
        _sum += s.y[i]
    }
    var _av float32 = float32(_sum / len(s.y))
    return _av
}


위의 코드를 실행하면, 먼저 입력된 점수의 평균인 3을 출력하고 첫번째 학생에게 100점을 주고, 제대로 들어갔는지 확인합니다.
D:\DevelopSource\Go Lang>go run testVal.go
3
102
102
하지만, 이역시 문제가 많습니다.
왜냐하면, 

Scoreboard{
        []string{"a", "b", "c"},
        []int{2, 3, 4},
    }

이렇게 선언해놓고는 데이터가 추가되면 어떻게 될까요? 혹은 이름은 10자리인데, 점수는 11자리.. 
일렇게 되면 망했죠... 망했어요...

그래서 C#이나 Java 같은 현대적인 언어들은 List로 이를 해결합니다.
Golang도 List Package가 있지만, Go 언어의 기본 패키지로 이를 해결하려면 결국 포인트를 쓰던가... 아니면 처음부터 Slice로 만듭니다.

package main

import (
    "fmt"
)

func main() {

    //아래는 좀 원시적인 방법으로 포인터 선언하고 새로 다 때려넣는 방법
    //s := []*Score{}
    //row := new(Score)
    //row.x = "a"
    //row.y = 1
    //s = append(s, row)

    //조금 아는척하면서 Slice로 init하고 Slice append로 데이터 넣는 방법
    Scoreboard := Scoreboard{
        {x: "a", y: 1},
        {x: "b", y: 2},
        {x: "c", y: 3},
    }
    Scoreboard = append(Scoreboard, Score{x: "d", y: 4})
    fmt.Println(Scoreboard[3].x, Scoreboard[3].y)
}

type Score struct {
    x string
    y int
}

type Scoreboard []Score


위 코드를 실행하면 
D:\DevelopSource\Go Lang>go run testVal.go
d 4
가 출력됩니다. =)

이제 마지막으로 한 맺힌 Tree 만들어 봅시다.

A는 B, C를 가집니다. B는 D, E를 가지고 C는 F를 가지고 있습니다. B를 삭제하면 이하에 있는 값이 삭제되도록 하는 겁니다.
* 단 빈공간으로 입력했던 흔적이 남아야 합니다.
package main

import (
    "fmt"
)

func main() {
    ScoreTree := ScoreTree{
         {Parent: "", Value: "A"},
          {Parent: "A", Value: "B"},
          {Parent: "A", Value: "C"},
          {Parent: "B", Value: "D"},
          {Parent: "B", Value: "E"},
          {Parent: "C", Value: "F"},
    }

     Remove(ScoreTree, "B")

     for key, val := range ScoreTree {
         fmt.Println("K:", key, "P:", val.Parent, "V:", val.Value)
     }
}

func Remove(obj ScoreTree, _x string) {
     fmt.Println("Remove Letter:", _x)

     for key, val := range obj {
         if val.Value == _x {
             obj[key].Value = ""
         }
         if val.Parent == _x {
             obj[key].Parent = ""
             Remove(obj, obj[key].Value)
         }
     }
}

type Score struct {
     Parent string
     Value string
}

type ScoreTree []Score


위의 코드를 실행하면...
D:\DevelopSource\Go Lang>go run testVal.go
Remove Letter: B
Remove Letter: D
Remove Letter: E
K: 0 P: V: A
K: 1 P: A V:
K: 2 P: A V: C
K: 3 P: V:
K: 4 P: V:
K: 5 P: C V: F

깔끔하게 실행되는 군요! 

한이 서린 재귀함수 호출은 이제 마지막 단계로 빈 값이 있는 Item들을 날려버리는 겁니다.

package main

import (
    "fmt"
)

func main() {
     ScoreTree := ScoreTree{
         {Parent: "", Value: "A"},
         {Parent: "A", Value: "B"},
         {Parent: "A", Value: "C"},
         {Parent: "B", Value: "D"},
         {Parent: "B", Value: "E"},
         {Parent: "C", Value: "F"},
     }

     ScoreTree.Remove(ScoreTree, "B")
     ScoreTree.Trunc()
     for key, val := range ScoreTree {
         fmt.Println("K:", key, "P:", val.Parent, "V:", val.Value)
     }
}

func (s ScoreTree) Remove(obj ScoreTree, _x string) {
     for key, val := range obj {
         if val.Value == _x {
             obj[key].Value = ""
         }
         if val.Parent == _x {
             obj[key].Parent = ""
             s.Remove(obj, obj[key].Value)
         }
     }
}

func (s *ScoreTree) Trunc() {
     var r ScoreTree
     for _, val := range *s {
         if val.Value != "" {
             r = append(r, val)
         }
    }
     *s = r
}

type Score struct {
     Parent string
     Value string
}

type ScoreTree []Score

실행하면... 깔끔하게 새로 작성해 줍니다. (포인터 드뎌 한번 썼네요...)
D:\DevelopSource\Go Lang>go run testVal.go
K: 0 P: V: A
K: 1 P: A V: C
K: 2 P: C V: F

이번편 마지막으로 드디어 Interface입니다.
OOP에서는 대표적으로 abstract Class와 interface Class가 있습니다.
하지만, 추상클래스의 경우는 처음 설계에서 대충 껍데기만 만들고 들어가서는 상속받아 사용하고 이걸 또 상속받아 사용하고 또 상속받아 사용하고... 이러다 망하죠.
그래서 최근 추세는 interface class를 만들어 놓고 1단계만 확장하는 개념으로 사용합니다.
여기에 C#은 Vrtual method라는 개념까지 겹쳐있어서 머리아프게 하지만, 별거 없음.

public class 짐승
{
 public virtual void 짖기()
 {
 Console.WriteLine("없어");
 }
}
 
public class 강아지 : 짐승
{
 public override void 짖기()
 {
 Console.WriteLine("으르릉!");
 }
}
 
//이하 사용법
//강아지 강쥐 = new 강아지();
//강쥐.짖기();


이하 추상화 클래스 C#
public abstract class 짐승
{
 public abstract void 짖기();
}
 
public class 강아지 : 짐승
{
 public override void 짖기()
 {
 Console.WriteLine("멍멍멍멍");
 }
}
 
//사용
//강아지 강쥐 = new 강아지();
//강쥐.짖기();


이하는 interface입니다.
 public interface 짐승
 {
 void 짖기();
 }

 class 강아지 : 짐승
 {
 private string _이름;
 public void 짖기()
 {
 Console.WriteLine(이름 + ":멍멍!");
 }
 public string 이름
 {
 get
 {
 return _이름;
 }
 set
 {
_이름 = value;
 }
 }
 }

//사용
//강아지 강쥐 = new 강아지();
//강쥐.이름 = "멍멍이";
//강쥐.짖기(); 


뭐.. 둘이 사실상 비슷합니다만, 메소드를 오버라이딩 하지 않아도 되기 때문에 인터페이스가 약간 유리? 그리고, 무한 상속 루프에 안걸리죠.
그래서인지 Golang은 인터페이스만 지원합니다.

뭐 암튼... 인터페이스 예제는 다음과 같습니다.

package main

import (
    "fmt"
)

func main() {
     fmt.Println("=============")
     maru := Dog{Animal{"Maru", 5, 3, 1}, "LA"}
     var m Dogs = maru
     m.Hi()
     m.Stay()
     fmt.Println("=============")
     ruru := Cat{Animal{"Ruru", 2, 5, 10}, "Seoul"}
     var c Cats = ruru
     c.Hi()
}

type Animal struct {
     name string
     age int
     speed int
     attack int
}

func (a Animal) Hi() {
     fmt.Println("Im Animal as ", a.name)
}

type Dog struct {
     Animal
     home string
}

func (d Dog) Stay() {
     fmt.Println("I stay at ", d.home)
}

type Cat struct {
     Animal
     home string
}

func (c Cat) Hi() {
     fmt.Println("Ningan! Give me food!")
}

type Dogs interface {
     Hi()
     Stay()
}

type Cats interface {
     Hi()
}


위코드는
- Dogs : Animal struct method중 Hi()를 그냥사용하고 Stay() 추가했습니다.
- Cats : Animal struct method중 Hi()를 교체했습니다.

실행결과는 
D:\DevelopSource\Go Lang>go run testVal.go
=============
Im Animal as Maru
I stay at LA
=============
Ningan! Give me food!

아마도, Go Language의 수 많은 부분중에 기본 사용법 80% 이상은 예제로 남긴것 같네요.
그럼 다음 시간에는 웹서버 그냥 한번 띄워보겠습니다.

* 유니티 플러그인을 이걸로 대체할 수 있으면 좋겠는데 말입니다. LOL
(찾아본 결과 Go로 생성되는 .lib는 C로 Wrapper만들어서 사용할 수 있다는 이야기가 있네요...)

* 위에서 포인터에 대한 설명없이 포인터를 사용했는데, 다음의 예제로 감잡아서 그냥 사용했어요.

=================
package main

import (
    "fmt"
)

func main() {
    a := 43 //var a int = 43;
    fmt.Println("Value of a : ", a)
    fmt.Println("Reference of a : ", &a)

    b := &a //var b *int = &a;
    fmt.Println("Address of a : ", b)
    fmt.Println("Value of a : ", *b)
    fmt.Println("Reference of a : ", &b)

    *b = 32
    fmt.Println("Value of a by *b handle : ", a)
}


결과는 
D:\DevelopSource\Go Lang>go run Pointer.go
Value of a : 43
Reference of a : 0xc0820022b0
Address of a : 0xc0820022b0
Value of a : 43
Reference of a : 0xc082024028
Value of a by *b handle : 32

포인터는 그냥 주소만 가지고 있고 실재 값은 다른곳에 저장되는 이른바 "바로 가기" 폴더입니다.
해당 값을 꺼내올때는 & (레퍼런스)로 꺼내 올 수 있구요.

2016/02/02 17:49 2016/02/02 17:49