Как считать повторяющиеся слова?

Учитывая файл размером 1 ГБ (очень большой), содержащий слова (некоторые из них повторяются), нам нужно прочитать файл и вывести, сколько раз повторяется каждое слово. Пожалуйста, дайте мне знать, является ли мое решение высокопроизводительным или нет.

(Для простоты предположим, что мы уже записали слова в arraylist<string>)

Я думаю, что большой O (n) - это «n». Я прав??

public static void main(String[] args) {

            ArrayList al = new ArrayList();
            al.add("math1");
            al.add("raj1");
            al.add("raj2");
            al.add("math");
            al.add("rj2");

            al.add("math");
            al.add("rj3");
            al.add("math2");
            al.add("rj1");
            al.add("is");
            Map<String,Integer> map= new HashMap<String,Integer>();

            for (int i=0;i<al.size();i++)
            {
                String s= (String)al.get(i);

                    map.put(s,null);

            }
            for (int i=0;i<al.size();i++)
            {
                String s= (String)al.get(i);
                if(map.get(s)==null)
                    map.put(s,1);
                else
                {
                    int count =(int)map.get(s);
                        count=count+1;
                        map.put(s,count);
                }


            }

            System.out.println("");
        }

person user830818    schedule 23.07.2011    source источник
comment
У вас еще есть возможности улучшить производительность.   -  person hatchet - done with SOverflow    schedule 24.07.2011
comment
Это домашнее задание? meta.stackexchange.com /вопросы/10811/   -  person Alex Churchill    schedule 24.07.2011
comment
@alex c: это не домашняя работа. Это вопрос интервью. Дайте нам знать, если мое решение эффективно или нет   -  person user830818    schedule 24.07.2011
comment
для сопоставления записей почти все с -›int отстой. В этом конкретном случае вам лучше использовать w/int[1] или любую ссылку на int (например, AtomicInteger, но запись стоит совсем немного)   -  person bestsss    schedule 25.07.2011


Ответы (5)


Теоретически, поскольку доступ к HashMap обычно равен O (1), я предполагаю, что ваш алгоритм равен O (n), но на самом деле он имеет несколько недостатков. В идеале вы должны перебирать содержимое файла только один раз, обрабатывая (т.е. подсчитывая) слова, пока вы их читаете. Нет необходимости хранить все содержимое файла в памяти (ваш ArrayList). Вы перебираете содержимое три раза: один раз, чтобы прочитать его, а второй и третий раз в двух циклах кода выше. В частности, первый цикл в приведенном выше коде совершенно не нужен. Наконец, использование HashMap будет медленнее, чем необходимо, потому что размер по умолчанию при построении очень мал, и ему придется несколько раз увеличиваться внутри, заставляя каждый раз перестраивать хеш-таблицу. Лучше начать с размера, соответствующего тому, что вы ожидаете от него. Вы также должны учитывать коэффициент нагрузки.

person hatchet - done with SOverflow    schedule 24.07.2011
comment
прочитать каждую строку файла и сделать следующее (при условии, что каждая строка содержит одно слово) //String s= текущая строка в файле; если(!map.containsKey(s)) map.put(s,1); else { int count =(int)map.get(s); количество = количество + 1; map.put(s,количество); } Но вопрос только в том, что я не знаю, как оценить начальный размер хэш-карты. Размер файла является переменным (я сказал только 1 ГБ, чтобы сообщить, что файл очень большой) - person user830818; 25.07.2011
comment
это намного лучше. Вы можете попытаться рассчитать его на основе размера файла, разумной средней длины слова и предположения о количестве дубликатов. Но, наверное, не стоит заморачиваться. Я думаю, что HashMap начинается с 16 и удваивается при каждом росте. Если бы вы начали с чего-то вроде 4096 или 8192, вы бы пропустили первую партию роста и попали бы в диапазон серьезных размеров. - person hatchet - done with SOverflow; 25.07.2011

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

Пища для размышлений о решении для хэш-карт

Ваш ответ приемлем, но учтите следующее: для простоты предположим, что вы читаете файл по одному байту за раз в StringBuffer, пока не нажмете пробел. В этот момент вы вызовете toString() для преобразования StringBuffer в строку. Затем вы проверяете, находится ли строка в HashMap, и либо она сохраняется, либо счетчик увеличивается.

Английский дик. входящий в состав Linux, содержит 400 тыс. слов и имеет размер около 5 МБ. Таким образом, мы можем предположить, что из «1 ГБ» текста, который вы прочитали, вы будете хранить только около 5 МБ в своей HashMap. Остальная часть файла будет преобразована в строки, которые нужно будет удалить после того, как вы закончите их поиск на карте. Я могу ошибаться, но я считаю, что байты будут повторяться снова во время построения строки, поскольку массив байтов необходимо копировать внутри и снова для вычисления HashCode. Таким образом, решение может тратить изрядное количество циклов ЦП и заставлять GC выполняться часто.

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

Я могу рассмотреть возможность использования собственной RadixTree или структуры, подобной Trie.

Помните, как работает метод вставки RadixT/Trie. Который должен взять поток символов/байтов (обычно строку) и сравнить каждый элемент с текущей позицией в дереве. Если префикс существует, он просто продвигается вниз по дереву и потоку байтов на шаге блокировки. Когда он достигает нового суффикса, он начинает добавлять узлы в дерево. Как только достигается конец потока, он помечает этот узел как EOW. Теперь представьте, что мы могли бы сделать то же самое при чтении гораздо большего потока, сбрасывая текущую позицию в корень дерева каждый раз, когда мы нажимаем пробел.

Если бы мы написали собственное дерево Radix (или, может быть, Trie), узлы которого имели бы счетчики конца слова (вместо маркеров) и метод вставки считывался непосредственно из файла. Мы могли бы вставлять узлы в дерево по одному байту/символу за раз, пока не прочитаем пробел. В этот момент метод вставки будет увеличивать счетчик конца слова (если это существующее слово) и сбрасывать текущую позицию в дереве обратно в начало и снова начинать вставлять байты/символы. Принцип работы поразрядного дерева заключается в сворачивании повторяющихся префиксов слов. Например:

The following file:

math1 raj1 raj2 math rj2 math rj3 

would be converted to:

(root)-math->1->(eow=1)
     |    |-(eow=2)
     |    
      raj->1->(eow=1)
      | |->2->(eow=1)
      | |->3->(eow=1)
      j2->(eow=1)

Время вставки в такое дерево будет равно O(k), где k — длина самого длинного слова. Но так как мы вставляем/сравниваем по мере чтения каждого байта. Мы не более неэффективны, чем просто чтение файла, как мы уже должны.

Кроме того, обратите внимание, что мы будем считывать байты во временный байт, который будет переменной стека, поэтому единственный раз, когда нам нужно выделить память из кучи, это когда мы сталкиваемся с новым словом (фактически новым суффиксом). Следовательно, сборка мусора не будет происходить так часто. И общая память, используемая деревом Radix, будет намного меньше, чем HashMap.

person eSniff    schedule 25.07.2011

Вы рассматривали возможность использования решения mapreduce? Если набор данных становится больше, то действительно было бы лучше разделить его на части и считать слова параллельно.

person Bhavana C    schedule 01.12.2011

Вы должны прочитать файл со словами только один раз.

Не нужно заранее ставить нули — это можно сделать в основном цикле.

Сложность действительно O(n) в обоих случаях, но вы хотите сделать константу как можно меньше. (О(n) = 1000 * O(n), верно :) )

person Petar Ivanov    schedule 24.07.2011

Чтобы ответить на ваш вопрос, во-первых, вам нужно понять, как работает HashMap. Он состоит из сегментов, и каждый сегмент представляет собой связанный список. Если из-за хеширования другая пара должна занять то же самое ведро, она будет добавлена ​​в конец связанного списка. Таким образом, если карта имеет высокий коэффициент загрузки, поиск и вставка больше не будут O (1), и алгоритм станет неэффективным. Более того, если коэффициент загрузки карты превышает заданный коэффициент загрузки (по умолчанию 0,75), вся карта будет перехэширована.

Это выдержка из JavaDoc http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html:

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

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

Map<String,Integer> map= new HashMap<String,Integer>(al.size());

Без этого ваше решение недостаточно эффективно, хотя и имеет линейную аппроксимацию O(3n), так как из-за амортизации перехеширования вставка элементов будет стоить 3n вместо n.

person Nulldevice    schedule 24.07.2011
comment
new HashMap‹String,Integer›(al.size()); к сожалению, конструкция не годится, в основном вам нужно al.size()/4*3, а затем фактический размер соответствует размеру pow2 Объект[] - person bestsss; 25.07.2011
comment
Я не согласен. API говорит, что конструктор будет учитывать коэффициент загрузки (по умолчанию 3/4). Но если честно - это всего лишь оценка, так что все значения C*n для начальной емкости теоретически будут хорошими. 1/2 размера будет иметь почти такую ​​же производительность, как 1/1 или 4/3. - person Nulldevice; 25.07.2011
comment
не знаю, что говорится в документе или он был обновлен, хэш-карта (которая, кстати, является плохой хэш-таблицей) использует массив корзин pow2, размер которого равен pow2 ›= initialCapacity, а максимальное количество записей равно емкости * loadFactor (по умолчанию .75f). Таким образом, если вы указываете 33-63 для начальной емкости, максимальное количество элементов без изменения размера, которые может содержать карта, составляет 48. - person bestsss; 25.07.2011