web-dev-qa-db-ja.com

Cの配列から要素を削除する

Cの配列について簡単な質問があります

配列から要素を削除し、その過程で配列を小さくするための最良の方法は何ですか。

つまり、配列のサイズがnである場合、配列から要素を取り出し、削除した分だけ配列が小さくなります。

基本的には、この配列をカードのデッキのように扱っており、一度デッキの一番上からカードを取り除いてしまえば、もうそこにあるはずはありません。

編集:一日の終わりまでに夢中になります。価値交換を試みているすべての助けに感謝しますが、うまくいきません。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

enum faces{Ace = 0, Jack = 10, Queen, King};
char * facecheck(int d); 
int draw(int deck, int i); 
int main() 
{ 
    int deck[52], i, n;
    char suits[4][9] = 
    {
        "Hearts",
        "Diamonds",
        "Clubs",
        "Spades"};


    n = 0;

    for(i = 0; i<52; i++)
    {
        deck[i] = n;
        n++;
     };



    for(i=0; i<52; i++)
    {       
        if(i%13 == 0 || i%13 == 10 || i%13 == 11 || i%13 == 12)
            printf("%s ", facecheck(i%13) );
        else printf("%d ", i%13+1);
        printf("of %s \n", suits[i/13]);
    }

    draw(deck, i);


    return 0; 
}  

char * facecheck(int d)
{
    static char * face[] = 
    {
        "Ace",
        "Jack",
        "Queen",
        "King" };

    if(d == Ace)
        return face[0];
    else
    {
        if(d == Jack) 
            return face[1];
        else
        {
            if(d == Queen)
                return face[2];
            else 
            { 
                if(d == King)
                    return face[3];
            }
        }
    } 
}

int draw(int deck,int i ) 
{ 
    int hand[5], j, temp[j];

    for(i=0; i<52; i++)
    {
             j = i
             }; 

    for(i = 0; i < 5; i++)
    {
          deck[i] = hand[]; 
          printf("A card has been drawn \n");
          deck[i] = temp[j-1];
          temp[j] = deck[i];
          };

          return deck;
}
20
Rini Posny

本当に2つの別々の問題があります。 1つ目は、要素を削除した後に「穴」がないように、配列の要素を適切な順序に保つことです。 2つ目は、実際に配列自体のサイズを変更することです。

Cの配列は、固定数の連続した要素として割り当てられます。配列内の個々の要素が使用しているメモリを実際に削除する方法はありませんが、要素を移動して要素を削除することでできた穴を埋めることができます。例えば:

void remove_element(array_type *array, int index, int array_length)
{
   int i;
   for(i = index; i < array_length - 1; i++) array[i] = array[i + 1];
}

静的に割り当てられた配列はサイズ変更できません。動的に割り当てられた配列は、realloc()でサイズ変更できます。これにより、配列全体がメモリ内の別の場所に移動する可能性があるため、配列またはその要素へのすべてのポインターを更新する必要があります。例えば:

remove_element(array, index, array_length);  /* First shift the elements, then reallocate */
array_type *tmp = realloc(array, (array_length - 1) * sizeof(array_type) );
if (tmp == NULL && array_length > 1) {
   /* No memory available */
   exit(EXIT_FAILURE);
}
array_length = array_length - 1;
array = tmp;

reallocは、要求されたサイズが0の場合、またはエラーがある場合、NULLポインターを返します。それ以外の場合は、再割り当てされた配列へのポインターを返します。一時ポインターは、reallocを呼び出すときにエラーを検出するために使用されます。これは、終了する代わりに、元の配列をそのまま残すこともできるためです。 reallocが配列の再割り当てに失敗した場合、元の配列は変更されません。

配列が大きい場合、または多くの要素が削除される場合、これらの操作は両方ともかなり遅くなります。効率的な挿入と削除が優先される場合に使用できるリンクリストやハッシュなど、他のデータ構造があります。

12
Ben

何かを削除するたびにメモリを再割り当てする必要はありません。 deckの大まかなサイズがわかっている場合は、配列に適切なサイズを選択し、現在のendへのポインターを保持しますリスト。これは stack です。

デッキのサイズがわからず、サイズが変化し続けるだけでなく本当に大きくなる可能性があると思う場合は、もう少し複雑なことをして、 linked-list を実装する必要があります。 。

Cでは、配列を宣言する2つの簡単な方法があります。

  1. スタック上で、静的配列として

    int myArray[16]; // Static array of 16 integers
    
  2. 動的に割り当てられた配列としてのヒープ上

    // Dynamically allocated array of 16 integers
    int* myArray = calloc(16, sizeof(int));
    

標準Cでは、これらのタイプの配列のサイズを変更できません。特定のサイズの新しい配列を作成してから、古い配列の内容を新しい配列にコピーするか、上記の提案のいずれかに従って、異なる抽象データ型(リンクリスト、スタック、キュー、等)。

7
Chris Farmiloe

興味深いことに、配列はインデックスによってランダムにアクセスできます。また、要素をランダムに削除すると、他の要素のインデックスにも影響する場合があります。

    int remove_element(int*from, int total, int index) {
            if((total - index - 1) > 0) {
                      memmove(from+i, from+i+1, sizeof(int)*(total-index-1));
            }
            return total-1; // return the new array size
    }

メモリが重複しているため、この場合memcpyは機能しません。

1つのランダムな要素を削除する効率的な方法の1つ(メモリ移動よりも良い)は、最後の要素と交換することです。

    int remove_element(int*from, int total, int index) {
            if(index != (total-1))
                    from[index] = from[total-1];
            return total; // **DO NOT DECREASE** the total here
    }

ただし、削除後に順序は変更されます。

繰り返しますが、ループ操作で削除が行われると、並べ替えが処理に影響を与える可能性があります。メモリの移動は、配列要素を削除しながら順序を維持するための高価な代替手段です。ループ中に順序を維持するもう1つの方法は、削除を延期することです。同じサイズの妥当性配列によって実行できます。

    int remove_element(int*from, int total, int*is_valid, int index) {
            is_valid[index] = 0;
            return total-1; // return the number of elements
    }

スパース配列を作成します。最後に、並べ替えを行うことで、スパース配列をコンパクトにすることができます(その間に無効な要素を含む2つの有効な要素が含まれない)。

    int sparse_to_compact(int*arr, int total, int*is_valid) {
            int i = 0;
            int last = total - 1;
            // trim the last invalid elements
            for(; last >= 0 && !is_valid[last]; last--); // trim invalid elements from last

            // now we keep swapping the invalid with last valid element
            for(i=0; i < last; i++) {
                    if(is_valid[i])
                            continue;
                    arr[i] = arr[last]; // swap invalid with the last valid
                    last--;
                    for(; last >= 0 && !is_valid[last]; last--); // trim invalid elements
            }
            return last+1; // return the compact length of the array
    }
3
shuva