코드를 자연어 알고리즘으로

들어가기

그냥 코딩하면 되잖아?

코드 vs 자연어 알고리즘 예시

상황

코드

public static string ChangeExtension(string originalPath, string? extension)
{
    int lastDotIndex = originalPath.LastIndexOf('.');
    if (lastDotIndex == -1)
    {
        return string.Concat(originalPath, extension);
    }
    else
    {
        return string.Concat(originalPath.AsSpan(0, lastDotIndex), extension);
    }
}

자연어 알고리즘

주어진 정보:

  1. 원본 파일 경로
  2. 새 확장자

실행 과정:

  1. '원본 파일 경로'에서 '.'의 마지막 위치를 얻는다.
  2. 1.의 결과가 -1 과 같은지 확인한다.
    1. 맞다면, '원본 파일 경로'와 '새 확장자'를 연결하고, 그 결과를 반환한다.
    2. 아니라면,
      1. '원본 파일 경로'를 0번째부터 (1.의 결과 - 1)번째까지 잘라낸다.
      2. 2.ii.a.의 결과와 '새 확장자'를 연결하고, 그 결과를 반환한다.

사담

사실 저 자연어 알고리즘도 썩 마음에 드는 건 아닌데...일단 저 정도로 할 수 있는 걸 목표로 하자.

코드-자연어 알고리즘 대응시키기

/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
public static string ChangeExtension(string originalPath, string? extension)
{
    // 1. '원본 파일 경로'에서 '.'의 마지막 위치를 얻는다.
    int lastDotIndex = originalPath.LastIndexOf('.');

    // 2. `1.의 결과`가 -1 과 같은지 확인한다.
    if (lastDotIndex == -1)
    {
        // 1. 맞다면, '원본 파일 경로'와 '새 확장자'를 연결하고, 그 결과를 반환한다.
        return string.Concat(originalPath, extension);
    }
    else
    {
        // 2. 아니라면,
        // 1. '원본 파일 경로'를 0번째부터 (`1.의 결과` - 1)번째까지 잘라낸다.
        var v1 = originalPath.AsSpan(0, lastDotIndex);
        // 2. `2.ii.a.의 결과`와 '새 확장자'를 연결하고, 그 결과를 반환한다.
        return string.Concat(v1, extension);
    }
}

일단은...

저 '분리' 부터 구현해야 하려나.

        return string.Concat(originalPath.AsSpan(0, lastDotIndex), extension);

        var v1 = originalPath.AsSpan(0, lastDotIndex);
        return string.Concat(v1, extension);

이것 말고도, 분리해야 할 게 많겠지..

코드 분리 기능 구현

예시 1

    public static string ChangeExtension(string originalPath, string? extension)
    {
        int lastDotIndex = originalPath.LastIndexOf('.');
        if (lastDotIndex == -1)
        {
            return string.Concat(originalPath, extension);
        }
        else
        {
            return string.Concat(originalPath.AsSpan(0, lastDotIndex), extension);
        }
    }
public static string ChangeExtension(string originalPath, string? extension)
{
    //
    int lastDotIndex = originalPath.LastIndexOf('.');
    //
    bool b1_1 = lastDotIndex == -1;
    if (b1_1)
    {
        //
        return string.Concat(originalPath, extension);
    }
    else
    {
        //
        var v2_1 = originalPath.AsSpan(0, lastDotIndex);
        //
        return string.Concat(v2_1, extension);
    }
}

예시 2

using RoslynQuoter;

namespace Nemonuri.RoslynQuoters;

/// <summary>
/// 프로그램
/// </summary>
public static class Program
{
    /// <summary>
    /// 이 프로그램의 진입점입니다.
    /// </summary>
    /// <param name="args">명령어</param>
    public static void Main(string[] args)
    {
        if (CommandParsingTheory.Parse(args) is not { } parseResult) { return; }

        string csharpText = File.ReadAllText(parseResult.TargetFile.FullName);

        Quoter quoter = new Quoter();
        Quoter.ApiCall apiCall = quoter.Quote(csharpText);

        Console.WriteLine(apiCall.ToString());
    }
}
using RoslynQuoter;

namespace Nemonuri.RoslynQuoters;
/// <summary>
/// 프로그램
/// </summary>
public static class Program
{
    /// <summary>
    /// 이 프로그램의 진입점입니다.
    /// </summary>
    /// <param name = "args">명령어</param>
    public static void Main(string[] args)
    {
        //
        bool b1_1 = CommandParsingTheory.Parse(args)is not { } parseResult;
        if (b1_1)
        {
            //
            return;
        }

        //
        var v2_1 = parseResult.TargetFile.FullName;
        //
        string csharpText = File.ReadAllText(v2_1);
        //
        Quoter quoter = new Quoter();
        //
        Quoter.ApiCall apiCall = quoter.Quote(csharpText);
        //
        var v2_2 = apiCall.ToString();
        //
        Console.WriteLine(v2_2);
    }
}

분리된 코드에 주석 달기

예시1

<|im_start|>system
You are a professional senior C# programmer.

<RULE>
1. Add a comment as sentence in Korean for each code line of the given method name.
2. User will input code in <CODE/> block, and method name in <METHOD_NAME/> block.
</RULE>
<|im_end|>
<|im_start|>user
<CODE>
/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name = "originalPath">원본 파일 경로</param>
/// <param name = "extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    //
    int lastDotIndex = originalPath.LastIndexOf('.');
    //
    bool b1_1 = lastDotIndex == -1;
    if (b1_1)
    {
        //
        return string.Concat(originalPath, extension);
    }
    else
    {
        //
        var v2_1 = originalPath.AsSpan(0, lastDotIndex);
        //
        return string.Concat(v2_1, extension);
    }
}
</CODE>

<METHOD_NAME>
ChangeExtension
</METHOD_NAME>
<|im_end|>
<|im_start|>assistant
/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    // 원본 파일 경로에서 마지막 '.' 인덱스를 찾습니다.
    int lastDotIndex = originalPath.LastIndexOf('.');
    
    // 만약 '.'이 없다면 확장자를 추가합니다.
    bool b1_1 = lastDotIndex == -1;
    if (b1_1)
    {
        // 원본 파일 경로에 새 확장자를 연결하여 반환합니다.
        return string.Concat(originalPath, extension);
    }
    else
    {
        // '.'이 있다면 '.' 전까지의 부분을 가져옵니다.
        var v2_1 = originalPath.AsSpan(0, lastDotIndex);
        
        // 원본 파일 경로에서 확장자를 제거한 후 새 확장자를 추가하여 반환합니다.
        return string.Concat(v2_1, extension);
    }
}

예시2

<|im_start|>system
You are a professional senior C# programmer.

<RULE>
1. Add a comment as sentence in Korean for each code line of the given method name.
2. User will input code in <CODE/> block, and method name in <METHOD_NAME/> block.
</RULE>
<|im_end|>
<|im_start|>user
<CODE>
using RoslynQuoter;


/// <summary>
/// 프로그램
/// </summary>
public static class Program
{
    /// <summary>
    /// 이 프로그램의 진입점입니다.
    /// </summary>
    /// <param name = "args">명령어</param>
    public static void Main(string[] args)
    {
        //
        bool b1_1 = CommandParsingTheory.Parse(args)is not { } parseResult;
        if (b1_1)
        {
            //
            return;
        }

        //
        var v2_1 = parseResult.TargetFile.FullName;
        //
        string csharpText = File.ReadAllText(v2_1);
        //
        Quoter quoter = new Quoter();
        //
        Quoter.ApiCall apiCall = quoter.Quote(csharpText);
        //
        var v2_2 = apiCall.ToString();
        //
        Console.WriteLine(v2_2);
    }
}
</CODE>

<METHOD_NAME>
Main
</METHOD_NAME>
<|im_end|>
<|im_start|>assistant
using RoslynQuoter;

/// <summary>
/// 프로그램
/// </summary>
public static class Program
{
    /// <summary>
    /// 이 프로그램의 진입점입니다.
    /// </summary>
    /// <param name = "args">명령어</param>
    public static void Main(string[] args)
    {
        // 명령어를 파싱하여 결과가 없는지 확인합니다. 결과가 있으면 true, 없으면 false
        bool b1_1 = CommandParsingTheory.Parse(args)is not { } parseResult;
        if (b1_1)
        {
            // 파싱된 결과가 없는 경우 메서드를 종료합니다.
            return;
        }

        // 파싱된 결과에서 대상 파일의 전체 경로를 가져옵니다.
        var v2_1 = parseResult.TargetFile.FullName;
        
        // 해당 경로에 있는 C# 텍스트를 읽어옵니다.
        string csharpText = File.ReadAllText(v2_1);

        // RoslynQuoter를 사용하여 C# 코드를 변환합니다.
        Quoter quoter = new Quoter();
        Quoter.ApiCall apiCall = quoter.Quote(csharpText);

        // 변환된 결과를 문자열로 변환합니다.
        var v2_2 = apiCall.ToString();

        // 변환된 결과를 콘솔에 출력합니다.
        Console.WriteLine(v2_2);
    }
}

주석 달린 코드를 자연어 알고리즘으로

일단, 예시 1만 해보자. 수작업으로.

주석 달린 코드

/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    // 원본 파일 경로에서 마지막 '.' 인덱스를 찾습니다.
    int lastDotIndex = originalPath.LastIndexOf('.');
    
    // 만약 '.'이 없다면 확장자를 추가합니다.
    bool b1_1 = lastDotIndex == -1;
    if (b1_1)
    {
        // 원본 파일 경로에 새 확장자를 연결하여 반환합니다.
        return string.Concat(originalPath, extension);
    }
    else
    {
        // '.'이 있다면 '.' 전까지의 부분을 가져옵니다.
        var v2_1 = originalPath.AsSpan(0, lastDotIndex);
        
        // 원본 파일 경로에서 확장자를 제거한 후 새 확장자를 추가하여 반환합니다.
        return string.Concat(v2_1, extension);
    }
}

자연어 알고리즘

ChangeExtension

자문자답

코드를 메서드로 감싸서 분리하기

수작업으로 진행했다.

    public static string ChangeExtension(string originalPath, string? extension)
    {
        int lastDotIndex = originalPath.LastIndexOf('.');
        if (lastDotIndex == -1)
        {
            return string.Concat(originalPath, extension);
        }
        else
        {
            return string.Concat(originalPath.AsSpan(0, lastDotIndex), extension);
        }
    }
/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name = "originalPath">원본 파일 경로</param>
/// <param name = "extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    int lastDotIndex = M1(originalPath);
    if (M2(lastDotIndex))
    {
        return M3(originalPath, extension);
    }
    else
    {
        Span<char> v2_1 = M4(originalPath, lastDotIndex);
        return M5(v2_1, extension);
    }
}

public static int M1(string originalPath)
{
    return originalPath.LastIndexOf('.');
}

public static bool M2(int lastDotIndex)
{
    return lastDotIndex == -1;
}

public static string M3(string originalPath, string extension)
{
    return string.Concat(originalPath, extension);
}

public static Span<char> M4(string originalPath, int lastDotIndex)
{
    return originalPath.AsSpan(0, lastDotIndex);
}

public static string M5(Span<char> v2_1, string extension)
{
    return string.Concat(v2_1, extension);
}

주석 달기

<|im_start|>system
You are a professional C# programmer.

<RULE>
1. Write `<summary>` style comment to each method, in Korean.
</RULE>
<|im_end|>
<|im_start|>user

```csharp
/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name = "originalPath">원본 파일 경로</param>
/// <param name = "extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    int lastDotIndex = M1(originalPath);
    if (M2(lastDotIndex))
    {
        return M3(originalPath, extension);
    }
    else
    {
        Span<char> v2_1 = M4(originalPath, lastDotIndex);
        return M5(v2_1, extension);
    }
}

public static int M1(string originalPath)
{
    return originalPath.LastIndexOf('.');
}

public static bool M2(int lastDotIndex)
{
    return lastDotIndex == -1;
}

public static string M3(string originalPath, string extension)
{
    return string.Concat(originalPath, extension);
}

public static Span<char> M4(string originalPath, int lastDotIndex)
{
    return originalPath.AsSpan(0, lastDotIndex);
}

public static string M5(Span<char> v2_1, string extension)
{
    return string.Concat(v2_1, extension);
}
```

<|im_end|>
<|im_start|>assistant
/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    int lastDotIndex = M1(originalPath);
    if (M2(lastDotIndex))
    {
        return M3(originalPath, extension);
    }
    else
    {
        Span<char> v2_1 = M4(originalPath, lastDotIndex);
        return M5(v2_1, extension);
    }
}

/// <summary>
/// 원본 파일 경로에서 마지막에 있는 '.'의 인덱스를 반환합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <returns>'.'의 인덱스 또는 -1(확장자가 없는 경우)</returns>
public static int M1(string originalPath)
{
    return originalPath.LastIndexOf('.');
}

/// <summary>
/// '.'이 원본 파일 경로에 있는지 확인합니다.
/// </summary>
/// <param name="lastDotIndex">'.'의 인덱스</param>
/// <returns>확장자가 없는 경우 true, 그렇지 않으면 false</returns>
public static bool M2(int lastDotIndex)
{
    return lastDotIndex == -1;
}

/// <summary>
/// 원본 파일 경로에서 확장자를 제외한 부분과 새 확장자를 결합하여 새로운 파일 경로를 생성합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string M3(string originalPath, string extension)
{
    return string.Concat(originalPath, extension);
}

/// <summary>
/// 원본 파일 경로에서 확장자를 제외한 부분을 가져옵니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="lastDotIndex">'.'의 인덱스</param>
/// <returns>확장자를 제외한 부분의 Span<char></returns>
public static Span<char> M4(string originalPath, int lastDotIndex)
{
    return originalPath.AsSpan(0, lastDotIndex);
}

/// <summary>
/// 확장자를 제외한 부분과 새 확장자를 결합하여 새로운 파일 경로를 생성합니다.
/// </summary>
/// <param name="v2_1">확장자를 제외한 부분의 Span<char></param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string M5(Span<char> v2_1, string extension)
{
    return string.Concat(v2_1, extension);
}

자연어 알고리즘

ChangeExtension

자연어 알고리즘 (수정)

앞의 것도 나쁘진 않은데,
'단계'를 약간 수정하면 좋을 것 같아.

ChangeExtension

그런데, Step2.1.의 설명이 뭔가 이상하다?
Step2. 의 맞다·아니다 도 완전 반대야.

LLM을 한번 더 실행해보자. 동일한 입력 프롬프트를 사용해서.

출력2

/// <summary>
/// 원본 파일 경로의 확장자를 새 것으로 교체합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">교체할 새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string ChangeExtension(string originalPath, string? extension)
{
    int lastDotIndex = M1(originalPath);
    if (M2(lastDotIndex))
    {
        return M3(originalPath, extension);
    }
    else
    {
        Span<char> v2_1 = M4(originalPath, lastDotIndex);
        return M5(v2_1, extension);
    }
}

/// <summary>
/// 원본 파일 경로에서 마지막 점의 인덱스를 반환합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <returns>마지막 점의 인덱스</returns>
public static int M1(string originalPath)
{
    return originalPath.LastIndexOf('.');
}

/// <summary>
/// 주어진 인덱스가 -1인지 확인합니다.
/// </summary>
/// <param name="lastDotIndex">점의 인덱스</param>
/// <returns>점이 없으면 true, 있으면 false</returns>
public static bool M2(int lastDotIndex)
{
    return lastDotIndex == -1;
}

/// <summary>
/// 원본 파일 경로와 새 확장자를 결합하여 새로운 파일 경로를 반환합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="extension">새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string M3(string originalPath, string extension)
{
    return string.Concat(originalPath, extension);
}

/// <summary>
/// 원본 파일 경로에서 마지막 점까지의 부분을 Span<char>로 반환합니다.
/// </summary>
/// <param name="originalPath">원본 파일 경로</param>
/// <param name="lastDotIndex">점의 인덱스</param>
/// <returns>Span<char> 형태의 파일 경로 부분</returns>
public static Span<char> M4(string originalPath, int lastDotIndex)
{
    return originalPath.AsSpan(0, lastDotIndex);
}

/// <summary>
/// Span<char>와 새 확장자를 결합하여 새로운 파일 경로를 반환합니다.
/// </summary>
/// <param name="v2_1">Span<char> 형태의 파일 경로 부분</param>
/// <param name="extension">새 확장자</param>
/// <returns>확장자가 교체된 파일 경로</returns>
public static string M5(Span<char> v2_1, string extension)
{
    return string.Concat(v2_1, extension);
}

자연어 알고리즘 (출력2)

ChangeExtension (단계 수정 전)

ChangeExtension (단계 수정 후)

사담