Как удалить объекты из массива в Java?


82

Для массива из n объектов, допустим, это массив строк со следующими значениями:

foo[0] = "a";
foo[1] = "cc";
foo[2] = "a";
foo[3] = "dd";

Что мне нужно сделать, чтобы удалить / удалить все строки / объекты, равные «a» в массиве?


2
Вы не можете изменить размер массива в Java. Я предполагаю, что вы не хотите просто обнулять элементы, поскольку это было бы тривиально. Хотите сместить остальные элементы, чтобы убрать зазоры?
Дэн Дайер,

1
Это тривиально, теперь, когда я знаю, что могу это сделать. ;) Благодаря!
ramayac

Ответы:


112

[Если вам нужен готовый код, прокрутите до моего «Edit3» (после вырезания). Остальное здесь для потомков.]

Чтобы конкретизировать идею Дастмана :

List<String> list = new ArrayList<String>(Arrays.asList(array));
list.removeAll(Arrays.asList("a"));
array = list.toArray(array);

Изменить: Я сейчас , используя Arrays.asListвместо Collections.singleton: синглтон ограничивается одной записи, в то время как asListподход позволяет добавить другие строки , чтобы отфильтровать позднее: Arrays.asList("a", "b", "c").

Edit2: Приведенный выше подход сохраняет тот же массив (поэтому массив все еще имеет ту же длину); элемент после последнего устанавливается равным нулю. Если вы хотите, чтобы размер нового массива был точно таким, как требуется, используйте вместо этого:

array = list.toArray(new String[0]);

Edit3: если вы часто используете этот код в одном классе, вы можете подумать о добавлении этого в свой класс:

private static final String[] EMPTY_STRING_ARRAY = new String[0];

Тогда функция становится:

List<String> list = new ArrayList<>();
Collections.addAll(list, array);
list.removeAll(Arrays.asList("a"));
array = list.toArray(EMPTY_STRING_ARRAY);

Тогда это перестанет засорять вашу кучу бесполезными массивами пустых строк, которые в противном случае редактировались бы newкаждый раз при вызове вашей функции.

Предложение циничного человека (см. комментарии) также поможет с засорением кучи, и для справедливости я должен его упомянуть:

array = list.toArray(new String[list.size()]);

Я предпочитаю свой подход, потому что может быть легче size()указать неправильный размер (например, вызвать неправильный список).


Рада что понравилось. Я изменил свою запись, чтобы поддержать удаление всех экземпляров «а», а не только первого. :-)
Крис Джестер-Янг

Офф ... сбит на финише. Знал, что мне следовало продолжить редактирование. К этой системе нужно привыкнуть. Хорошая редакция!
Dustman,

GHad: Вы читали мой Edit2 выше? Он обращается именно к тому, что вы упомянули, и было опубликовано до вашего сообщения.
Крис Джестер-Янг,

2
Почему бы не использовать list.toArray (new String [list.size ()]), а не new String [0], поскольку код будет использовать новый массив, если он имеет правильный размер?
cynicalman

Да, это работает. В противном случае некоторый код (в основном в библиотеке классов Java) хранит статический экземпляр String [0] (и других подобных массивов нулевого размера) и передает статические экземпляры вместо того, чтобы каждый раз создавать новый. :-)
Крис Джестер-Янг

31

Альтернатива в Java 8:

String[] filteredArray = Arrays.stream(array)
    .filter(e -> !e.equals(foo)).toArray(String[]::new);

Это должен быть принятый ответ. Хотя это не очень приятно, учитывая, что другие языки программирования могут делать это с меньшим и более ясным кодом ... это лучшее, что Java может предложить, независимо от другой библиотеки.
Джейсон

7
Хотя это аккуратно, это будет удивительно неэффективно по сравнению с System.arraycopy. Наверное, не стоит этого делать в реальном коде.
Билл К.

если foo является динамической строковой переменной, это вызывает ошибку времени компиляции. Локальная переменная foo, определенная во включающей области видимости, должна быть окончательной или фактически окончательной
Махендер Редди Яса

Вопрос гласит: «Дан массив из n объектов» - этого Stream.of(foo).filter(s -> ! s.equals("a")).toArray()будет достаточно.
Kaplan

20

Сделайте Listиз массива Arrays.asList()и вызовите remove()все соответствующие элементы. Затем вызовите toArray()«Список», чтобы снова превратить его в массив.

Не очень производительный, но если вы его правильно инкапсулируете, вы всегда сможете сделать что-то быстрее позже.


3
Повторите свой комментарий: Ничего страшного, скоро привыкнешь. :-) Я разместил свой пост, потому что не хотел, чтобы читатели догадались, что элементы могут быть удалены из результата Arrays.asList () (это неизменяемый список), поэтому я подумал, что пример может позаботиться об этом. :-)
Крис Джестер-Янг,

Я имел в виду список без изменения размера (add () и remove () не работают). :-P У него все еще есть пригодный для использования метод set (). :-)
Крис Джестер-Янг,

Хотя это может показаться странным, мой опыт показывает, что снижение производительности при таком подходе минимально.
Маркус Даунинг,

8
Что с этим? @Chris указал, что полученный список Arrays.asList()не поддерживает remove(). Так этот ответ полностью неверен? Похоже, что некоторые комментарии были удалены, поэтому я не знаю, обсуждалось ли это.
LarsH

1
И Крис, и Ларш правы: списки, поддерживаемые массивами (другими словами, те, которые создаются с помощью Arrays.asList ()), структурно неизменяемы, что полностью аннулирует этот ответ. Тем не менее, на данный момент я вижу семнадцать голосов "за". Удивительно ...
Игорь Судакевич

15

Вы всегда можете:

int i, j;
for (i = j = 0; j < foo.length; ++j)
  if (!"a".equals(foo[j])) foo[i++] = foo[j];
foo = Arrays.copyOf(foo, i);

6

Вы можете использовать внешнюю библиотеку:

org.apache.commons.lang.ArrayUtils.remove(java.lang.Object[] array, int index)

Он находится в проекте Apache Commons Lang http://commons.apache.org/lang/


ArrayUtils.removeElement(boolean[] array, boolean element)тоже очень полезно.
scai

5

См. Код ниже

ArrayList<String> a = new ArrayList<>(Arrays.asList(strings));
a.remove(i);
strings = new String[a.size()];
a.toArray(strings);

4

Если вам нужно удалить несколько элементов из массива, не преобразовывая его и Listне создавая дополнительный массив, вы можете сделать это за O (n), независимо от количества удаляемых элементов.

Здесь aисходный массив, int... rотдельные упорядоченные индексы (позиции) удаляемых элементов:

public int removeItems(Object[] a, int... r) {
    int shift = 0;                             
    for (int i = 0; i < a.length; i++) {       
        if (shift < r.length && i == r[shift])  // i-th item needs to be removed
            shift++;                            // increment `shift`
        else 
            a[i - shift] = a[i];                // move i-th item `shift` positions left
    }
    for (int i = a.length - shift; i < a.length; i++)
        a[i] = null;                            // replace remaining items by nulls

    return a.length - shift;                    // return new "length"
}  

Небольшое тестирование:

String[] a = {"0", "1", "2", "3", "4"};
removeItems(a, 0, 3, 4);                     // remove 0-th, 3-rd and 4-th items
System.out.println(Arrays.asList(a));        // [1, 2, null, null, null]

В вашей задаче вы можете сначала сканировать массив, чтобы собрать позиции «а», а затем позвонить removeItems().


1
Пожалуйста, не делай этого. Это сбивает с толку, медленно и подвержено ошибкам. Просто используйте вместо этого System.arraycopy () - хотя и бонусные баллы за тот факт, что если вы собираетесь манипулировать массивом таким образом, вы должны отслеживать длину.
Bill K

4

Здесь есть много ответов - проблема, как я вижу, в том, что вы не сказали, ПОЧЕМУ вы используете массив вместо коллекции, поэтому позвольте мне предложить пару причин и какие решения будут применяться (большинство решений здесь уже были даны ответы на другие вопросы, поэтому я не буду вдаваться в подробности):

причина: вы не знали о существовании пакета сбора или не доверяли ему

Решение: используйте коллекцию.

Если вы планируете добавлять / удалять из середины, используйте LinkedList. Если вас действительно беспокоит размер или вы часто индексируете прямо в середине коллекции, используйте ArrayList. Оба они должны иметь операции удаления.

причина: вы обеспокоены размером или хотите контролировать распределение памяти

Решение: используйте ArrayList с определенным начальным размером.

ArrayList - это просто массив, который может расширяться, но это не всегда нужно делать. Будет очень разумно добавлять / удалять элементы, но, опять же, если вы вставляете / удаляете ЛОТ из середины, используйте LinkedList.

причина: у вас есть массив, входящий и выходящий массив, поэтому вы хотите работать с массивом

Решение: преобразовать его в ArrayList, удалить элемент и преобразовать обратно

причина: вы думаете, что можете написать лучший код, если сделаете это сами

Решение: вы не можете использовать массив или связанный список.

причина: это назначение класса, и вам не разрешено или у вас нет доступа к API коллекции по какой-то причине

предположение: вам нужно, чтобы новый массив был правильного «размера»

Решение: просканируйте массив на предмет совпадающих элементов и посчитайте их. Создайте новый массив правильного размера (исходный размер - количество совпадений). используйте System.arraycopy несколько раз, чтобы скопировать каждую группу элементов, которые вы хотите сохранить, в новый массив. Если это назначение класса и вы не можете использовать System.arraycopy, просто копируйте их по одному вручную в цикле, но никогда не делайте этого в производственном коде, потому что это намного медленнее. (Эти решения подробно описаны в других ответах)

причина: вам нужно запускать голый металл

предположение: вы НЕ ДОЛЖНЫ выделять пространство без необходимости или занимать слишком много времени

предположение: вы отслеживаете размер, используемый в массиве (длину) отдельно, потому что в противном случае вам пришлось бы перераспределять массив для удаления / вставки.

Пример того, почему вы можете захотеть это сделать: один массив примитивов (скажем, значения int) занимает значительную часть вашего барана - например, 50%! ArrayList включит их в список указателей на объекты Integer, которые будут использовать в несколько раз больше памяти.

Решение: выполните итерацию по вашему массиву и всякий раз, когда вы найдете элемент, который нужно удалить (назовем его элементом n), используйте System.arraycopy, чтобы скопировать хвост массива поверх «удаленного» элемента (Источник и Назначение - это один и тот же массив) - это достаточно умен, чтобы делать копию в правильном направлении, чтобы память не перезаписывалась:

 System.arraycopy (арный, п + 1, арный, п, длина-п) 
 длина -;

Вы, вероятно, захотите быть умнее этого, если удаляете более одного элемента за раз. Вы должны перемещать только область между одним «совпадением» и другим, а не весь хвост, и, как всегда, избегайте перемещения любого фрагмента дважды.

В этом последнем случае вы обязательно должны выполнить работу самостоятельно, и использование System.arraycopy - действительно единственный способ сделать это, поскольку он будет выбирать лучший способ перемещения памяти для вашей компьютерной архитектуры - это должно быть во много раз быстрее. чем любой код, который вы могли бы написать самостоятельно.


3

Что-то в том, чтобы составить список, затем удалить, а затем вернуться в массив, кажется мне неправильным. Не тестировал, но я думаю, что следующие будут работать лучше. Да, я, наверное, излишне заранее оптимизирую.

boolean [] deleteItem = new boolean[arr.length];
int size=0;
for(int i=0;i<arr.length;i==){
   if(arr[i].equals("a")){
      deleteItem[i]=true;
   }
   else{
      deleteItem[i]=false;
      size++;
   }
}
String[] newArr=new String[size];
int index=0;
for(int i=0;i<arr.length;i++){
   if(!deleteItem[i]){
      newArr[index++]=arr[i];
   }
}

3

Я понимаю, что это очень старый пост, но некоторые ответы здесь помогли мне, так что вот моя цена!

Я долго изо всех сил пытался заставить это работать, прежде чем подумать, что массив, в который я записываю, должен быть изменен, если только изменения, внесенные в список, не ArrayListоставляют размер списка неизменным.

Если ArrayListмодифицируемый вами элемент будет иметь большее или меньшее количество элементов, чем было в начале, строка List.toArray()вызовет исключение, поэтому вам нужно что-то вроде List.toArray(new String[] {})или List.toArray(new String[0])для создания массива с новым (правильным) размером.

Теперь, когда я это знаю, это звучит очевидно. Это не так очевидно для новичка в Android / Java, который разбирается в новых и незнакомых конструкциях кода, и не очевидно из некоторых из предыдущих публикаций здесь, поэтому просто хотел прояснить этот момент для всех, кто часами ломает голову, как я !


Я чувствовал необходимость опубликовать это, так как я часто использую фрагменты кода, которые не работают, потому что я пропустил то, что другие кодеры считают само собой разумеющимся. GHad указал на размер массива, который заставил мой код работать (спасибо, что разъяснили это). Пробовать что-то делать - это способ учиться, и если это означает, что я заслуживаю всего, что получаю за то, что взял код из SO и попытался понять, как / почему он работает, то пусть будет так. Как бесплатный любитель, я не гений Java, как некоторые думают! К счастью, большинство участников SO отвечают на вопросы, чтобы помочь другим писать лучший код: за это вы заслуживаете благодарности!
DDSports 05

2

Исходный массив

   int[] array = {5,6,51,4,3,2};

если вы хотите удалить 51, который является индексом 2, используйте следующие

 for(int i = 2; i < array.length -1; i++){
    array[i] = array[i + 1];
  }

1

РЕДАКТИРОВАТЬ:

Точка с нулями в массиве очищена. Извините за мои комментарии.

Оригинал:

Эм ... линия

array = list.toArray(array);

заменяет все пробелы в массиве, где был удаленный элемент, на null . Это может быть опасно , потому что элементы удаляются, но длина массива остается прежней!

Если вы хотите избежать этого, используйте новый массив в качестве параметра для toArray (). Если вы не хотите использовать removeAll, альтернативой будет Set:

        String[] array = new String[] { "a", "bc" ,"dc" ,"a", "ef" };

        System.out.println(Arrays.toString(array));

        Set<String> asSet = new HashSet<String>(Arrays.asList(array));
        asSet.remove("a");
        array = asSet.toArray(new String[] {});

        System.out.println(Arrays.toString(array));

Дает:

[a, bc, dc, a, ef]
[dc, ef, bc]

Где, как текущий принятый ответ от Криса Йестера Янга, выводится:

[a, bc, dc, a, ef]
[bc, dc, ef, null, ef]

с кодом

    String[] array = new String[] { "a", "bc" ,"dc" ,"a", "ef" };

    System.out.println(Arrays.toString(array));

    List<String> list = new ArrayList<String>(Arrays.asList(array));
    list.removeAll(Arrays.asList("a"));
    array = list.toArray(array);        

    System.out.println(Arrays.toString(array));

без каких-либо оставленных нулевых значений.


Хорошая попытка, но без сигары. Я опубликовал правку именно по этой теме задолго до того, как вы сделали свой пост. Итак, хотя вы «технически правы», мне не нравится ваша попытка заставить людей сместить мой пост. Я просто подумал, что тебе стоит это знать.
Крис Джестер-Янг,

Дело не в смещении поста, а в том, чтобы избежать ошибок и опасного кода. Greetz GHad
GHad

Ошибок можно избежать, если люди прочитают весь мой пост (включая оба приложения). Если люди просто копируют и вставляют код, не задумываясь, они заслуживают все, что получают. Программистам платят за то, что они делают, потому что они тренируют свой мозг ... Я надеюсь. [продолжение]
Крис Джестер-Янг,

1
[продолжение] Ваш код тоже «опасен», если люди не осознают тот факт, что при использовании хеша элементы становятся беспорядочными. Конечно, мыслящие программисты это понимают, но если вы называете мой код опасным, потому что люди копируют и вставляют, не задумываясь, будет справедливо сказать то же самое о вашем.
Крис Джестер-Янг,

Конечно, насчет хеша ты прав. И поскольку ваша вторая редакция прояснила это, я, должно быть, перечитал это. Как уже было сказано, просто хотел избежать массива с нулевыми значениями и дублирования. Вы можете изменить свой код на основе второго редактирования и оставить комментарий в Edit3 о массиве. Не хотел нападать на тебя
GHad

1

Мой небольшой вклад в эту проблему.

public class DeleteElementFromArray {
public static String foo[] = {"a","cc","a","dd"};
public static String search = "a";


public static void main(String[] args) {
    long stop = 0;
    long time = 0;
    long start = 0;
    System.out.println("Searched value in Array is: "+search);
    System.out.println("foo length before is: "+foo.length);
    for(int i=0;i<foo.length;i++){ System.out.println("foo["+i+"] = "+foo[i]);}
    System.out.println("==============================================================");
    start = System.nanoTime();
    foo = removeElementfromArray(search, foo);
    stop = System.nanoTime();
    time = stop - start;
    System.out.println("Equal search took in nano seconds = "+time);
    System.out.println("==========================================================");
    for(int i=0;i<foo.length;i++){ System.out.println("foo["+i+"] = "+foo[i]);}
}
public static String[] removeElementfromArray( String toSearchfor, String arr[] ){
     int i = 0;
     int t = 0;
     String tmp1[] = new String[arr.length];     
         for(;i<arr.length;i++){
              if(arr[i] == toSearchfor){     
              i++;
              }
             tmp1[t] = arr[i];
             t++;
     }   
     String tmp2[] = new String[arr.length-t];   
     System.arraycopy(tmp1, 0, tmp2, 0, tmp2.length);
     arr = tmp2; tmp1 = null; tmp2 = null;
    return arr;
}

}


Это действительно хороший ответ, хотя ваш отличный тестовый код заставляет его выглядеть намного больше, чем есть на самом деле. Все решение может представлять собой однострочную копию массива (при условии, что вы используете тот же массив, что вы хотели бы сделать, если бы вы работали так близко к металлу, вы использовали массивы вместо коллекций).
Bill K

спасибо, я написал это, чтобы читатели могли немного увидеть, что происходит под капотом, и протестировать это, теперь я снова смотрю на это System.arraycopy (tmp1, 0, tmp2, 0, tmp2.length); можно удалить и заменить на что-то вроде for (i = 0; i <(arr.length - t); i ++) {tmp2 [i] = tmp1 [i]; } вы также можете создать байтовый буфер и копировать по 8 байтов за раз на 64-битную машину, чтобы получить дополнительную производительность копирования
Андре

0

Это зависит от того, что вы подразумеваете под «удалением»? Массив - это конструкция фиксированного размера - вы не можете изменить количество элементов в нем. Таким образом, вы можете либо а) создать новый, более короткий массив без элементов, которые вам не нужны, или б) назначить записи, которые вы не хотите, чему-то, что указывает на их «пустой» статус; обычно null, если вы не работаете с примитивами.

В первом случае создайте список из массива, удалите элементы и создайте новый массив из списка. Если производительность важна, перебирайте массив, назначая любые элементы, которые не следует удалять, в список, а затем создайте новый массив из списка. Во втором случае просто пройдите и присвойте пустым записям массива.


0

Arrgh, я не могу заставить код отображаться правильно. Извините, у меня все заработало. Еще раз извините, я не думаю, что правильно прочитал вопрос.

String  foo[] = {"a","cc","a","dd"},
remove = "a";
boolean gaps[] = new boolean[foo.length];
int newlength = 0;

for (int c = 0; c<foo.length; c++)
{
    if (foo[c].equals(remove))
    {
        gaps[c] = true;
        newlength++;
    }
    else 
        gaps[c] = false;

    System.out.println(foo[c]);
}

String newString[] = new String[newlength];

System.out.println("");

for (int c1=0, c2=0; c1<foo.length; c1++)
{
    if (!gaps[c1])
    {
        newString[c2] = foo[c1];
        System.out.println(newString[c2]);
        c2++;
    }
}

0

Скопирует все элементы, кроме элемента с индексом i:

if(i == 0){
                System.arraycopy(edges, 1, copyEdge, 0, edges.length -1 );
            }else{
                System.arraycopy(edges, 0, copyEdge, 0, i );
                System.arraycopy(edges, i+1, copyEdge, i, edges.length - (i+1) );
            }

0

В массиве строк типа

String name = 'abcdeafbde' // может быть как String name = 'aa bb cde aa f bb de'

Строю следующий класс

class clearname{
def parts
def tv
public def str = ''
String name
clearname(String name){
    this.name = name
    this.parts = this.name.split(" ")
    this.tv = this.parts.size()
}
public String cleared(){

        int i
        int k
        int j=0        
    for(i=0;i<tv;i++){
        for(k=0;k<tv;k++){
            if(this.parts[k] == this.parts[i] && k!=i){
               this.parts[k] = '';
                j++
            }
        }
    }
    def str = ''
    for(i=0;i<tv;i++){
        if(this.parts[i]!='')

           this.str += this.parts[i].trim()+' '
    } 
    return this.str    
}}



return new clearname(name).cleared()

получить этот результат

abcdef

надеюсь, что этот код поможет кому-нибудь С уважением


0

Если не имеет значения порядок элементов. вы можете переключаться между элементами foo [x] и foo [0], а затем вызывать foo.drop (1).

foo.drop(n) удаляет (n) первых элементов из массива.

Думаю, это самый простой и ресурсосберегающий способ сделать это.

PS : indexOfможно реализовать разными способами, это моя версия.

Integer indexOf(String[] arr, String value){
    for(Integer i = 0 ; i < arr.length; i++ )
        if(arr[i] == value)
            return i;         // return the index of the element
    return -1                 // otherwise -1
}

while (true) {
   Integer i;
   i = indexOf(foo,"a")
   if (i == -1) break;
   foo[i] = foo[0];           // preserve foo[0]
   foo.drop(1);
}


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.