2021년 8월 17일 화요일

C# linq를 이용한 리스트 변환

 다음과 같은 객체의 리스트가 있습니다. 여기서 이름만 따로 뽑아 리스트를 만들고 싶습니다.


internal class Student
{
    internal string name;
    internal int kor;
    internal int eng;
    internal int math;
}

internal List<string> Student2NameList(List<Student> studentList)
{  // Student리스트에서 이름만 뽑아 새 리스트 만듦
    var nameList = new List<string>();
    
    foreach(var student in studentList)
        nameList.Add(student.Name);
return nameList; }

Student2NameList메서드는 보통 위와 같이 구현할 수 있습니다.

하지만 C#에서 linq를 이용하면 이런 작업을 좀 더 쉽게 할 수 있습니다. linq(Language Integrated Query)란 것은 배열이나 리스트 등에서 필요한 데이터를 추출하는 데이터베이스의 Query문과 같은 것입니다.

Query문은 다음과 같이 구성되어 있습니다.


    from 임시변수 in 데이터뭉치
    where 조건식                     // 생략가능
    select 수식;

데이터뭉치(리스트/배열)의 각 요소가 순서대로 임시변수에 대입하여 조건식을 확인한 후 수식의 새로운 데이터뭉치를 만들어내는 것입니다. 즉

internal List<string> Student2NameList(List<Student> studentList)
{  // Student리스트에서 이름만 뽑아 새 리스트 만듦
var nameList = from student in studentList // where문 생략 select student.name; ........ }
과 같이 함으로써 studentList 각 요소의 name만 모인 새로운 데이터뭉치를 만들 수 있습니다. 다만 이 데이터뭉치의 타입은 List도 Array도 아닌 IEnumerable형입니다. 그러므로
internal List<string> Student2NameList(List<Student> studentList)
{  // Student리스트에서 이름만 뽑아 새 리스트 만듦
var nameList = from student in studentList // where문 생략 select student.name; return nameList.ToList(); }
와 같이 구현 가능합니다.
만약 합격한 학생들의 리스트가 필요하다면 다음과 같이 where문 추가하고 select문을 변경시켜야 합니다.
internal class PassedStudent
{
    internal string name;
    internal float average;
    internal PassedStudent(string name, float average)
    {
        this.name = name;
        this.average = average;
    }
}

internal List<string> PassedStudentList(List<Student> studentList)
{  // Student리스트에서 이름만 뽑아 새 리스트 만듦
var nameList = from stu in studentList where (stu.kor + stu.eng + stu.math) / 3F > 60 select new PassedStudent(stu.name,
(stu.kor + stu.eng + stu.math) / 3F);
return nameList.ToList(); }




2021년 6월 22일 화요일

C# 모든 Enum값 Iterator

 



internal enum EnumType { A, B, C, }


foreach (EnumType idx in Enum.GetValues(typeof(EnumType)))
    ....;


2020년 11월 13일 금요일

C#에서의 더블버퍼링(Double Buffering)

일반적으로 윈도우의 DC에 그림을 그리면 그릴 때마다 번쩍거리는 현상이 일어납니다. 이러한 현상을 막기 위해 Double Buffering이라는 기법을 사용합니다.

이것은 화면을 두개 마련해 놓고, 뒤 화면에 그림을 그린 후 한번에 앞쪽으로 옮기는 방법이죠.

다른 언어에서는 화면을 두개 만드는 것부터 어렵지만 C#에서는 상당히 쉽게 되는군요.

C#에서 다음과 같은 Form Application으로 form과 panel을 만들었습니다.


그리고 양쪽에 큰 원을 그렸습니다.


        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.FillEllipse(Brushes.Black, 100, 100, 300, 300);
        }
        private void panel1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.FillEllipse(Brushes.Black, 10, 10, 300, 300);
        }

이 상태로는 더블버퍼링이 적용되지 않아 원이 깜박입니다.

더블버퍼링을 적용하기 위해서는


        public Form1()
        {
            InitializeComponent();
            DoubleBuffered = true;
        }
를 포함시켜야 합니다. 실행해 보면 form에 그리는 원은 잘 출력되는 반면 panel에 그리는 원은 여전히 깜박입니다. 이것은 더블버퍼링이 form에만 적용되었다는 뜻이죠. 판넬에도 DoubleBuffered를 true로 세팅해야 합니다.

해당 컨트롤의 Load이벤트가 있는 경우에는 그곳에 넣어도 되지만, 없을 경우에는 다음과 같이 확장된 컨트롤을 만들어야죠

    internal class DoublePanel : System.Windows.Forms.Panel
    {
        internal DoublePanel()
        {
            DoubleBuffered = true;
        }
    }
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.panel1 = new DoublePanel();
            this.SuspendLayout();
            // 
이부분은 원래 System.Windows.Forms.Panel() 였지만, DoublePanel로 바꿈으로써 기존의 Panel역할을 그대로 하면서 생성될 때 DoubleBuffered를 true로 세팅하게 됩니다.

2019년 10월 5일 토요일

C#의 프로퍼티(Property of C#)

OOP 프로그램의 특징 중 하나인 capsulation을 위해 대부분 정보값을 얻는 함수와 쓰는 함수를 함께 만들어야 합니다.

class Student
{
private :
    int studentNo;
    char name[32];
public :
    int GetStudentNo()
    {
        return studentNo;
    }
    char *GetName()
    {
        return name;
    }
};

멤버의 정보를 은닉한 덕에, 정보를 얻기 위해서는 항상 멤버에 접근하는 함수가 있어야 합니다.

그래서 C#에서는 아예 은닉된 정보에 접근하는 property라는 개념을 만들었죠.

// 가
public class Student
{
    private int studentNo;
    public int StudentNo
    {
        get
        {
            return studentNo;
        }
        private set
        {
            studentNo = value;
        }
    }
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            name = value;
        }
    }
}

이것이 번거롭다면 다음과 같이 프로퍼티를 간단하게 만들 수도 있습니다.

// 나
public class Student
{
    private int StudentNo { get; private set; }
    private string Name { get; private set; }
}

겉보기에는 의 방법과 의 방법이 크게 다르지 않습니다. 둘 다 다음과 같은 방식으로 쓸 수 있죠.

publc StudentFunction(Student student)
{
    int no = student.StudentNo;    // 학생번호 얻어오기
    student.StudentNo = 12345;     // private멤버에 접근(set) 불가
}


그렇다면 이 두 가지 방법이 어떻게 다를까요?

먼저 의 방법은 정수형 studentNo와 문자열형 name이라는 멤버변수를 만들고 각각에 해당하는 StudentNo와 Name이라는 프로퍼티를 만듧니다. 그 때문에 일반변수인 student를 사용할 수 있습니다. ref나 out을 붙여서 쓸 수 있다는 말이죠.


// 가
public class Student
{
    private int studentNo;
    public int StudentNo
    {
        get
        {
            return studentNo;
        }
        private set
        {
            studentNo = value;
        }
    }
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            name = value;
        }
    }
    public Student()
    {
        Reader(out studentNo, out name);
    }
}




의 경우, 멤버변수를 생략하고 프로퍼티만 표현한 것으로, 내부적으로는 다음과 같이 표현됩니다.

// 나
public class Student
{
    private int _s3DRfxD4Se;  // 컴파일러가 만든 변수
    public int StudentNo
    {
        get
        {
            return _s3DRfxD4Se;
        }
        private set
        {
            _s3DRfxD4Se = value;
        }
    }
    private string _y7FdV4Ee;  // 컴파일러가 만든 변수
    public string Name
    {
        get
        {
            return _y7FdV4Ee;
        }
        private set
        {
            _y7FdV4Ee = value;
        }
    }
}

즉 컴파일러가 자기 맘대로 이름을 정해 변수를 만든 후 위와 같은 정형화된 코드를 만들어내는 것입니다.
이때 우리는 프로퍼티의 이름을 알 뿐 컴파일러가 만든 변수의 이름을 알지 못합니다. 그러므로 의 경우와 같이 변수의 참조를 사용할 수 없습니다.


// 나
public class Student
{
    private int StudentNo { get; private set; }
    private string Name { get; private set; }

    public Student()
    {
        Reader(out StudentNo, out Name);  // 컴파일 에러
    }
}

2018년 11월 2일 금요일

C# - 이름으로 객체 생성 2

밑에 C#에서 이름으로 객체를 생성하는 방법이 있습니다만, 가끔 이런 코드가 불가능한 경우가 있습니다. 특히 모바일용 Portable 코드를 사용할 때 말입니다.

using System.Reflection;

namespace ClassName
{
    internal class ClassA
    {
        internal void Write()
        {
            Console.WriteLine("ClassA");
        }
    }
    internal class ClassB
    {
        internal void Write()
        {
            Console.WriteLine("ClassB");
        }
    }
}

이런 상황에서는 대부분 Assembly의 GetExcutingAssembly 함수를 사용할 수가 없습니다.
이 경우에는 이런 식으로 사용할 수가 있죠.

    string className = "ClassName.ClassA";
    Type type = Type.GetType(className);
    if (type == null)
        throw new NotImplementedException();
    object obj = Activator.CreateInstance(type);
    var myClass = obj as SpObj.CompAI.AIBrain;
    if (myClass == null)
        throw new NotImplementedException();

2018년 6월 12일 화요일

C# - 이름으로 객체 생성

지난번에 이름으로 멤버변수 및 메서드에 접근하는 방법을 살펴보았습니다. 비슷한 방식으로 클래스 이름으로 객체를 만드는 방법 역시 존재합니다.


using System.Reflection;

namespace ClassName
{
    internal class ClassA
    {
        internal void Write()
        {
            Console.WriteLine("ClassA");
        }
    }
    internal class ClassB
    {
        internal void Write()
        {
            Console.WriteLine("ClassB");
        }
    }
}

이와 같은 두개의 클래스가 있습니다. 여기서 클래스 이름("ClassA" 또는 "ClassB"를 가지고 객체를 만들기 위해서는 다음과 같이 할 수 있습니다.

using System.Reflection;

namespace ClassName
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly creator = Assembly.GetExecutingAssembly();
            object obj = creator.CreateInstance("ClassName.ClassA");
            if (obj is ClassA)
                (obj as ClassA).Write();
            obj = creator.CreateInstance("ClassName.ClassB");
            if (obj is ClassB)
                (obj as ClassB).Write();
        }
    }
}

이와 같이 Assembly 객체를 만들고 나서 CreateInstance메서드로 객체를 생성할 수 있습니다. 단, 이 경우에 클래스는 반드시 namespace까지 포함하고 있는 이름이어야 합니다. 이 경우에 Program과 ClassA, ClassB가 같은 namespace에 있으므로 코드에서 객체를 만들 때는 ClassName을 생략할 수 있지만, 문자열로 객체를 만들 때는 생략할 수 없습니다.

또한 CreateInstance의 반환값은 object형이므로 is, as연산자를 통해 형변환 후 작업을 해야겠죠.

using System.Reflection;

namespace ClassName
{
    class Program
    {
        static void Main(string[] args)
        {
            string clsName = Console.ReadLine();
            Assembly creator = Assembly.GetExecutingAssembly();
            object obj = creator.CreateInstance("ClassName." + clsName);
            if (obj is ClassA)
                (obj as ClassA).Write();
            else if (obj is ClassB)
                (obj as ClassB).Write();
        }
    }
}

만약 이런 식으로 형을 하나하나 검사하기 힘들다면 상속관계를 이용할 수 있습니다.


using System.Reflection;

namespace ClassName
{
    internal abstract class ClassBase
    {
        internal abstract void Write();
    }
    internal class ClassA : ClassBase
    {
        internal override void Write()
        {
            Console.WriteLine("ClassA");
        }
    }
    internal class ClassB : ClassBase
    {
        internal override void Write()
        {
            Console.WriteLine("ClassB");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string clsName = Console.ReadLine();
            Assembly creator = Assembly.GetExecutingAssembly();
            object obj = creator.CreateInstance("ClassName." + clsName);
            if (obj is ClassBase)
                (obj as ClassBase).Write();
        }
    }
}

다만 namespace 안에 있을 때와 클래스 안에 중복된 클래스에는 이런 차이가 있습니다.

using System.Reflection;

namespace SpaceName
{
    internal class ClassA
    {
    }
}
internal class ClassName
{
    internal class ClassB
    {
    }
}

object a = creator.CreateInstance("SpaceName.ClassA");
object b = creator.CreateInstance("ClassName+ClassB");


2018년 5월 30일 수요일

C# - 이름으로 변수/메서드 접근

public class Exam
{
    public int korean;
    public int english;
    public int math;
}

public class ExamWrite
{
    public void Write(Exam exm, string subject)
    {
        if(subject == "korean")
            Console.WriteLine("korean : {0}", exm.korean);
        if(subject == "english")
            Console.WriteLine("english : {0}", exm.english);
        if(subject == "math")
            Console.WriteLine("math : {0}", exm.math);
    }
}

와 같은 프로그램이 있다고 해 봅시다. subject를 검색해서 같은 이름의 멤버변수값을 출력하는 프로그램이죠.
그런데 만약 과목이 수십개라면 어떨까요? 저런 if문이 수십개가 들어가야 합니다. 이럴 경우에는 문자열로 직접 변수이름을 가져올 수 있습니다. 이런 기능을 reflection이라고 합니다. 그러므로 다음과 같은 using문이 필요합니다.

using System.Reflection;

reflection 기능을 사용하기 위해서는 먼저 해당 객체의 타입을 필요로 합니다. typeof함수로서 Exam이라는 클래스의 정보를 얻어옵니다.

            Type tp = typeof(Exam);

다음에는 이 Exam이라는 클래스의 정보가 담긴 tp라는 객체에서 필드의 정보를 얻어와야겠죠. 필드의 정보를 얻기 위해서는 GetField라는 메서드를 사용합니다.
즉 다음 명령어는 subject에 담겨있는 문자열과 같은 이름의 멤버변수를 찾으라는 명령입니다.

            FieldInfo fld = tp.GetField(subject, BindingFlags.Instance |
                                                 BindingFlags.Static |
                                                 BindingFlags.Public |
                                                 BindingFlags.NonPublic);

여기서 BindingFlags는 각각 일반멤버변수, 정적멤버변수, 공용번수, 공용이 아닌 변수들을 모두 찾으라는 명령어죠. 이 외에 많은 플래그가 있지만, 가장 중요한 것은 이것들입니다. 필요한 것만 골라서 조합하면 됩니다.
만약 제대로 찾았다면 fld에는 그 변수에 대한 정보가 들어갑니다. 찾지 못했다면 null이 반환됩니다.
이 변수의 값을 얻기 위해서는

            object point = fld.GetValue(exm);

와 같이 할 수 있습니다. 위에서 찾은 subject라는 변수의 정보를 exm이라는 객체에서 찾아 그 값을 반환하라는 명령이죠. 다만 아직까지 이 필드의 정확한 타입을 알 수 없기에 반환값은 object형입니다. 하지만 이 명령어가 제대로 실행되었다면 (점수는 모두 정수형이므로) point에는 정수형 값이 들어가 있을 것입니다. 그러므로 값을 출력하기 위해서는

            if (point is int)
                Console.WriteLine("{0} : {1}", fld.Name, (int)point);

와 같이 쓸 수 있습니다.(이때 fld.Name에는 위에서 받아온 필드정보의 이름입니다)
즉,

using System.Reflection;

....

public class ExamWrite
{
    public void Write(Exam exm, string subject)
    {
        Type tp = typeof(Exam);
        FieldInfo fld = tp.GetField(subject, BindingFlags.Instance |
                                             BindingFlags.NonPublic |
                                             BindingFlags.Public |
                                             BindingFlags.NonPublic);
        object point = fld.GetValue(exm);
        if (point is int)
            Console.WriteLine("{0} : {1}", fld.Name, (int)point);
    }
}

와 같이 사용할 수 있죠.

그런데 이런 식으로 하면 한번에 하나의 성적만 출력 가능하죠. 모든 성적을 한번에 출력할 방법은 없을까요?
GetField가 필드 하나의 정보를 얻어오는 것이라면 GetFields는 모든 필드의 정보를 가져오는 메서드입니다. 그러므로 이 함수를 사용하면 모든 필드에 대한 조작을 할 수 있습니다.

using System.Reflection;

....

public class ExamWrite
{
    public void WriteAll(Exam exm)
    {
        Type tp = typeof(Exam);
        FieldInfo[] flds = tp.GetFields(BindingFlags.Instance |
                                        BindingFlags.Static |
                                        BindingFlags.Public |
                                        BindingFlags.NonPublic);
        foreach (var f in flds)
        {
            object point = f.GetValue(exm);
            if (point is int)
                Console.WriteLine("{0} : {1}", f.Name, (int)point);
        }
    }
}

마찬가지로 멤버변수에 값을 넣기 위해서는 FieldInfo의 GetValue 대신 SetValue를 사용할 수 있습니다.

using System.Reflection;

....

public class ExamWrite
{
    public void Perfect(Exam exm)
    {
        Type tp = typeof(Exam);
        FieldInfo[] flds = tp.GetFields(BindingFlags.Instance |
                                        BindingFlags.Static |
                                        BindingFlags.Public |
                                        BindingFlags.NonPublic);
        foreach (var f in flds)
        {
            object point = f.GetValue(exm);
            if (point is int)
                f.SetValue(exm, 100);
        }
    }
}

이것은 exm의 모든 변수값을 100점으로 만드는 함수가 되겠습니다.

만약 다음과 같이

public class Exam
{
    public const int korean;
    public const int english;
    public const int math;
}

와 같이 모두 const로 설정되어 있을 경우에도 이상없이 실행됩니다(다만 Perfect메서드에서는 const변수의 값을 바꾸려 시도하고 있으므로 TargetInvocationException이 뜹니다).

---------------------------

마찬가지로 메서드 역시 이름으로 호출이 가능합니다. 이 경우에는 GetField 대신 GetMethod를 사용할 수 있습니다.

        Type tp = typeof(ExamWrite);
        MethodInfo method = tp.GetMethod("WriteAll");

그리고 이 메서드를 호출하기 위해서는 Invoke 메서드를 사용할 수 있죠.

        method.Invoke(ew, new object[] { exm });

이것 역시 ew라는 객체를 통해 위에서 얻어온 "WriteAll"이라는 메서드를 호출하라는 뜻이죠. 이 메서드의 인수는 object의 배열을 통해 전달됩니다.

using System.Reflection;

....

        static void Main(string[] args)
        {
            Exam exm = new Exam();
            ExamWrite ew = new ExamWrite();

            Type tp = typeof(ExamWrite);
            MethodInfo method = tp.GetMethod("WriteAll");
            method.Invoke(ew, new object[] { exm });
        }

---------------------------

멤버변수나 메서드들은 모두 접근한정자(private, public 등)를 가지고 있습니다. 이 접근한정자는 컴파일시 확인을 하게 됩니다.
그러나 이 방식으로 멤버에 접근하는 것은 컴파일시가 아니라 실행시이기 때문에 접근한정자가 의미가 없습니다. 즉 private로 꼭꼭 숨겨놓은 멤버도 이 방식으로 접근하면 값을 읽어오거나 바꿀 수 있습니다. 이런 것을 주의해야 합니다.

2017년 9월 3일 일요일

do while(false)

어떤 작업을 하다가 문제가 생겼을 때 원래대로 복원하고 리턴해야 할 경우가 있습니다.
메모리 관리가 필요한 C++에서

MyClass MemoryAllocate()
{
    ClassA a = new ClassA();
    if(a == null)
        return NULL;

    ClassB b = new ClassB();
    if(b == null)
    {
        delete a;
        return NULL;
    }

    ClassC c = new ClassC();
    if(c == null)
    {
        delete a;
        delete b;
        return NULL;
    }

    MyClass mc = new MyClass(a, b, c);
    if(mc != null)
    {
        delete a;
        delete b;
        delete c;
        return NULL;
    }
    return mc;
}

그런데 이런 식으로 한다면 메모리할당에 문제가 생겼을때 처리하는 루틴이 많이 분산되어 있네요. 에러처리하는 루틴은 한군데 모아놓는 것이 낫죠.

MyClass MemoryAllocate()
{
    ClassA a = NULL;
    ClassB b = NULL;
    ClassC c = NULL;
    MyClass mc = NULL;

    a = new ClassA();
    if(a == NULL)
        goto ErrRtn;

    b = new ClassB();
    if(b == NULL)
        goto ErrRtn;

    ClassC c = new ClassC();
    if(c == NULL)
        goto ErrRtn;

    MyClass mc = new MyClass(a, b, c);
    if(mc != NULL)
        goto ErrRtn;
    return mc;

ErrRtn:
    if(a != NULL)
        delete a;
    if(b != NULL)
        delete b;
    if(c != NULL)
        delete c;
    if(mc != NULL)
        delete mc;
    return NULL;
}

그런데 goto문을 쓴다는 것에 거부감을 느끼는 사람이 많습니다. 저 역시 goto문은 쓰지 않으려고 하고 있거든요.
이런 경우에 do while(false문을 사용하면 구조적으로 해결할 수 있습니다.


MyClass MemoryAllocate()
{
    ClassA a = NULL;
    ClassB b = NULL;
    ClassC c = NULL;
    MyClass mc = NULL;

    do
    {
        a = new ClassA();
        if(a == NULL)
            break;

        b = new ClassB();
        if(b == NULL)
            break;

        ClassC c = new ClassC();
        if(c == NULL)
            break;

        MyClass mc = new MyClass(a, b, c);
        if(mc != NULL)
            break;
        return mc;
    } while(0);

    if(a != NULL)
        delete a;
    if(b != NULL)
        delete b;
    if(c != NULL)
        delete c;
    if(mc != NULL)
        delete mc;
    return NULL;
}



2017년 7월 27일 목요일

c# enum 마지막값 얻기

using System;
using System.Linq;

public class Test
{
    internal enum ttt
    {
        QAZ,
        XSW,
        EDC,
        VFR,
    }

    public static void Main()
    {
        var first = Enum.GetValues(typeof(ttt)).Cast<ttt>().First();
        var last = Enum.GetValues(typeof(ttt)).Cast<ttt>().Last();
        Console.WriteLine(first);
        Console.WriteLine(last);
    }
}
QAZ
VFR


C# 소멸자와 Dispose

다음과 같은 C++ 프로그램을 봅시다

#include <iostream>
using namespace std;

class Test
{
    public :
    Test()
    {
        puts(" Constructor");
    }
    ~Test()
    {
        puts(" Destructor");
    }
};

int main()
{
    puts("Program Start");
    {
        Test t;
        puts("  Do with Test");
    }
    puts("Program End");
    return 0;
}
Program Start
 Constructor
  Do with Test
 Destructor
Program End

블럭 안에서 Test를 선언해 사용하면, 블럭을 빠져나갈때 자동으로 소멸자(Detstructor, ~Test())가 호출되어 Test에 대한 마무리를 할 수 있습니다.

이 프로그램을 C#으로 포팅해서 실행해 보겠습니다.

using System;

public class Test
{
    public Test()
    {
        Console.WriteLine(" Constructor");
    }
    ~Test()
    {
        Console.WriteLine(" Destructor");
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Program Start");
        {
            Test t = new Test();
            Console.WriteLine("  Do with Test");
        }
        Console.WriteLine("Program End");
    }
}
Program Start
 Constructor
  Do with Test
Program End
 Destructor

조금 다른 결과가 나왔군요. 소멸자가 호출되긴 하는데, 그 시점이 블럭을 빠져나갈 때가 아니라 프로그램 종료된 후입니다.

C++에서는 t라는 객체가 스택 위에 잡힙니다. 그래서 블록이 닫히고 스택이 해제될 때 해당 객체의 소멸자를 호출할 수 있습니다.
반면 C#의 경우 t 객체는 스택이 아니라 힙(heap) 위에 동적할당의 형태로 잡힙니다. 그래서 스택과는 달리 해제될 시점을 알 수 없는 것이죠.
실제로 C++에서도

        Test *t = new Test();

형태로 동적할당을 하면 블럭을 빠져나갈 때가 아니라 t를 해제할때 소멸자가 호출됩니다.
다만 C#에서는 메모리관리를 프로그래머가 아니라 시스템(Gabage Collection)에서 하므로 메모리해제할 시점을 프로그래머가 확정할 수 없습니다. 위 예제에서처럼 프로그램 종료할 때라든지, 아니면 메모리가 부족해져 가비지 콜렉션을 할때 실행됩니다.

이런 경우 C#에서는 소멸자 대신 Dispose를 사용하는 것이 좋습니다.

using System;

public class Test : IDisposable
{
    public Test()
    {
        Console.WriteLine(" Constructor");
    }
    public void Dispose()
    {
        Console.WriteLine(" Dispose");
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Program Start");
        using(Test t = new Test())
        {
            Console.WriteLine("  Do with Test");
        }
        Console.WriteLine("Program End");
    }
}
Program Start
 Constructor
  Do with Test
 Dispose
Program End

Dispose는 인터페이스인 IDisposable의 멤버이므로 반드시 public으로 정의되어야 합니다.
그리고 using문에서 선언된 객체는 using블럭을 빠져나갈때 그 객체의 Dispose() 메소드를 호출합니다. 그러므로 Dispose()로서 소멸자와 동일한 효과를 낼 수 있습니다.

2017년 1월 19일 목요일

숫자보다 상수를 사용하자

만약 10개의 데이터를 처리하는 함수를 만들었다고 해 봅시다.

void Data::Function()
{
    for(int k = 0; k < 10; ++k)
        Process(data[k]);

    for(int k = 0; k < 10; ++k)
        SendTo(socket, data[k]);
}

Datum Data::GetData(int slot)
{
    if(slot >= 0 && slot < 10)
        return Data[slot];
    return null;
}

그런데 데이터가 15개로 늘었다면 어떻게 해야 할까요?

void Data::Function()
{
    for(int k = 0; k < 15; ++k)
        Process(data[k]);

    for(int k = 0; k < 15; ++k)
        SendTo(socket, data[k]);
}

Datum Data::GetData(int slot)
{
    if(slot >= 0 && slot < 15)
        return Data[slot];
    return null;
}

와 같이 프로그램 전체를 뒤져 위와 같이 Data[]를 참조하는 부분을 다 고쳐야 합니다. 한군데라도 빠뜨리면 그것이 바로 버그로 나타나게 됩니다.

이럴 경우에 C/C++에서는 #define을 사용할 수 있습니다.

#define DATACOUNT    15
void Data::Function()
{
    for(int k = 0; k < DATACOUNT; ++k)
        Process(data[k]);

    for(int k = 0; k < DATACOUNT; ++k)
        SendTo(socket, data[k]);
}

Datum Data::GetData(int slot)
{
    if(slot >= 0 && slot < DATACOUNT)
        return Data[slot];
    return null;
}


와 같이 한다면 이후에 #define문만 수정하면 됩니다.

#define 기능이 부족한 C#이나 아예 다른 언어일 경우에는 어떻게 할까요? 제 경우에는 저런 상수들만 모아놓는 다른 클래스를 선언합니다.*

public class Constant
{
    public const int DataCount = 10;
}

public class Data
{
    public void Function()
    {
        for(int k = 0; k < Constant.DataCount; ++k)
            Process(data[k]);

        for(int k = 0; k < Constant.DataCount; ++k)
            SendTo(socket, data[k]);
    }

    Datum Data::GetData(int slot)
    {
        if(slot >= 0 && slot < Constant.DataCount)
            return Data[slot];
        return null;
    }
}



Constant = {
    DataCount = 10
    }


function Function()
    for k = 1, Constant.DataCount do
        Process(data[k]);
    end

    for k = 1, Constant.DataCount do
        SendTo(socket, data[k]);
    end
end

function GetData(int slot)
    if(slot >= 0 && slot < Constant.DataCount)
        return Data[slot];
    return null;
end

단, Lua처럼 constant기능이 없는 프로그램언어일 경우, 프로그램 실행중 변수값이 바뀌지 않도록 조심해야 합니다.


물론 이렇게 숫자를 그냥 입력하는 것에 비해 상수로 처리하는 것이 더 번거로운 것은 사실입니다. 그냥 간단하게 숫자 두세개 입력하는 것에 비해 #define문을 삽입하고(또는 constant변수를 만들고) 그 변수이름을 입력하는 것이 귀찮을 수도 있습니다.
그 때문에 '여기서만 사용할 것이다', '어차피 바뀌지 않을 값이다' 등 여러 핑계로 숫자를 그대로 입력하고 싶어지는 경우가 많죠(저역시 그런 유혹을 가끔 느끼곤 합니다)

하지만 그런 상황에서도 숫자를 직접 입력하는 것보다 #define/const를 사용하는 것이 좋습니다. 만약

internal class DataClass
{
    private Datum[] = new Datum[100];

    internal void Function()
    {
        for(int k = 0; k < 10; ++k)
            Send(socket, Datum[k]);
    }
}

이런 코드가 있다면 어떨까요? Function()의 10이란 숫자가 원래 크기 100을 잘못 쓴 버그라고 생각할 가능성이 큽니다. 하지만

internal class Constant
{
    internal const int DataSize = 100;
    internal const int FirstElementSend = 10;
}

internal class DataClass
{
    private Datum[] = new Datum[Constant.DataSize];

    internal void Function()
    {
        for(int k = 0; k < Constant.FirstElementSend; ++k)
            Send(socket, Datum[k]);
    }
}

라고 하면 원래부터 앞쪽 일부 데이터만 보내는 것임을 확실히 알 수 있을 것입니다.



* 사실 C/C++에서도 #define을 쓰는 것보다 const 변수를 사용하는 것이 좋습니다. 왜냐하면 #define과 달리 const변수는 디버깅정보로 들어가기 때문입니다.
그때문에 const변수를 사용한다면 디버깅중에 상수값을 확인할 수도, 중간계산값을 확인할 수도 있습니다. #define을 사용하면 디버깅시 최종값만을 확인할 수 있습니다.

2017년 1월 7일 토요일

bool보다 enum이 좋다

프로그래밍을 하다보면 여러 조건들을 합쳐 하나의 조건으로 만들어야 하는 경우가 있습니다.
이를테면

    bool CanMoveTo(int x, int y)  // x, y위치로 갈 수 있는가
    {
        if(IsStun())              // 스턴걸린 상태라면
           return false;
        if(IsObstacle(x, y)       // x, y위치에 장애물이 있다면
           return false;
        if(IsThereEnemy(x, y)     // x, y위치에 적이 있다면
           return false;
        return true;
    }

    void TryMoveTo(int x, y)
    {
        if(CanMoveTo(x, y))
            Enter(x, y);
        else
            EnterFail();    // 로그
    }

와 같이 말입니다. x, y 위치로 갈 수 있는지 없는지 파악해서 true/false를 리턴하는 함수입니다.
그런데 테스트해보니, 분명히 갈 수 있는 위치이며 갈 수 있는 상태인데 false를 리턴한다면 어떨까요?
만약 다른 테스터의 테스트중이나 외부에서 디버그로그를 받아야 하는 상황이라서 디버거를 사용 못한다면 저 세 함수를 다 분석해서 버그가 어디 있는지 찾아야 합니다.

이럴 때는 bool보다 enum을 사용하는 것이 낫습니다.

    enum WhyCannotMoveTo
    {
        NoReason,           // 이유없음 - 들어갈 수 있음
        Stun,
        Obstacle,
        Enemy,
    }

    WhyCannotMoveTo CanMoveTo(int x, int y)  // x, y위치로 갈 수 있는가
    {
        if(IsStun())              // 스턴걸린 상태라면
           return Stun;
        if(IsObstacle(x, y)       // x, y위치에 장애물이 있다면
           return Obstacle;
        if(IsThereEnemy(x, y)     // x, y위치에 적이 있다면
           return Enemy;
        return NoReason;
    }

    void TryMoveTo(int x, y)
    {
        WhyCannotMoveTo reason = CanMoveTo(x, y);
        if(reason == NoReason)
            Enter(x, y);
        else
            EnterFail(reason);    // 로그
    }

즉 reason값을 확인하면 저 세 함수들 중 어느 부분에 버그가 있는지 확인할 수 있으므로 좀더 쉬운 디버깅이 가능합니다.

2016년 12월 6일 화요일

Action과 Func

C#에서는 delegate를 사용해서 여러가지 작업을 할 수 있습니다.


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

namespace Delegate
{
    class Program
    {
        private static void Plus(int a, int b)
        {
            Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
        }
        private static void Minus(int a, int b)
        {
            Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
        }
        private static void Multiply(int a, int b)
        {
            Console.WriteLine("{0} * {1} = {2}", a, b, a * b);
        }
        private static void Divide(int a, int b)
        {
            if(b != 0)
                Console.WriteLine("{0} / {1} = {2}", a, b, a / b);
        }
        private delegate void Function(int a, int b);
        static void Main(string[] args)
        {
            Function f = null;
            f += Plus;
            f += Minus;
            f += Multiply;
            f += Divide;
            f(1, 2);
            Console.WriteLine("-----------");
            f(2, 1);
            Console.WriteLine("-----------");
            f(3, 0);
        }
    }
}

인수 두개를 요구하는 함수를 Function으로 정의해 놓고, Function인 f를 만든 후, f에 Plus, Minus 등의 함수를 등록시키면, f를 한번 호출하는 것으로 등록된 함수들을 모두 호출할 수 있습니다.

그런데 사실 이런 일을 하기 위해서는 delegate를 사용할 필요도 없습니다. 이미 필요한 함수들이 정의되어 있기 때문입니다. Action<>이 바로 이미 정의된 delegate들입니다.,
바로 System에 다음과 같이 정의되어 있습니다.

namespace System
{
    public delegate void Action();
    public delegate void Action<in T1>(T1 arg1);
    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
    public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
    public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12arg12, T13 arg13);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
    public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
}

위와같이 16개의 인수를 가질 수 있는 함수까지 만들어져 있습니다(사실 이렇게까지 많이 만들 필요는 없을것 같습니다만....)

그러므로 만약 위 프로그램을 Action으로 만든다면 다음과 같습니다.

        // private delegate void Function(int a, int b); 딜리게이트 제거
        static void Main(string[] args)
        {
            Action<int, int> f = null;  // delegate 대신 인수가 int 2개인 Action 선언
            f += Plus;
            f += Minus;
            f += Multiply;
            f += Divide;
            f(1, 2);
            Console.WriteLine("-----------");
            f(2, 1);
            Console.WriteLine("-----------");
            f(3, 0);
        }

최초의 프로그램과 완전히 똑같은 결과가 나오죠.


이와 거의 동일하게 Func<> 역시 존재합니다. Func는 리턴값까지 있는 delegate를 대신합니다.

        private static string Plus(int a, int b)
        {
            return string.Format("{0} + {1} = {2}", a, b, a + b);
        }
        private static string Minus(int a, int b)
        {
            return string.Format("{0} - {1} = {2}", a, b, a - b);
        }

        static void Main(string[] args)
        {
            Func<int, int, string> f = null; // int 2개를 인수로 하고 string을 리턴하는 함수 선언
            f += Plus;
            f += Minus;
            Console.WriteLine(f(1, 2));
            Console.WriteLine(f(2, 1));
        }

단, 이 경우에는 f에 등록된 함수들 중 가장 마지막으로 실행된 함수의 리턴값만이 남게 됩니다. 이점을 주의해야 합니다.

2016년 12월 2일 금요일

Flag Clear

길찾기 알고리즘에 있어서 이런 일이 흔히 생길 수 있습니다.

class Cell
{
private :
    bool isChecked = 0;
    bool canEnter;
    int coordX, coordY;
public :
    void Reset()
    {
        isChecked = false;
    }

    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(!isChecked)
        {
            isChecked = true;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        for(int x = 0; x < 2048; ++x)
            for(int y = 0; y < 2048; ++y)
                field[x][y].Reset();   // 이전 탐색결과 지우기
        return field[fromX][fromY].Search(field, toX, toY);
};

매번 길을 찾기 전에 필드의 모든 셀에 대해 이전에 찾았던 길의 정보를 지워야 하죠.

그런데 위와 같이 모든 셀에 대해 플래그를 지우는데 약간의 시간이 지체될 수 있습니다. 2048 * 2048 = 4194304개의 셀에 대해 작업을 해야 하거든요.

이것을 이렇게 하면 어떨까요?


class Cell
{
public :
    static unsigned long currentChecked = 0;
private :
    unsigned long isChecked;
    bool canEnter;
    int coordX, coordY;
public :
    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(isChecked != currentChecked)
        {
            isChecked = currentChecked;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        ++Cell::currentChecked;   // 이전 탐색결과 지우기
        return field[fromX][fromY].Search(field, toX, toY);
};


currentChecked를 바꾸는 것만으로 2048*2048개 모든 Cell의 isChecked가 갱신됩니다. currentChecked와 isChecked의 값이 같은지 다른지가 중요하므로 currentChecked에 오버플로우가 일어나도 상관이 없죠.

다만 한가지 문제는, 어느 한 셀이 0xFFFFFFFF회 동안 한번도 참조되지 않는다면 그 셀은 isChecked가 true인 상태가 된다는 것입니다.

이럴 가능성이 없거나, 무시해도 된다면 상관없지만, 이럴 가능성을 무시할 수 없다면 다음과 같은 추가코드가 필요해집니다.


class Cell
{
public :
    static unsigned long currentChecked = 0;
private :
    unsigned long isChecked = 0;
    bool canEnter;
    int coordX, coordY;
public :
    void Reset()
    {
        isChecked = 0;
    }

    bool Search(Cell field[2048][2048], int goalX, goalY)
    {
        if(!canEnter)
            return false;
        if(goalX == coordX && goalY == coordY)
            return true;
        if(isChecked != currentChecked)
        {
            isChecked = currentChecked;
            return field[CoordX + 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX - 1][CoordY].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY + 1].Search(field, goalX, goalY) ||
                   field[CoordX][CoordY - 1].Search(field, goalX, goalY);
        }
    }
};

class Building
{
private :
    Cell field[2048][2048];
public :
    bool CanGoTo(int fromX, int fromY, int toX, int toY)
    {
        // 이전 탐색결과 지우기
        if(Cell::currentChecked == 0xFFFFFFFF)
        {
            // 오버플로우 나기 전에 초기화
            ++Cell::currentChecked
            for(int x = 0; x < 2048; ++x)
                for(int y = 0; y < 2048; ++y)
                    field[x][y].Reset();
            
        }
        else
            ++Cell::currentChecked;
        return field[fromX][fromY].Search(field, toX, toY);
};

2016년 11월 30일 수요일

임시변수의 범위를 명확하게

가끔 비슷한 부분을 복사해서 사용하는 경우가 있습니다. 몇줄 안되는 데다가 몇군데만 달라서 함수화하기도 애매한 경우에 말입니다.

    Player first = GetFirst();
    first.Prize(Prize.GoldMedal);
    newspaper.OlimpicArticle(first, Prize.GoldMedal);

    Player second = GetSecond();
    second.Prize(Prize.SilverMedal);
    newspaper.OlimpicArticle(second, Prize.SilverMedal);

    Player third = GetThird();
    first.Prize(Prize.BronzeMedal);
    newspaper.OlimpicArticle(third, Prize.BronzeMedal);

즉, 선수들에게 등수에 따라 각각 금메달, 은메달, 동메달을 주고, 신문에 기사를 올리는 코드입니다. 비슷비슷하기에 첫 세줄을 복사해서 일부만 수정한 코드입니다.

이런 식으로 코딩을 할 때 가장 문제가 되는 것이 수정을 잘못하는 것이죠. 위 코드에도 이미 버그가 있습니다. third에게는 아무것도 주지 않고 first에게 금메달과 동메달을 같이 주고 있죠.

이런 실수를 막기 위해, 저는 저런 경우에는 변수의 범위를 명확하게 설정하곤 합니다.
즉,

    {
        Player first = GetFirst();
        first.Prize(Prize.GoldMedal);
        newspaper.OlimpicArticle(first, Prize.GoldMedal);
    }
    {
        Player second = GetSecond();
        second.Prize(Prize.SilverMedal);
        newspaper.OlimpicArticle(second, Prize.SilverMedal);
    }
    {
        Player third = GetThird();
        first.Prize(Prize.BronzeMedal);   // first : 선언되지 않은 변수
        newspaper.OlimpicArticle(third, Prize.BronzeMedal);
    }

변수 first의 영역은 첫째 단락에서 끝나버렸으므로 세번째 나온 first에서 컴파일 에러를 볼 수 있습니다.

물론 변수를 선언할 필요가 없는 루아 같은 경우에는 쓸 수 없는 꼼수이긴 합니다만.

2016년 11월 23일 수요일

정확한 확률 조정 방법

흔히들 확률에 대해 불만을 가지는 사람들이 많습니다. 0.1% 확률이 정확하게 0.1%로 일어나느냐는 것이죠.
물론 내부에서 random()같은 확률함수를 사용하지만, 이 확률함수를 믿을수 있느냐는 문제도 있습니다.
만약 그렇다면 다음과 같은 방식으로 0.1%를 정확하게 만들 수 있습니다.


public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0] = true;
    }
}

여기까지 하면 1000개의 배열에 하나의 true가 들어갑니다. 0.1% 확률이죠.
물론 2000개 배열에 true를 2개 넣든가 5000개 배열에 5개를 넣던가 해도 상관 없습니다.
그 다음 이 배열을 잘 섞어줍니다. 이것은 시스템의 random함수를 사용해도 됩니다.
컴파일러마다 조금씩 다르지만, 보통 정수난수를 발생하는 함수라면,

public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = random() % 1000;    // 1)

            // from과 to를 맞바꿈
            bool tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }
}

다음에 이 array에서 하나씩 꺼내옵니다.

public class OneByThousent
{
    private bool[] array = new bool[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = false;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = (int)(random() * 1000);

            // from과 to를 맞바꿈
            bool tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }

    public bool Get()
    {
        bool rtn = array[point];
        ++point;
        if(point > 1000)
            Shuffle();
        return rtn;
    }
}

이렇게 하면 Get()함수는 정확하게 0.1% 확률로 true를 리턴하게 될 것입니다. 비록 random()함수가 정확한 랜덤이 아니라고 해도 말입니다.




마찬가지로, 만약

public class OneByThousent
{
    private float[] array = new float[1000];
    private int point = 0;

    public OneByThousent()
    {
        for(int k = 0; k < 1000; ++k)
            array[k] = k / 1000F;
        array[0];

        Shuffle();
    }

    private void Shuffle()
    {
        for(int from = 0; from < 1000; ++from)
        {
            int to = (int)(random() * 1000);

            // from과 to를 맞바꿈
            float tmp = array[from];
            array[from] = array[to];
            array[to] = tmp;
        }

        point = 0;
    }

    public float Get()
    {
        float rtn = array[point];
        ++point;
        if(point > 1000)
            Shuffle();
        return rtn;
    }
}

이렇게 하면 0~1 사이에서 1/1000 단위로 균일하게 분포된 난수가 만들어집니다.


다만, 이런 정확한 확률은 shuffle이 여러번 일어날 때 - 컴퓨터가 오랜 시간 돌아갈 때 정확하게 나타납니다.

만약 1/(1백만)의 확률을 만들기 위해 저런 식으로 1백만개의 어레이를 만들었다고 합시다.
그런데 저 함수로 10만개의 확률을 계산한 이후 (점검 등으로) 컴퓨터가 내려가는 일이 반복된다면, 그것은 그냥 random()함수를 사용하는 것이나 다름이 없습니다. 만약 이런 정확한 확률이 필요하다면 이 어레이를 파일이나 DB에라도 저장해 놓고 컴퓨터를 재시작한 이후에도 다시 연결될 수 있도록 해야겠죠.

1) 이런 식으로 나머지연산을 사용하면 일정 범위의 난수를 쉽게 얻을 수 있습니다.
하지만 이럴 경우 주의를 해야 합니다.
언젠가 난수를 출력해 봤는데, 난수가 다음과 같이 나오더군요(물론 이것도 컴파일러에 따라 다릅니다).

random() % 2 : 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,.......
random() % 4 : 1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,1,3,2,0,........
random() % 8 : 6,1,7,4,3,0,5,2,6,1,7,4,3,0,5,2,6,1,7,4,3,0,5,2,..........

다른 수일 경우는 괜찮았는데, 저렇게 2의 제곱수의 나머지를 구하면 순서대로 나옵니다.

그러므로 가장 좋은 방법은 다음과 같습니다.

    int rnd = (int)((random() * 1000.0) / MAXRANDOM);

2016년 11월 15일 화요일

C++ : Class Capsulation

가끔씩 따로 관리하던 두 가지를 한꺼번에 관리해야 할 경우가 생길 수 있습니다.

이를테면 다음과 같은 애완동물호텔을 만들었는데...

class Pet : public Animal
{
public :
    virtual void Feed() = 0;
    virtual void Bath();                  // Pet bathing
};

class Dog : public Pet
{
public :
    bool Feed() override;        // Dog feed
};

class Cat : public Pet
{
public :
    bool Feed() override;        // Cat feed
}


class Hotel
{
private :
    std::list<Pet *> m_List;
public :
    ....
    void Care()
    {
        for each(Pet *pet in m_List)
        {
            pet->Feed();
            pet->Bath();
        }
    }
};

그런데 갑자기 주인이 자동차나 오토바이 보관까지 하겠다고 나선다면 어떨까요? 더구나 자동차나 오토바이는

class Car : public Machine
{
   virtual void Fual() = 0;
   void Wash();            // car washing
};

class MotorCycle : public Car
{
   void Fual() override;   // fual Gasolin
};

class Sedan : public Car
{
   void Fual() override;   // fual Gasolin
};

class Jeep : public Car
{
   void Fual() override;   // fual Desel
};

class F1Formular : public Car
{
   void Fual() override;   // fual High grade Gasolin
};

와 같이 전혀 관계없는 클래스라면 더욱 난감해집니다.

이 경우 이런 클래스들을 감싸는 클래스를 만들면 비교적 손쉽게 됩니다.

class GuestBase
{
public :
    virtual void Feed() = 0;
    virtual void Bath() = 0;
    virtual void Fuel() = 0;
};

class GuestPet : GuestBase
{
private :
    Pet *m_Pet;
public :
    virtual void Feed()
    {
        m_Pet->Feed();
    }
    virtual void Bath()
    {
        m_Pet->Bath();
    }
    virtual void Fuel()
    {
        // Do nothing
    }
};

class GuestMachine : GuestBase
{
private :
    Machine *m_Machine;
public :
    virtual void Feed()
    {
        // Do nothing
    }
    virtual void Bath()
    {
        m_Machine->Wash();
    }
    virtual void Fuel()
    {
        m_Machine->Fuel();
    }
};



class Hotel
{
private :
    std::list<GuestBase *> m_List;
public :
    ....
    void Care()
    {
        for each(Guest *guest in m_List)
        {
            guest->Feed();
            guest->Bath();
            guest->Fuel();
        }
    }
};

Java '...' is not an enclosing class

자바에서

public class ViewPort
{
    public class BaseView
    {
    }

    public class MainView extends BaseView
    {
        ....
    }

    public class SideView extends BaseView
    {
        ....
    }
}

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        currentView = new ViewPort.MainView();
    }
}

이럴경우

'com.program.ViewPort' is not an enclosing class

가 나타날 때가 있습니다. 자바는 처음 시작한지라 왜 이런 에러가 나타나는지는 영...
하지만 이렇게 하니까 해결은 되는군요.

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        currentView = new ViewPort().new MainView();
    }
}

또는

public class MainClass
{
    private ViewPort currentView;

    public MainClass()
    {
        ViewPort vp = new ViewPort();
        currentView = vp.new MainView();
    }
}

로 해야 합니다.

C# .. is

다음 프로그램을 봅시다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class BaseClass
    {
    }

    public class DerivedClass : BaseClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            BaseClass b = new BaseClass();
            BaseClass d = new DerivedClass();
            Console.WriteLine("b is BaseClass : {0}", b is BaseClass);
            Console.WriteLine("b is DerivedClass : {0}", b is DerivedClass);
            Console.WriteLine("d is BaseClass : {0}", d is BaseClass);
            Console.WriteLine("d is DerivedClass : {0}", d is DerivedClass);
        }
    }
}

실행하면 결과는 다음과 같습니다.

b is BaseClass : True
b is DerivedClass : False
d is BaseClass : True
d is DerivedClass : True

즉,

if(b is DerivedClass)
   .....



if(b as DerivedClass != null)
   .....

과 동일한 식이죠.


그러므로 obj라는 객체가 BaseClass인지 DerivedClass인지 알기 위해서는

if(obj is BaseClass)
    Console.WriteLine("obj is BaseClass");
else
    Console.WriteLine("obj is DerivedClass");

이것은 DerivedClass도 BaseClass이므로 항상 true가 됩니다.

그러므로

if(obj is DerivedClass)
    Console.WriteLine("obj is DerivedClass");
else
    Console.WriteLine("obj is BaseClass");

이렇게 해야 제대로 동작하게 되죠.

2016년 11월 8일 화요일

OOP 추상화(Abstraciton)

OOP의 특징 중 하나가 추상화(Abstraciton)입니다. 그런데 이 추상화가 뭘까요? 피카소의 그림일까요?

'추상화'란 것은, 어떤 기능을 가진 객체를 구현할때, 객체 외부에서는 그 기능의 정확한 묘사를 하지 않는 것입니다. 그 대신 그 기능의 '추상적 행위'를 묘사해야 합니다.

이를테면 다음과 같은 잠수함을 구현한다고 해 봅시다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);


    // 수면으로 이동
    if(ship.GetDepth() > 0)
        ship.SetDepth(ship.GetDepth() - 1);
}

이런 식으로 해도 동작은 하겠죠. 하지만 이런 구현을 한다면 구태여 OOP 프로그램을 사용할 필요가 없습니다. 그냥 C로도 구현되는 수준이거든요. 오히려 C로 하면 저런 잡다한 멤버함수를 만들 필요도 없이 쉽게 끝납니다(하지만 그냥 C 프로그램에서도 이런 식의 프로그램은 좋지 않습니다*).

이것을 제대로 추상화를 구현한다면 이런 식이 되어야 합니다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public void Float()
    {
        if(depth > 0)
           --depth;
    }

    public void Sink() { ++depth; }
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 수면으로 이동
    ship.Float();
}


이런 식으로 Submarine 밖에서는 Submarine의 멤버변수 자체를 생각하지 않고 코딩하는 것이 '추상화'입니다. x니 y니 따위를 생각하지 않고 오로지 '북쪽으로 이동'만 코딩하는 것이죠. '구체화'는 Submarine.ToNorth() 안에서 하고 말입니다.
캡슐화(Capsulation)는 멤버변수에 직접 접근이 힘들 뿐, 실제 멤버변수가 뭐가 있는지 알 수는 있다는 점에서, 추상화는 캡슐화보다 한단계 더 진행한 개념이라고 볼 수 있죠.

만약

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);

    // 암초에 걸렸는지 체크
    if(ocean.IsReef(ship.GetX(), ship.GetY())
        ship.Destroyed();
}

이런 것은 '캡슐화'는 되었지만(x, y변수가 '캡슐화되었죠) '추상화'가 된 코드는 아닙니다. '추상화'를 시킨다면

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public bool IsSafe(Ocean ocean)
    {
        if(ocean.IsReef(x, y))  // 암초에 걸렸으면
            return false;
        // 다른 상황 체크

        // 모두 통과했으면 안전
        return true;
    }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 배가 안전한지 체크
    if(ship.isSafe(ocean))
        ship.Destroyed();
}


가 되어야 합니다.


* 위에서 저런 식의 코딩은 C에서도 좋지 않다고 했는데, C에서의 코딩은 다음과 같이 하는 것이 좋습니다.

struct Ocean
{
    ......
}

struct Submarine
{
    int x;    // x좌표
    int y;    // y좌표
    int depth;// 깊이
}

void ToEast(Submarine ship) { ++ship.x; }
void ToWest(Submarine ship) { --ship.x; }
void ToSouth(Submarine ship) { ++ship.y; }
void ToNorth(Submarine ship) { --ship.y; }

bool IsSafe(Ocean ocean, Submarine ship)
{
    if(ocean.IsReef(ship.x, ship.y))  // 암초에 걸렸으면
        return false;
    // 다른 상황 체크

    // 모두 통과했으면 안전
    return true;
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ToNorth(ship);

    // 배가 안전한지 체크
    if(isSafe(ocean, ship))
        Destroyed(ship);
}