문자열에서 문자열 리스트에 존재하는 특정문자만 가져오려면 어떻게 해야 할까요?

제목만으로 원하는 기능설명이 어렵네요.
예를 들어보자면 문자열은 “상품명-모델명” 과 같이 작성되어있고
문자열리스트는 상품명으로 되어있습니다.
여기서 문자열에서 상품명만 가져오려고 합니다.
list의 for문으로 contains 를 쓰면 쉽게 될거라 생각했는데
상품명 리스트에 책과 책상이 있는경우 문자열이 “책상-1234556” 일경우에 책과 책상 모두 포함하므로 책과 책상중 하나인데 length가 큰것으로 선택하기에는 뭔가 다른 좋은 방법이 있을것 같아서 질문한번 남겨봐요. 상품명 문자열리스트가 항목이 많아질경우 for문돌리는 속도가 걱정되기도 하구요. 이럴때 사용할만한 라이브러리나 좋은방법 있을까요? 고견을 주시면 감사하겠습니다.

3개의 좋아요

System.Text.RegularExpressions 네임스페이스에서 쓸 수 있는 정규표현식 (Regex)을 활용하는 것이 복잡한 텍스트 매칭에서 가장 권장되는 방법입니다.

정규표현식에 약간의 변형은 있지만 대체로 언어나 플랫폼을 가리지 않고 보편적으로 쓸 수 있어서 따로 배워두시면 매우 유용합니다. 정규표현식은 아주 오래된 기술이지만 유니코드 문자 체계까지 모두 지원하기 때문에 개발자나 엔지니어라면 꼭 알아두어야 할 도구이기도 합니다.

질문해주신 내용으로 보면, 중간의 “-” 구분자를 기준으로 앞/뒤로 문자열을 나누어서 가져오려는 것이 목적으로 보이는데요 (정확한 맥락을 알 수 없어 내용으로 추정한 답변입니다.)

이 때에는 String Tokenization (String.Split 메서드)을 이용해서 나눌 수도 있고, 정규표현식을 이용해서 단순히 나누기만 하는 것이 아니라 특정한 조건에 맞는 검색 결과만 처음부터 골라서 탐색할 수도 있는데, 후자를 이용하면 정규표현식을 잘 설계했을 때 받아보게 되는 결과 집합의 크기를 더 줄일 수 있어서 힙 메모리 사용량을 줄이는데 도움이 될 수 있습니다.

5개의 좋아요

'string.LastIndexOf()'로 '-'의 위치를 얻어서 상품명을 얻고 비교하시면 됩니다.

var items = new List<string>();

// 10만건 + 1
foreach (var _ in Enumerable.Range(1, 100000))
    items.Add("책-1234567");
items.Add("책상-1234556");


// 상품명이 "책상"인 것만 조회
var foundItems = items
    .Where(x => x.LastIndexOf('-') is int index && index is not -1 && x[..index] is "책상");

// 출력
foreach (var foundItem in foundItems)
{
    Console.WriteLine(foundItem);
}
5개의 좋아요

x[..index]x.AsSpan()[..index]로 변경하면 조금 더 성능 개선이 됩니다.

4개의 좋아요

답글 감사합니다. 쉽게 설명드리기위해 중간에 - 와 상품명은 한글 모델명은 숫자로 작성하였으나 실제 해야되는 문자열의 상품명과 모델명은 모두 숫자와 영문 대문자로 이루어져 있습니다.
중간에 - 도 없고 띄어쓰기로 구분되어 있어요. 예를 들어 AB38B TA34987-AB456 이런식으로 말이죠…
처음에 정규식을 생각해보았는데 상품명과 모델명 모두 영문과 숫자로만 이루어져 있어 정규식으로 구분이 어려울것 같다는 생각을 했어요. 실제 사용되는 문자열은 GD22K210 S45C 이런식으로 되어있습니다. 앞모델명 뒤가 제품명입니다. 여기서 s45c 만 가져오면 되는데 방법이 생각나지 않아 미리 문자열리스트(s45c,s20c) 에서 일치하는 문자열을 가져오려고 한거에요. 여기서 문제점이 문자열리스트에 s45c만 있는것이 아닌 s45,s45c, s45cs 등 비슷한 무자열을 갖고 있다보니 이런 부분을 어떻게 해결하면 될지가 고민입니다

3개의 좋아요

답변 감사합니다. 실제 문자열은 GD22K210 S45C 와 같이 작성되어 있어요. “모델명 소재명” 이렇게요. 전부 이런식으면 lastindexof 를 사용하면 되겠지만 “소재명 모델명”, “소재명 규격 모델명” 등과같이 다양한형태입니다. 그래서 위와같은 “GD22K210 S45C” 문자열에서 “S45C” 만 가져오기위해 S45C와 같이 소재명으로된 문자열리스트 [“S45C”, “S20C”,…] 를 만들어 문자열에서 문자열리스트의 항목과 일치하는 텍스트를 뽑으려고 생각했습니다.

3개의 좋아요

파싱을 하려면 규칙이 있어야 합니다. 예를 들어 문자열로 이루어진 항목의 속성 구분자는 공백 ' ' 이다 식입니다.

그리고 항목의 속성이 상황에 따라 다를 경우 항목이 가진 속성의 개수는 다를 수 있다로 정의할 수 있습니다.

만약 속성이 공백(’ ')으로 구분되는 것이 정확하다면 다음의 한계가 생깁니다.

소재명, 규격, 모델명은 공백(' ')이 들어갈 수 없다

보통 이름에 공백이 들어가기도 해서 문자열로 다양한 속성을 표현할 때는 잘 사용하지 않는 파이프라인(|)을 사용합니다.

어쨌든 구분자로 각 속성을 추출하고 (string.Split()) 얻어진 속성을 적절한 구조체에 담아 사용하거나 배열 인덱스로 접근할 수 있습니다.

저 같은 경우 속성의 개수가 다르더라도 동일한 접근을 선호하기 때문에 다음의 구조로 만듭니다.

using System.Text.Json;

// 문자열로 된 목록
var itemStrings = new List<string>
{
    "GD22K210 S45C",
    "GD22K220 cm S45D"
};


// 문자열 목록 -> 정보 인스턴스 목록
var items = itemStrings
    .Select(x => x.Split(' ', StringSplitOptions.TrimEntries))
    .Select(x => x.Length switch
    {
        2 => new ItemInfo(MaterialName: x[0], ModelName: x[1]),
        3 => new ItemInfo(MaterialName: x[0], Spec: x[1], ModelName: x[2]),
        _ => throw new InvalidOperationException()
    });

// 모델명으로 조회
var result = items.First(x => x.ModelName is "S45C");
Console.WriteLine(result);


// record로 했으나 class로 만들어도 됨
record ItemInfo(
    string MaterialName,
    string ModelName,
    string? Spec = default
)
{
    // 보기좋게 출력하기 위함
    public override string ToString()
    {
        var options = new JsonSerializerOptions()
        {
            WriteIndented = true
        };
        return JsonSerializer.Serialize(this, options);
    }
}

| 출력

{
  "MaterialName": "GD22K210",
  "ModelName": "S45C",
  "Spec": null
}
4개의 좋아요

문자열이 어떤 패턴을 가지고 있든 와일드카드 매칭을 정규표현식에서 쓸 수 있는 점을 활용하시고, 정규표현식 외에 몇 가지 유틸리티 메서드를 이용하시면 원하는 목표에 근접한 코드를 작성하실 수 있지 않을까 생각합니다. 정확한 구현 요구 사항을 알 수 없기 때문에, 추정을 근거로 샘플 코드를 만들어본다면 다음과 같은 형태가 될 것 같습니다.

using System.Text.RegularExpressions;

var data = @"\GD22K210 S45C
AB38B TA34987-AB456
GD22K220 cm S45D
3DDBD9DB-76C4 4C54-ABAB-E39959A973F0
";

var regex = new Regex(@"(?<Material>[^\s]+)[\s]+(?<StdModel>.+)",
    RegexOptions.Compiled | RegexOptions.Multiline);
var subRegex = new Regex(@"(?<Std>[^\s]+)[\s]+(?<Model>.+)",
    RegexOptions.Compiled);
foreach (var eachLine in data.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
{
    var result = regex.Match(eachLine);
    var material = result.Groups["Material"].Value;
    var stdModel = result.Groups["StdModel"].Value;

    if (stdModel.Any(x => Char.IsWhiteSpace(x)))
    {
        var subResult = subRegex.Match(stdModel);
        var std = subResult.Groups["Std"].Value;
        var model = subResult.Groups["Model"].Value;
        Console.WriteLine($"Material: {material} / Standard: {std} / Model: {model}");
    }
    else
    {
        Console.WriteLine($"Material: {material} / Model: {stdModel}");
    }
}

위 코드의 실행 결과는 다음과 같습니다.

Material: \GD22K210 / Model: S45C
Material: AB38B / Model: TA34987-AB456
Material: GD22K220 / Standard: cm / Model: S45D
Material: 3DDBD9DB-76C4 / Model: 4C54-ABAB-E39959A973F0
3개의 좋아요

음… 원본 자체(상품명, 모델명)의 규칙성이 확보된 상태여야 앞으로도 새로 등록될 명칭에 대해서도 문제가 없을 것 같습니다…
원본을 수정하기 힘드시다면 어쩔 수 없겠지만…

2개의 좋아요