У меня есть рекурсивный алгоритм, который просматривает строку, символ за символом, и анализирует ее, чтобы создать древовидную структуру. Я хочу иметь возможность отслеживать индекс символа, на котором в настоящее время находится синтаксический анализатор (для сообщений об ошибках, как и для всего остального), но я не заинтересован в реализации чего-то вроде кортежа для обработки нескольких возвращаемых типов.
Я пробовал использовать целочисленный тип, объявленный вне метода и переданный в рекурсивный метод, но поскольку он окончательный, приращения рекурсивного вызова «забываются», когда я возвращаюсь. (Поскольку приращение значения Integer делает ссылку объекта, передаваемого по значению, на новый объект)
Есть ли способ заставить что-то подобное работать, которое не загрязняет мой код?




Это своего рода взлом, но иногда я использую изменяемый AtomicInteger, чтобы делать такие вещи. Я также видел случаи, когда передается int [] размера 1.
Текущее решение, которое я использую:
int[] counter = {0};
а затем передать его рекурсивному алгоритму:
public List<Thing> doIt (String aString, int[] counter) { ... }
и когда я хочу увеличить его:
counter[0]++;
Не супер элегантно, но работает ...
Целые числа неизменяемы, что означает, что когда вы передаете их в качестве аргумента, они создают копию, а не ссылку на тот же элемент. (объяснение).
Чтобы получить желаемое поведение, вы можете написать свой собственный класс, подобный Integer, только изменяемый. Затем просто передайте его рекурсивной функции, он увеличивается в пределах рекурсии, и когда вы снова обращаетесь к нему после завершения рекурсии, он все равно сохранит свои новые значения.
Обновлено: обратите внимание, что использование массива int [] является разновидностью этого метода ... В Java массивы также передаются по ссылке, а не копируются, как примитивы или неизменяемые классы.
Поскольку вы уже обнаружили псевдоизменяемый целочисленный «взлом», как насчет этой опции:
Есть ли смысл делать отдельный класс Parser? Если вы сделаете это, вы можете сохранить текущее состояние в переменной-члене. Вероятно, вам нужно подумать о том, как вы собираетесь обрабатывать любые проблемы безопасности потоков, и это может быть излишним для этого конкретного приложения, но может сработать для вас.
Честно говоря, я бы перекодировал функцию, чтобы сделать ее линейным алгоритмом, использующим цикл. Таким образом, у вас не будет шансов закончиться нехваткой места в куче, если вы перейдете через очень большую строку. Кроме того, вам не понадобится дополнительный параметр, чтобы отслеживать счетчик.
Это также, вероятно, приведет к ускорению алгоритма, поскольку ему не нужно выполнять вызов функции для каждого символа.
Если, конечно, нет особой причины, по которой он должен быть рекурсивным.
Одна из возможностей, о которой я могу думать, - это сохранить счетчик в переменной-члене класса. Это, конечно, предполагает, что общедоступный метод doIt вызывается только одним потоком.
Другой вариант - рефакторинг общедоступного метода для вызова частного вспомогательного метода. Частный метод принимает список в качестве параметра и возвращает счетчик. Например:
public List<Thing> doIt(String aString) {
List<Thing> list = new ArrayList<Thing>();
int count = doItHelper(aString, list, 0);
// ...
return list;
}
private int doItHelper(String aString, List<Thing> list, int count) {
// ...
// do something that updates count
count = doItHelper(aString, list, count);
// ...
return count;
}
Это предполагает, что вы можете выполнять обработку ошибок в общедоступном методе doIt, поскольку переменная count фактически не передается обратно вызывающей стороне. Если вам нужно это сделать, вы, конечно, можете выбросить исключение:
public List<Thing> doIt(String aString) throws SomeCustomException {
List<Thing> list = new ArrayList<Thing>();
int count = doItHelper(aString, list, 0);
// ...
if (someErrorOccurred) {
throw new SomeCustomException("Error occurred at chracter index " + count, count);
}
return list;
}
Трудно понять, поможет ли это, не зная больше о том, как на самом деле работает ваш алгоритм.
Вы можете просто использовать статическую переменную класса int, которая увеличивается каждый раз, когда вызывается ваш метод doIt.
Вы также можете:
private int recurse (int i) {
if (someConditionkeepOnGoing) {
i = recurse(i+1);
}
return i;
}
Если в функции более одного рекурсивного вызова и предыдущий счетчик равен 1, оба новых вызова функции получат аргумент 2, тогда как один из них должен получить 3, поскольку это уже третье действие.
Правда, не ответ, а немного сочувствия: :-) Пару месяцев назад я столкнулся с похожей ситуацией. Поскольку я использовал Common Lisp, я мог просто объявить свой счетчик «специальным» (т.е. с динамической областью видимости вместо лексической), но я помню, как думал: «Практически на любом другом языке для этого потребуется намного больше, чем 1 строка кода. решать".