programing

용량이 아닌 특정 크기로 목록을 초기화하는 방법

topblog 2023. 6. 2. 20:05
반응형

용량이 아닌 특정 크기로 목록을 초기화하는 방법

.NET은 성능이 거의 동일한 일반 목록 컨테이너를 제공합니다( 어레이 성능 대질문 나열).그러나 초기화는 상당히 다릅니다.

어레이는 기본값으로 매우 쉽게 초기화할 수 있으며, 정의에 따라 이미 특정 크기가 있습니다.

string[] Ar = new string[10];

이렇게 하면 안전하게 임의 항목을 할당할 수 있습니다.

Ar[5]="hello";

목록과 함께 사물들은 더 까다롭습니다.동일한 초기화를 수행하는 두 가지 방법을 알 수 있습니다. 어느 것도 우아하다고 할 수 없습니다.

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

또는

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

더 깨끗한 방법은 무엇일까요?

편집: 지금까지의 답변은 용량에 대한 것으로, 목록을 미리 작성하는 것과는 다릅니다.예를 들어, 용량이 10인 목록에서는 다음 작업을 수행할 수 없습니다.L[2]="somevalue"

편집 2: 사람들은 목록이 의도된 방식이 아닌데 왜 이런 식으로 사용하려고 하는지 궁금해합니다.두 가지 이유를 알 수 있습니다.

  1. 목록이 "차세대" 어레이라고 하면 거의 아무런 불이익 없이 유연성을 추가할 수 있습니다.따라서 기본적으로 사용해야 합니다.초기화하는 것이 그렇게 쉽지 않을 수도 있다는 것입니다.

  2. 제가 현재 쓰고 있는 것은 더 큰 프레임워크의 일부로 기본 기능을 제공하는 기본 클래스입니다.제가 제공하는 기본 기능에서는 목록의 크기가 고급으로 알려져 있으므로 배열을 사용할 수 있었습니다.그러나 기본 클래스에 동적으로 확장할 수 있는 기회를 제공하고 싶으므로 목록을 선택합니다.

List<string> L = new List<string> ( new string[10] );

제가 이것이 자주 필요하다고 말할 수는 없습니다. 왜 이것을 원하는지 더 자세히 알려주시겠습니까?도우미 클래스에서 정적인 방법으로 사용할 수 있습니다.

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

사용할 수 있습니다.Enumerable.Repeat(default(T), count).ToList()버퍼 크기 조정으로 인해 비효율적일 수 있습니다.

: 만약인 경우 하세요.T으로, 참유형저니다장을 합니다.count『 』 『 』 『 』 『 』에.value매개 변수 - 모든 개체가 동일한 개체를 참조합니다.사용 사례에 따라 원하는 것이 될 수도 있고 아닐 수도 있습니다.

편집: 댓글에서 언급한 것처럼, 당신은 할 수 있습니다.Repeated원하는 경우 루프를 사용하여 목록을 채웁니다.그것도 조금 더 빠를 것입니다.개인적으로 나는 다음을 사용하여 코드를 찾습니다.Repeat실제 환경에서는 성능 차이와 무관하지만 주행 거리는 다를 수 있습니다.

int("capacity")를 인수로 사용하는 생성자:

List<string> = new List<string>(10);

편집: 저는 프레데릭의 의견에 동의합니다.당신은 목록을 처음부터 사용한 배경에 있는 전체적인 추론과 반대되는 방식으로 사용하고 있습니다.

EDIT2:

EDIT 2: 제가 현재 쓰고 있는 것은 더 큰 프레임워크의 일부로 기본 기능을 제공하는 기본 클래스입니다.제가 제공하는 기본 기능에서는 목록의 크기가 고급으로 알려져 있으므로 배열을 사용할 수 있었습니다.그러나 기본 클래스에 동적으로 확장할 수 있는 기회를 제공하고 싶으므로 목록을 선택합니다.

모든 null 값을 가진 목록의 크기를 알아야 하는 이유는 무엇입니까?목록에 실제 값이 없으면 길이가 0이 될 것으로 예상합니다.어쨌든, 이것이 흐리멍덩하다는 것은 수업의 의도된 사용에 반하는 것이라는 것을 보여줍니다.

먼저 원하는 항목 수로 배열을 만든 다음 배열을 목록으로 변환합니다.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();

일부 고정 값의 N개 요소로 목록을 초기화하려는 경우:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}

고정 값으로 초기화하려면 목록을 사용하는 이유는 무엇입니까?성능을 위해 초기 용량을 제공하려는 것은 이해할 수 있지만, 일반 어레이보다 목록의 이점 중 하나가 필요할 때 확장할 수 있다는 것이 아닙니까?

이 작업을 수행할 때:

List<int> = new List<int>(100);

용량이 100 정수인 리스트를 생성합니다.이는 101번째 항목을 추가할 때까지 목록이 '증가'할 필요가 없음을 의미합니다.목록의 기본 배열은 100 길이로 초기화됩니다.

이것은 오래된 질문이지만, 저는 두 가지 해결책이 있습니다.하나는 빠르고 더러운 성찰입니다. 다른 하나는 질문에 실제로 답하는 솔루션입니다(용량이 아닌 크기 설정). 여기서는 어떤 답변도 수행하지 않습니다.


반사

이것은 빠르고 지저분하며 코드가 무엇을 하는지 매우 분명해야 합니다.속도를 높이려면 GetField의 결과를 캐시하거나 DynamicMethod를 만들어 다음과 같이 수행합니다.

public static void SetSize<T>(this List<T> l, int newSize) =>
    l.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(l, newSize);

분명히 많은 사람들이 그러한 코드를 생산에 넣는 것을 주저할 것입니다.


ICollection<T>

이 솔루션은 생성자가 다음과 같은 사실을 기반으로 합니다.List(IEnumerable<T> collection)에 대해 합니다.ICollection<T>크기를 반복하지 않고 정확한 크기로 즉시 조정합니다.을 그런다수호출다니합을집음▁the다라고 부릅니다.CopyTo복사하기 위해.

의 코드는 다음과 같습니다.List<T>생성자는 다음과 같습니다.

public List(IEnumerable<T> collection) {
....
    ICollection<T> c = collection as ICollection<T>;
    if (collection is ICollection<T> c)
    {
        int count = c.Count;
        if (count == 0)
        {
            _items = s_emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    

따라서 추가 복사 없이 목록을 올바른 크기로 최적으로 사전 초기화할 수 있습니다.

▁an?▁creating수▁how▁by럴?그?ICollection<T>반환하는 것 외에는 아무것도 하지 않는 객체Count구체적으로, 우리는 어떤 것도 구현하지 않을 입니다.CopyTo호출되는 유일한 다른 함수입니다.

private struct SizeCollection<T> : ICollection<T>
{
    public SizeCollection(int size) =>
        Count = size;

    public void Add(T i){}
    public void Clear(){}
    public bool Contains(T i)=>true;
    public void CopyTo(T[]a, int i){}
    public bool Remove(T i)=>true;
    public int Count {get;}
    public bool IsReadOnly=>true;
    public IEnumerator<T> GetEnumerator()=>null;
    IEnumerator IEnumerable.GetEnumerator()=>null;
}

public List<T> InitializedList<T>(int size) =>
    new List<T>(new SizeCollection<T>(size));

우리는 이론적으로 같은 일을 할 수 있습니다.AddRange/InsertRange배열의 에는 기존어대설명다니합도해도 설명합니다.ICollection<T>하지만 코드는 추정되는 항목에 대해 새로운 배열을 생성한 다음 복사합니다.때는 그냥 빈 루프로 하는 게 것 요.Add:

public void SetSize<T>(this List<T> l, int size)
{
    if(size < l.Count)
        l.RemoveRange(size, l.Count - size);
    else
        for(size -= l.Count; size > 0; size--)
            l.Add(default(T));
}

목록의 내용을 그런 식으로 초기화하는 것은 목록의 목적이 아닙니다.목록은 개체를 포함하도록 설계되었습니다.특정 숫자를 특정 개체에 매핑하려면 목록 대신 해시 테이블 또는 사전과 같은 키-값 쌍 구조를 사용하는 것이 좋습니다.

데이터와의 위치 연결이 필요하다는 점을 강조하는 것 같은데, 연관 배열이 더 적합하지 않을까요?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";

승인된 답변(녹색 확인 표시가 있는 답변)에 문제가 있습니다.

문제:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

개체를 복사하려면 위의 줄을 변경하는 것이 좋습니다.이에 대한 다양한 기사가 있습니다.

목록의 모든 항목을 NULL이 아닌 기본 생성자로 초기화하려면 다음 방법을 추가합니다.

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }

Linkq를 사용하여 목록을 기본값으로 교묘하게 초기화할 수 있습니다.(David B의 답변과 유사합니다.)

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

한 단계 더 나아가 "string 1", "string 2", "string 3" 등의 고유한 값을 사용하여 각 문자열을 초기화합니다.

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();

다시 생각해 본 결과, OP 질문에 대한 비반영적인 답을 찾았지만, 찰리 페이스가 저를 이겼습니다.그래서 저는 정확하고 완전한 답은 https://stackoverflow.com/a/65766955/4572240 이라고 생각합니다.

나의 예전 대답:

내가 정확히 이해했다면, 당신은 목록을 원합니다 <새로운 T[size] 버전으로, 값을 추가하는 오버헤드가 없습니다.

목록의 구현이 두렵지 않다면 <앞으로 급격하게 변화할 것입니다(이 경우 확률이 0에 가깝다고 생각합니다). 반사를 사용할 수 있습니다.

    public static List<T> NewOfSize<T>(int size) {
        var list = new List<T>(size);
        var sizeField = list.GetType().GetField("_size",BindingFlags.Instance|BindingFlags.NonPublic);
        sizeField.SetValue(list, size);
        return list;
    }

이는 항목 유형의 기본값으로 미리 채우기 위한 기본 배열의 기본 기능을 고려합니다.모든 int 배열의 값은 0이고 모든 참조 유형 배열의 값은 null입니다.또한 참조 유형 목록의 경우 각 항목에 대한 포인터 공간만 생성됩니다.

만약 어떤 이유로 반사를 사용하지 않기로 결정한다면, 저는 생성기 메소드가 있는 AddRange 옵션을 List 아래에 제공하고 싶습니다.<삽입을 10억 번만 호출해도 소용이 없습니다.

또한 Array 클래스에는 RezizeArray라는 정적 메서드가 있습니다. 반대로 Array에서 시작하려면 이 메서드를 사용해야 합니다.

마지막으로, 저는 제가 질문을 하는데 모두가 그것이 잘못된 질문이라고 지적하는 것을 정말 싫어합니다.그럴 수도 있고, 정보를 주셔서 감사합니다. 하지만 여전히 답변을 원합니다. 왜냐하면 당신은 제가 왜 그것을 묻고 있는지 모르기 때문입니다.즉, 자원을 최적으로 사용할 수 있는 프레임워크를 만들고 싶다면 목록<컬렉션의 끝에 물건을 보관하고 추가하는 것보다 훨씬 비효율적인 클래스입니다.

IList에 대한 알림: MSDN IList 비고: "IList 구현은 읽기 전용, 고정 크기 및 가변 크기의 세 가지 범주로 나뉩니다. (...)이 인터페이스의 일반 버전은 을 참조하십시오.System.Collections.Generic.IList<T>."

IList<T>에서 .IList(그러나List<T>는 두가를모구다니합현을 모두 합니다.IList<T>그리고.IList)이지만 항상 가변 크기입니다..NET 4.5 이후로, 우리는 또한IReadOnlyList<T>하지만 AFAIK, 당신이 찾고 있는 고정된 크기의 제네릭 리스트는 없습니다.

이것은 제가 유닛 테스트에 사용한 샘플입니다.클래스 개체 목록을 만들었습니다.그런 다음 루프를 사용하여 서비스에서 기대하는 'X'개의 객체를 추가했습니다.이렇게 하면 지정된 크기에 대해 목록을 추가/초기화할 수 있습니다.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

제가 당신들에게 도움이 되었기를 바랍니다.

조금 늦었지만 당신이 제안한 첫 번째 해결책은 훨씬 더 깨끗해 보입니다: 당신은 메모리를 두 번 할당하지 않습니다.심지어 List 생성자도 배열을 복사하려면 배열을 반복해야 합니다. 내부에 null 요소만 있는지 미리 알지도 못합니다.

- 할당 N - 루프 N 비용 : 1 * 할당(N) + N * 루프_반복

- 할당 N - 할당 N + 루프 ( ) 비용 : 2 * 할당(N) + N * 루프 반복

그러나 List는 기본 제공 클래스이지만 C#은 jit-compilled soo이기 때문에 List의 할당 및 루프가 더 빠를 수 있습니다.

언급URL : https://stackoverflow.com/questions/466946/how-to-initialize-a-listt-to-a-given-size-as-opposed-to-capacity

반응형