Блог пользователя IlyaCk

Автор IlyaCk, 13 лет назад, По-русски

Туповатый вопрос, но всё же: как правильно реализовывать в Java представление графа списками смежности?

Пишу
ArrayList<Integer>[] graph = new ArrayList<Integer>[N];
for(int i=0; i<N; i++)
    graph[i] = new ArrayList<Integer>();
вообще не компилирует говорит ошибку "generic array creation".

Пишу
List[] graph = new List[N];
for(int i=0; i<N; i++)
    graph[i] = new ArrayList<Integer>();
компилирует но говорит предупреждение "Note: Main.java uses unchecked or unsafe operations.".

А как сделать по-хорошему, чтоб не было предупреждений?

Просьба не объяснять, как делать в других языках программирования. Вопрос именно по реализации именно в Java.

  • Проголосовать: нравится
  • +18
  • Проголосовать: не нравится

13 лет назад, # |
Rev. 2   Проголосовать: нравится -35 Проголосовать: не нравится

В прошлой правке абсолютный бред. (Правка сделана после сообщения ниже)

13 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
ArrayList<Integer> g[] = new ArrayList [n];
for (int i=0;i<n;i++)
g[i]=new ArrayList<Integer>();
13 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
http://download.oracle.com/javase/tutorial/java/generics/index.html

Правильно писать
ArrayList<Integer>[] graph = new ArrayList[N];

Дело в том что в Java у объектов есть не только класс но и параметры класса, при этом соответствие параметров при вызове методов и при присваивании не проверяется, просто иногда компилятор кидает warning'и о том что он не знает, какой у данного объекта параметр. Можно создать ArrayList<String> и присвоить его в ArrayList<Integer> и это будет компилироваться. В рантайме происходит следующее: при вызове параметризованного метода проверяется что параметр объекта соответствует тому параметру который там ожидается, в случае неудачи генерируется какой-то RuntimeException.
Созданный массив имеет тип ArrayList[], без всякого параметра. А вот ссылка на него может быть с параметром, чтобы указать какой параметр имеют объекты хранящиеся в этом массиве.
  • 13 лет назад, # ^ |
      Проголосовать: нравится -8 Проголосовать: не нравится
    Что-то я не заметил, чтоб от этого пропало предупреждение "Note: Main.java uses unchecked or unsafe operations". Или это так и должно остаться, а заявления о том, что Java лучше, чем плюсы, гарантирует безопасность выполнения, надо делить на 0x100?

    Полный текст --
    [cut]
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.StreamTokenizer;
    import java.util.ArrayDeque;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Queue;
    import java.util.logging.Level;
    import java.util.logging.Logger;

    public class Main {
        static int nextInt(StreamTokenizer in) {
            try {
                in.nextToken();
                return (int) in.nval;
            } catch (IOException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                return 0;
            }
        }
        public static void main(String[] args) throws FileNotFoundException {
            StreamTokenizer in = new StreamTokenizer(new FileReader("input.txt"));
            PrintWriter out = new PrintWriter(new File("output.txt"));
            int TEST_NUM = nextInt(in);
            for(int the_test=0; the_test<TEST_NUM; the_test++) {
                int N = nextInt(in), M = nextInt(in);
                ArrayList<Integer>[] graph = new ArrayList[N];
                for(int i=0; i<N; i++)
                    graph[i] = new ArrayList<Integer>();
                for(int k=0; k<M; k++) {
                    int u = nextInt(in), v = nextInt(in);
                    graph[u].add(new Integer(v));
                }
                int[] st = new int[N];
                int[] d = new int[N];
                Arrays.fill(st, 0);
                Arrays.fill(d, 987654321);
                int start = nextInt(in);
                st[start] = 1;
                d[start] = 0;
                Queue<Integer> qqq = new ArrayDeque<Integer>();
                qqq.add(start);
                while(!qqq.isEmpty()) {
                    int u = qqq.poll();
                    for(int w : (ArrayList<Integer>) graph[u])
                        if(st[w]==0) {
                            qqq.add(w);
                            st[w] = 1;
                            d[w] = d[u] +1;
                        }
                }
                for(int i=0; i<N; i++)
                    out.print(d[i] + " ");
                out.println();
            }
            out.close();
        }
    }

    Точные требования с форматам ввода-вывода и т.д. -- http://informatics.mccme.ru/moodle/mod/statements/view3.php?id=3311&chapterid=3489#1
    • 13 лет назад, # ^ |
        Проголосовать: нравится +8 Проголосовать: не нравится
      Ну замените

      ArrayList<Integer>[] graph = new ArrayList[N];

      на

      @SuppressWarnings("unchecked")
      ArrayList<Integer>[] graph = new ArrayList[N];

      раз уже так этот warning ненавистен
    • 13 лет назад, # ^ |
        Проголосовать: нравится +8 Проголосовать: не нравится
      Больше так не делай, используй что-нибудь типа ideone или paste.pocoo.org
      Во-первых generic-ами часто бывают unsafe casts, и warning-и никуда не денешь. Во-вторых в Java 7 компилятор рассматривает часть из этих случаев как safe и ничего не пишет. В-третьих безопасность - это когда программа не падает от неправильного действия а генерирует exception, который ловится и обрабатывается. Точно так же можно попросить проверить возможность деления на 0 или присваивания типа Integer x =(Integer) obj; 
      • 13 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        "безопасность - это когда программа не падает от неправильного действия а генерирует exception, который ловится и обрабатывается"
        Где именно я не прав, считая, что когда неправильное действие вообще не пропускается компилятором, то это ещё более безопасно?
        • 13 лет назад, # ^ |
            Проголосовать: нравится +5 Проголосовать: не нравится
          Дерективы компилятора вообще то можно изменять. Поэтому все Ваши шлагбаумы можно вполне обойти, отключив проверки на уровне JVM.
          • 13 лет назад, # ^ |
            Rev. 5   Проголосовать: нравится +8 Проголосовать: не нравится

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

            Очень прошу тех, кому хочется минусовать ДАННЫЙ пост, подтверждать своё минусование более развёрнутыми аргументами, что тут неправильно, указывая, сколько в своей жизни они написали проектов размером хотя бы 1000 строк.

        • 13 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится
          Где именно я не прав, считая, что когда неправильное действие вообще не пропускается компилятором, то это ещё более безопасно?

          Еще более безопасно вообще не запускать и не компилировать никакой код. А если серьезно, то что вы называете неправильным действием? Полностью исключить ошибки времени исполнения компилятор не сможет, он даже не всегда сможет понять используется какой-либо кусок кода программы или нет. Это следует из halting problem.
    • 13 лет назад, # ^ |
      Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится

      там после этой ошибки написано "перекомпилируйте с ключом xlint:unchecked чтобы посмотреть где происходят эти преобразования"

      Например, в этой строчке:
      ArrayList<Integer>[] graph = new ArrayList[N];

      То что женеричный массив нельзя создавать не означает что его надо создать без типа. Используйте вместо него ArrayList<ArrayList<Integer>> если уж очень хочется. Вообще если вам настоящий список нужен, то надо пользоваться LinkedList а не ArrayList, иначе всё можно было просто двумерным массивом представить и не мучаться.

      Вообще-то... Лучше уж либо с женериками разобраться, либо писать без них. Для олимпиадных задач этого бы хватило. ;-)

      UPD: Писать без них я имею в виду что не нужно обязательно параметризовать коллекции. Напишите

      ArrayList[] graph = new ArrayList[N];

      И всё будет нормально. В маленькой программе для личных потребностей это вполне допустимо.

      • 13 лет назад, # ^ |
          Проголосовать: нравится +8 Проголосовать: не нравится
        Меня интересуют не "маленькие программы для личных потребностей", а "как правильно" и "как промышленно".
        • 13 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится +5 Проголосовать: не нравится

          Ещё одно "промышленное замечание"

          Не стоит создавать ArrayList<Integer> ar = new ArrayList<Integer>(), а лучше

          List<Integer> ar = new ArrayList<Integer>();

          Причина в том, что этот список во втором случае можно будет создать разными способами, т.е. отделить абстракцию (List) от реализации ArrayList(). Но шаблоны лучше все равно использовать.

        • 13 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится
          "Правильно" и "Промышленно" однозначно использовать коллекцию вместо массива, если нужны параметризованные типы элементов. В массиве параметризация стирается. Особого влияния на производительность от этого нет из-за того что коллекции ультрамелких объектов для которых это важно почти не встречаются.

          Любые ситуации когда параметризация типов стирается предлагается старательно избегать за исключением случаев когда надо совместиться со старым (java 1.4) кодом.
      • 13 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится +8 Проголосовать: не нравится

        double post

    • 13 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
      ignore, spam
    • 13 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
      ignore, triple post
  • 13 лет назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится
    За разъяснение, что писать надо
    ArrayList<Integer>[] graph = new ArrayList[N];
    -- огромное спасибо, похоже, это на 99% закрывает вопрос.

    А насчёт остального текста сообщения -- что-то то ли плохо написано, то ли я плохо читаю, но сначала я его совершенно не понял. Особенно меня огорчило утверждение "
    Можно создать ArrayList<String> и присвоить его в ArrayList<Integer> и это будет компилироваться.".

    Но в том-то и дело, что при объявлении
    ArrayList<Integer>[] graph = new ArrayList[N];
    и дальнейшей попытке использования
    for(String s : (ArrayList<String>)graph[u])
    ошибка компиляции таки возникает, сообщая мне, что у меня бардак с типами. Что, насколько я понимаю, хорошо и правильно. А из Вашего сообщения я сначала понял в точности наоборот, и поначалу даже не проверил.
    • 13 лет назад, # ^ |
        Проголосовать: нравится +8 Проголосовать: не нравится
      Чтобы присвоить их при компиляции нужно это сделать через преобразование к Raw Type:

              List<Integer> listI = new LinkedList<Integer>();
              listI.add(7);
              List list = listI;
              List<String> listS = list;
              listS.add("Java");
              System.out.println(listS);

      При компиляции не будет ошибки, однако будет предупреждение которое можно увидеть с помощью -XLint:unchecked

      Это сделано для совместимости со старыми версиями (до 5-й Java).
  • 13 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +8 Проголосовать: не нравится
    double post
13 лет назад, # |
  Проголосовать: нравится +9 Проголосовать: не нравится
Я обычно реализовываю это как односвязный список, причем вручную.

class Edge { int from, to; Edge next;
public Edge(int from, int to) {this.from=from;this.to=to;next=first[from]; first[from]=this;}}
Edge[] first;

добавление: new Edge(a,b);
проход по списку: for (Edge e = first[x]; e!=null; e = e.next) System.out.println(e.to);
В некоторых случаях надо уметь удалять рёбра, но обычно это самые первые ребра и достаточно написать void remove() { first[from] = next; } 

В эту структуру граф читается быстрее чем в ArrayList[], из-за регулярного resize буфера в ArrayList-ах. Правда можно сначала узнать степень вершины а потом создавать ArrayList с нужным capacity, но в таком случае и двумерный массив сойдёт.
  • 13 лет назад, # ^ |
      Проголосовать: нравится -7 Проголосовать: не нравится
    хм... а LinkedList (опять же) чем не покатил. Он как раз для этого, вроде - без буфера...
    • 13 лет назад, # ^ |
        Проголосовать: нравится +1 Проголосовать: не нравится
      Конечно, можно и  LinkedList. Но я привык вот такое вот писать :)
      • 13 лет назад, # ^ |
          Проголосовать: нравится -7 Проголосовать: не нравится
        В душе я солидарен... Мне всегда так нравилось писать маленькие динамические структуры саому...

        Но "за такое" меня на одном собеседовании (в некое ЗАО Астротэк) два года назад не взяли. Именно спросили - "какого многоточия вы использовали самодельные списки вместо структур из STL (я тогда ещё на C++ собеседовался). Ни на что не намекаю, просто на всякий случай рассказываю... Хотя не единственная проблема была в моём решении... ;-)
        • 13 лет назад, # ^ |
            Проголосовать: нравится +2 Проголосовать: не нравится
          Да, дело в том что в промышленном коде за такое наказывают, ведь тем кто будет читать потом этот код надо быстро его понять, особо не напрягая извилины. Мне вот больше не хочется работать в большой команде из-за таких вещей.
        • 13 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

          даблпост

    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Сам обычно писал на плюсах vector<list<int> > но как-то недавно наслушался что vector<vector<int> > типа эффективнее. Сам такого правда не заметил, но почему-то поверил. В list-е ведь наверняка много памяти уходит на указатели, память наверно ж и теперь (а не только 15 лет назад) выделяется не побайтно а какими-то параграфами или ещё какими порциями, и т.д. 
      Впрочем, ArrayList или LinkedList, как мне кажется,  совершенно не влияет на суть основного вопроса.
  • 13 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    Чем всё-таки не устраивает массив ArrayList'ов ?
    Вроде операции с графом реализовывать проще.
    Насчёт перфоманса примерно одинаково, но вообще у списков смежности есть потенциальное преимущество в том, что смежные с вершиной рёбра идут последовательно в отличие от одного большого списка, где нет такого порядка.
    Если нужно полноценное удаление рёбер, то можно по-аналогии сделать массив HashSet'ов, если перфоманс не очень важен.
    Интересный вопрос - как сделать быструю структуру для добавления/удаления рёбер/вершин.

    • 13 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

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

      Кстати, сам когда-то придумал тренировочную задачу http://informatics.mccme.ru/moodle/mod/statements/view3.php?id=3311&chapterid=3490#1, заставляющую писать (на плюсах) либо vector<multiset<int> >  либо vector<map<int,int> >, а недавно попытался переписать на джаву (как Map<Integer,Integer>[]) -- так скорость работы падает даже не в несколько раз, а то ли во много десятков раз то ли асимптотически (0.688 сек где на плюсах 0.027), при том что чтение/вывод -- StreamTokenizer(FileReader), PrintWriter(File).

13 лет назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

А что на счет

class IntArray extends ArrayList<Integer>{}

IntArray ar[] = new IntArray[10];

?



  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Генерики сделаны таким образом, чтобы не пришлось создавать по отдельному классу на каждый параметр. Но в нашем случае отдельный класс - это нормальное решение, по нему никаких unchecked быть не может. 
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Генерики созданы таким образом, чтобы интегрировать уже имеющуюся Java к потребностям жизни. Необходимость type-cast-ов в коллекциях, мягко скажем, утомляла. Templates в С++ намного более разумное и точное решение.
      • 13 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Более разумное и точное решение требует создания дополнительного бинарного кода при использовании каких-то новых параметров, а ещё, если не дай бог чего не то вставишь, ошибки компиляции будут возникать прямо в template-е , а не там, где этот параметр указан.
        • 13 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится
          Ошибки компиляции шаблона студия, например, разворачивает вплоть до вызова шаблона.
          Появление лишнего кода - проблема? выкидываем все паттерны, они создают лишний исходник!
          Зато все проверки времени компиляции осуществляются во время компиляции, а не когда программа повалится с рантаймом.
          • 13 лет назад, # ^ |
              Проголосовать: нравится +3 Проголосовать: не нравится
            Появление лишнего кода еще какая проблема. В java модульность на уровне классов, classloader-ы и все дела. Представь что бы было если бы пришлось подгружать сотню версий разных списков? А ещё потом подгрузил какой-то левый класс (один из модов для игры например), а там ещё и 101-ый оказался нужен.

            И да, понятно что у каждого из этих двух подходов свои плюсы и минусы.
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Ещё на generic-и подходят для эмуляции некоторых элементов функционального программирования. Это конечно немного через задницу, но надеюсь что динамические языки на JVM, в которых это всё нормально реализовано, сильно рванут вперёд.
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Что интересно: мне кажется, что генерики в джава могут быть при модификации структуры байт-кода реализованы вплоть до полной неотличимости от templates с параметрами - классами в С++.

              Заметим следующие факты:
              1) Без использования специализации шаблона в Java весь код шаблона может быть реализован одним бинарником. Никто не мешает рассматривать HashSet<Integer> и HashSet<String> как разные классы с общим объектным кодом.

              2) Использование специализации шаблона можно рассматривать как создание дополнительной версии функции/класса.

              Таким образом, компилятор не будет генерировать "лишний" бинарник, т.е. размер бинарника будет O(1)*размер написанного пользователем кода.

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

              Я что-то упускаю?
              • 13 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                И да, полностью согласен с тем, что у каждого подхода есть свои плюсы и минусы. Согласен с тем, что более или менее серьезное использование шаблонов в С++ на практике занятие с крайне высоким порогом вхождения, например. Однако, считаю, что многие вещи в Java, в частности, генерики, в принципе не доработаны выше уровня "сократим один оператор в коде".
13 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится
Вот тут мне кинули интересную штуку:
есть public class A<B>{<C>A(C d){}};
Как вызвать конструктор, явно указав параметры генериков?
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    new A<Thread>("blabla");

    Тип C определён параметром конструктора (String) а тип B указан явно (Thread)... Или что имелось в виду сделать?
    • 13 лет назад, # ^ |
      Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится

      А если ты хочешь чтобы C был не String а просто Object, но подать на вход надо String?

      • 13 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится +8 Проголосовать: не нравится

        Дык покастить к объекту прямо там?
        new A<Thread>((Object) "blabla");

        UPD: Сорри, я это написал к первой версии коммента но из-за тормозов оно проявилось только сейчас... ;-)

        Да ведь C к женерикам не имеет отношения - это типовой параметр метода.
        • 13 лет назад, # ^ |
            Проголосовать: нравится +3 Проголосовать: не нравится
          По-моему подойдёт, не знаю как проверить. Но правильный ответ
          new <C> A<B>(d);
          • 13 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Хм. А не оказался ли я втянут в софистику. Ведь тип C присутствует только во время компиляции (для проверки валидности вызовов). Мы от него не можем создать объект или получить класс во время выполнения. Только имена... Если бы это был метод, возвращающий объект типа C, то он бы проверился при присвоении (опять же во время компиляции)... Но тогда предложенный способ с указанием <Object> перед вызовом по-моему не сработал бы, только кастинг... (чувствую себя неуверенно и мутно ;-)

            // печатает 4 раза "C"
            public class A<B>{
               
                public <C>A(C d){
                    System.out.println(getClass().getConstructors()[0]
                        .getTypeParameters()[0]);
                } // A

                public static void main(String... args) {
                    A a = new A<Thread>("blabla");
                    new <String> A<Thread>("blabla");
                    new A<Thread>((Object) "blabla");
                    new <Object> A<Thread>("blabla");
                } // main
            };
            //ядлиннаястрочкаприветядлиннаястрочкаприветядлиннаястрочкаура
13 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

На всякий случай: я проминусовал только самый первый пост ((user:anonymous)) . Остальное не я.

  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    У меня написан GreaseMonkey-скрипт, который, в частности, полностью скрывает количество голосов около сообщения. Так что у меня лично никаких претензий нет и быть не может :)
»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
ArrayList<Integer>[] list = new ArrayList[n];
for (int i = 0; i<n; i++)
 list[i] = new ArrayList<Integer>();
  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    Type safety: The expression of type ArrayList[] needs unchecked conversion to conform to ArrayList[]

»
12 лет назад, # |
  Проголосовать: нравится +17 Проголосовать: не нравится

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

То есть я бы писал таки

@SuppressWarnings("unchecked")
List<Integer> lists = new List[n];
for (int i = 0; i < n; i++)
    lists[i] = new ArrayList<Integer>();
  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +5 Проголосовать: не нравится

    С другой стороны, могут оказаться нужными методы конкретной реализации...