Ответ 1
Используйте JChronic
Вы можете использовать DateParser2 из пакета edu.mit.broad.genome.utils.
Мне нужно знать строки дат. Неважно, не могу ли я различать месяц и дату (например, 12/12/10), мне просто нужно классифицировать строку как дату, а не преобразовывать ее в объект Date. Итак, это действительно классификация, а не проблема синтаксического анализа.
У меня будут фрагменты текста, например:
"bla bla bla bla 12 января 09 bla bla bla 01/04/10 bla bla bla"
и я должен иметь возможность распознавать начальную и конечную границы для каждой строки даты внутри.
Мне было интересно, знает ли кто-нибудь о каких-либо java-библиотеках, которые могут это сделать. Мой google-fu пока ничего не придумал.
ОБНОВЛЕНИЕ: Мне нужно уметь распознавать как можно более широкий набор способов представления дат. Конечно, наивным решением может быть запись оператора if для каждого мыслимого формата, но подход распознавания образов с обученной моделью идеально подходит для меня.
Используйте JChronic
Вы можете использовать DateParser2 из пакета edu.mit.broad.genome.utils.
Вы можете закодировать все доступные форматы дат в Java:
for (Locale locale : DateFormat.getAvailableLocales()) {
for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style ++) {
DateFormat df = DateFormat.getDateInstance(style, locale);
try {
df.parse(dateString);
// either return "true", or return the Date obtained Date object
} catch (ParseException ex) {
continue; // unperasable, try the next one
}
}
}
Это, однако, не учитывает какие-либо пользовательские форматы даты.
Правила, которые могут помочь вам в вашем квесте:
Jan
или January
. Во время поиска он должен быть нечувствительным к регистру, потому что fEBruaRy также является месяцем, хотя человек, печатающий его, должен был быть пьян. Если вы планируете искать неанглийские месяцы, необходима база данных, потому что никакая эвристика не обнаружит, что "Wrzesień" польский для сентября.0*
, где * может быть 1-9.{-,_, ,:,/,\,.,','}
, но возможно, что * представляет собой комбинацию из 2 или 3 элементов упомянутого множества. Еще раз, вы должны выбрать приемлемые разделители. 10? 20? 1999 может быть подходящей датой для кого-то с странным чувством элегантности. 10/20/1999 также может быть действительной датой, но 10_/20_/1999 будет очень странным.Я думаю, этого достаточно для "наивной" классификации, специалист по лингвистам может помочь вам больше.
Теперь, идея для вашего алгоритма. Скорость не имеет значения. Могут быть несколько проходов по одной и той же строке. Оптимизируйте, когда это начнет иметь значение. Если вы сомневаетесь в том, что вы нашли строку даты, сохраните ее где-нибудь "безопасно" в ListOfPossibleDates
и выполните экзамен еще раз, с более жесткими правилами с использованием комбинаций от 1. до 8. Когда вы считаете, что строка даты действительна, отправьте его в класс Date
, чтобы убедиться, что он действительно действителен. 32 марта 1999 года недействительно, когда вы конвертируете его в формат, который будет понимать Date
.
Один важный повторяющийся шаблон - lookbehind и lookaround. Когда вы считаете, что действительная сущность (день, месяц, год) найдена, вам нужно будет увидеть, что лежит за и после. Здесь может помочь механизм или рекурсия на основе стека.
Шаги:
Поскольку существует буквально множество возможностей, вы не сможете их поймать. Как только вы нашли образец, который, по вашему мнению, может произойти еще раз, сохраните его где-нибудь, и вы можете использовать его в качестве регулярного выражения для передачи других строк.
Возьмем ваш пример, "bla bla bla bla 12 Jan 09 bla bla bla 01/04/10 bla bla bla"
. После того, как вы извлечете первую дату, 12 Jan 09
, затем используйте оставшуюся часть этой строки ("bla bla bla 01/04/10 bla bla bla"
) и повторите все вышеописанные шаги еще раз. Таким образом, вы будете уверены, что ничего не пропустите.
Я надеюсь, что эти предложения будут, по крайней мере, полезными. Если не существует библиотеки для всех этих грязных (и более) шагов для вас, то у вас впереди трудная дорога. Удачи!
Я сделал это с огромным регулярным выражением (самостоятельно созданным):
public static final String DATE_REGEX = "\b([0-9]{1,2} ?([\\-/\\\\] ?[0-9]{1,2} ?| (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?)([\\-/\\\\]? ?('?[0-9]{2}|[0-9]{4}))?)\b";
public static final Pattern DATE_PATTERN = Pattern.compile(DATE_REGEX, Pattern.CASE_INSENSITIVE); // Case insensitive is to match also "mar" and not only "Mar" for March
public static boolean containsDate(String str)
{
Matcher matcher = pattern.matcher(str);
return matcher.matches();
}
Это соответствует следующим датам:
06 Sep 2010
12-5-2005
07 Mar 95
30 DEC '99
11\9\2001
И не это:
444/11/11
bla11/11/11
11/11/11blah
Он также соответствует датам между символами типа []
, ()
, ,
:
Yesterday (6 nov 2010)
Он соответствует датам без года:
Yesterday, 6 nov, was a rainy day...
Но он соответствует:
86-44/1234
00-00-0000
11\11/11
И это больше не похоже на дату. Но это то, что вы можете решить, проверяя, являются ли цифры возможными значениями за месяц, день, год.
Очень хороший синтаксический анализатор даты в java Natty, вы можете попробовать его здесь
Я уверен, что исследователи в извлечении информации рассматривали эту проблему, но я не мог найти бумагу.
Одна вещь, которую вы можете попробовать - сделать это как двухэтапный процесс. (1) после сбора как можно большего количества данных, извлеките функции, некоторые функции, которые приходят на ум: количество чисел, которые появляются в строке, количество чисел от 1-31, которые появляются в строке, количество чисел из 1- 12, которые появляются в строке, количество месяцев имен, которые появляются в строке, и так далее. (2) изучать функции с использованием какого-либо типа метода двоичной классификации (например, SVM) и, наконец, (3) при появлении новой строки, извлекать функции и запрашивать SVM для прогнозирования.
Вот простой пример natty:
import com.joestelmach.natty.*;
List<Date> dates =new Parser().parse("Start date 11/30/2013 , end date Friday, Sept. 7, 2013").get(0).getDates();
System.out.println(dates.get(0));
System.out.println(dates.get(1));
//output:
//Sat Nov 30 11:14:30 BDT 2013
//Sat Sep 07 11:14:30 BDT 2013
Может быть, вы должны использовать регулярные выражения?
Надеюсь, этот будет работать в формате mm-dd-yyyy:
^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$
Здесь (0[1-9]|1[012])
соответствует месяцу 00..12, (0[1-9]|[12][0-9]|3[01])
соответствует дате 00..31 и (19|20)\d\d
соответствует году.
Поля могут быть отброшены тире, косой чертой или точкой.
С уважением, Serge
Практически невозможно распознать все возможные форматы даты в виде дат, используя "стандартные" алгоритмы. Это просто потому, что их так много.
Мы, люди, способны это делать только потому, что узнали, что что-то вроде 2010-03-31 напоминает дату. Другими словами, я бы предложил использовать алгоритмы машинного обучения и научить вашу программу распознавать правильные последовательности дат. Google Prediction API, который должен быть осуществим.
Или вы можете использовать регулярные выражения, как было предложено выше, для обнаружения некоторых, но не всех форматов даты.
Что бы я сделал, это искать характеристики даты, а не сами даты. Например, вы можете искать косые черты (для получения дат формы 1/1/1001), тире (1 - 1 - 1001), названиях месяцев и аббревиатурах (1 января 1001 или 1 января 1001 года). Когда вы получаете хит для них, соберите близлежащие слова (2 с каждой стороны должно быть хорошо) и сохраните это в массиве строк. После того, как вы проверили весь вход, проверьте этот массив строк с помощью функции, которая будет немного глубже и вытащить строки фактической даты, используя найденные здесь методы. Важно то, что общие даты доходят до уровня управления.
Обычно даты являются символами, разделенными обратной или прямой косой чертой или тире. Вы считали регулярное выражение?
Я предполагаю, что вы не хотите классифицировать даты типа Sunday, October 3rd 2010 и т.д.
Я не знаю ни одной библиотеки, которая могла бы это сделать, но написать собственное не было бы невероятно сложно. Предполагая, что ваши даты отформатированы с помощью слэшей типа 12/12/12
, тогда вы можете убедиться, что у вас есть три "\". Вы можете получить еще больше технических средств и проверить его значения между косой чертой. Например, если у вас есть:
30/12/10
Тогда вы знаете, что 30 дней, а 12 - месяц. Однако, если вы получаете 30/30/10, вы знаете, что даже если ti имеет правильный формат, он не может быть датой, потому что нет "30" месяцев.
Я не знаю ни одной библиотеки, которая тоже это делает. Я бы предложил сочетание вложенных рекурсивных функций и регулярных выражений (много), чтобы соответствовать строкам и попытаться придумать лучшее предположение, чтобы увидеть, может ли это быть датой. Даты могут быть написаны по-разному, некоторые могут написать их как "Воскресенье, 3 октября 2010" или "Воскресенье, 3 октября 2010" или "10/03/2010" или "10/3/2010" и целую кучу разных способов (даже если вы рассматриваете даты на других языках/культурах).
Вы всегда можете проверить, есть ли в строке два символа '/'.
public static boolean isDate(){
String date = "12/25/2010";
int counter = 0;
for(int i=0; i<date.length(); i++){
if ("\/-.".indexOf(date.charAt(i)) != -1) //Any symbol can be used.
counter++;
}
if(counter == 2) //If there are two symbols in the string,
return true; //Return true.
else
return false;
}
Вы можете сделать что-то похожее, чтобы проверить, является ли все остальное целым.