Стек

Автор karpo518, 17 января 2017, 10:21

0 Пользователи и 1 гость просматривают эту тему.

karpo518

Добрый день. Потребовался функционал стека для массива строк. Реализовать самому не позволяет незнание некоторых моментов языка. К примеру, когда нужно и вернуть значение, и удалить его из массива(при извлечении из стека), не вижу другого способа реализации, кроме как использование указателей. Но для меня до сих пор остается загадкой, существуют ли в версии VB для OO указатели и как с ними работать. Если у кого-нибудь завалялась реализация стека, поделитесь, пожалуйста.
Linux Mint 18 (64 bit),  LibreOffice 5.1.6.2

mikekaganski

#1
С уважением,
Михаил Каганский

karpo518

mikekaganski, спасибо за ответ. В данный момент мне непонятно, как функция, извлекающая из стека значение, может одновременно вернуть это значение и удалить его из массива. Если извлечённое значение будет возвращено самой функцией, то как из функции получить доступ к массиву, в котором хранятся данные, чтобы удалить извлекаемое значение?
Linux Mint 18 (64 bit),  LibreOffice 5.1.6.2

mikekaganski

sub push(stack, val)
  dim lo as integer, hi as integer
  lo = LBound(stack)
  hi = UBound(stack)
  ReDim Preserve stack(lo to hi+1)
  stack(hi+1) = val
end sub

function pop(stack) as variant
  dim lo as integer, hi as integer
  lo = LBound(stack)
  hi = UBound(stack)
  pop = stack(hi)
  ReDim Preserve stack(lo to hi-1)
end function


sub tst
  dim stack(0) As Integer
  for i = 0 to 5
    push(stack, i)
  next

  for i = 0 to 5
   j = pop(stack)
   print j
  next 
 
end sub
С уважением,
Михаил Каганский

JohnSUN

#4
+
Только я бы в pop добавил проверку на hi>=lo... Ну, на случай, если стек уже пуст
И не уверен, что ReDim подходит для удаления единственного (последнего) элемента.
Возможно, просто присвоить пустой Array() было бы проще (но это может изменить типизированный массив на Variant).
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

mikekaganski

В данном случае подходит.
Я специально не делал проверку, поскольку мне больше импонирует перехват ошибки с помощью On Error ...
В стеке изначально выделен защитный первый элемент, который позволит успешно вызывать pop для всех push, но вызовет "Inadmissible value or data type. Index out of defined range." для лишнего.
С уважением,
Михаил Каганский

JohnSUN

Я имел в виду нечто вроде такого
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

mikekaganski

:) Именно.
Я ни в коем случае не спорю, что Ваша твоя (не сочти за фамильярность - использую это местоимение как знак глубочайшего уважения, зная твоё к этому отношение) трактовка верна.

Просто в реальном коде у меня это было бы обрамлено в On Error Goto Handler, поскольку для меня (любителя C++ и структурной обработки ошибок) факт вызова лишнего pop - это признак ошибки в коде, а ошибку надо (мне надо :) ) обрабатывать отдельно... :)
С уважением,
Михаил Каганский

JohnSUN

А я в свою очередь не спорю с твоим решением - я же уже плюсанул!  :beer:

Просто недавно пришлось пободаться именно с ситуацией, когда функция должна была возвращать массив, но для некоторых данных результата просто не было. Возвращать Nothing и анализировать результат на IsEmpty/IsNull было скучно, выдумывать способ бросить исключение (а потом обрабатывать его в On Error) - ещё скучнее.

Взял на вооружение приём из Standard-Tools-UCB-ReadDirectories
If CurIndex > -1 Then
ReDim Preserve sFileArray(CurIndex,1) as String
Else
ReDim sFileArray() as String
End If
Просто и понятно... А вызывающая процедура как крутила цикл от LBound до UBound, так и крутит - ни дополнительных проверок не надо, ни обработки ошибок...
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

karpo518

Спасибо за ответы. JohnSUN, я не использую обработку исключений. Покажите, пожалуйста, полный вариант кода с учетом вашего совета по дополнительной проверке параметра.
Также смущает 2 момента:

1. pop - function, а push - sub. Это сделано, чтобы в push была доступен для изменения массив stack? Если это так, то нет ли другого способа изменить массив? Не хотелось бы для внутренних процедур использовать оператор sub.

2. функция push не возвращает значение, а должна, как мне кажется, возвращать, например, новый размер стека.

Не сочтите за грубость. Просто я почти не имею опыта в VB, поэтому хочу получить для себя стабильный код, но сам модифицировать его не решаюсь.
Linux Mint 18 (64 bit),  LibreOffice 5.1.6.2

JohnSUN

Цитата: karpo518 от 30 января 2017, 13:18
Покажите, пожалуйста, полный вариант кода с учетом вашего совета по дополнительной проверке параметра.
Думаю, как-то так:
REM  *****  BASIC  *****
Option Explicit
Option Base  0

Rem Глобальное объявление массива для стека позволит не указывать
Rem его в качестве параметров функций push и pop
Dim str_stack() As String

Function push(val As String) As Long
Dim lo As Integer, hi As Integer
lo = LBound(str_stack)
hi = UBound(str_stack)+1
ReDim Preserve str_stack(lo to hi)
str_stack(hi) = val
push = hi - lo + 1
End Function

Function pop(Optional elemCount As Long) As Variant
Dim lo As Integer, hi As Integer
lo = LBound(str_stack)
hi = UBound(str_stack)
If Not IsMissing(elemCount) Then elemCount = 0
If lo = hi Then ' Сейчас стек лишится последнего элемента
pop = str_stack(hi)
ReDim str_stack() As String ' Теперь LBound(str_stack)>UBound(str_stack) - массив пуст
ElseIf lo > hi Then ' Стек уже пуст
pop = empty ' Нужно вернуть что-то, что скажет, что стек пуст,
' но не станет вызывать ошибку выполнения. Empty будет трактоваться как string "", а Null вызовет ошибку
Else
pop = str_stack(hi)
ReDim Preserve str_stack(lo to hi-1)
If Not IsMissing(elemCount) Then elemCount = hi - lo
EndIf
End Function

Sub tst
Dim i As Long, j As Long
Dim s As String
For i = 0 To 4
s = "Элемент " + i
Print "После добавления '" + s + "' размер стека " + push(s)
Next

s = pop(j)
Print "Снят верхний '" + s + "', осталось " + j + " элементов"
While j > 0 
Print "Снят '" + pop(j) + "'"
Wend

s = pop(j)
Print "Попытка чтения из стека c " + j + " элементами возвращает '" + pop() + "', что в виде строки выглядит как '" + s + "'"
End Sub

Цитата: karpo518 от 30 января 2017, 13:18
Также смущает 2 момента:
1. pop - function, а push - sub. Это сделано, чтобы в push была доступен для изменения массив stack? Если это так, то нет ли другого способа изменить массив?
Сделал функцией
Цитата: karpo518 от 30 января 2017, 13:18Не хотелось бы для внутренних процедур использовать оператор sub.
Причина?
Цитата: karpo518 от 30 января 2017, 13:18
2. функция push не возвращает значение, а должна, как мне кажется, возвращать, например, новый размер стека.
Должна - значит вернет. Но тогда вроде как pop получится слишком "молчаливым"... Пусть он тоже рассказывает об оставшемся количестве элементов
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

karpo518

JohnSUN, cпасибо за развернутый ответ.

ЦитироватьНе хотелось бы для внутренних процедур использовать оператор sub. Причина?

Как я понял в sub запихивают самодостаточные модули для назначения кнопкам и прочего публичного использования. Всё остальное, включая функционал для внутреннего использования в макросе, следует засовывать в функции. Поправьте, если не прав.
Linux Mint 18 (64 bit),  LibreOffice 5.1.6.2

JohnSUN

Извини, что долго не отвечал - старался подобрать формулировки, которые бы не очень сильно обидели того человека, который внушил тебе такое видение. Ответ получался или слишком лаконичным ("Нет, это не так"), или слишком длинным (и нецензурным).

Начнем с того, что в функцию тоже "запихивают самодостаточные" фрагменты кода. И даже используют эти функции в ячейках электронных таблиц. (Не "модули" - давай договоримся, что "модуль" это такая закладочка в редакторе макросов, в нем обычно может быть много и функций, и процедур).

С точки зрения компьютера разницы между функциями и процедурами нет. Например, процедуру можно считать функцией, которая возвращает специальное значение void, от которого программисту никакого особого проку нет.

Обычно функция что-то делает и возвращает одно (если программист не захотел сделать иначе) значение. Когда функция закончит работу, это значение будет лежать в переменной с точно таким же именем, что и имя самой вызванной функции. Перед завершением каждой функции отдельным оператором так и записывают <имя_функции>=<то-то_или_то-то>.
Процедура точно так же, оператор за оператором выполняет предусмотренный для нее код и может ничего не возвращать (ты такой вариант назвал "для назначения кнопкам"), может вернуть одно или несколько значений. Но процедура, в отличие от функции, их возвращает не в собственном имени, а через параметры.
Возьмем, например, старую задачу: получить по дню, месяцу, году дату и - обратная задача - имея дату, получить день, месяц и год.
В первом случае возвращаемое значение одно - значит пишем функцию. Во втором случае - возвращаемых значений много. Значит нужно написать процедуру с четырьмя параметрами: через первый параметр передадим значение даты, а в переменных, которые указали вторым-третьим-четвертым параметрами получим результаты.
Можно было поступить иначе. В первом случае написать процедуру с четырьмя параметрами - через три передать день-месяц-год, а в четвертом получить дату. А во втором случае - написать три разных функции, каждая из которых будет возвращать одно значение день, месяц или год. Можно было сделать и так. Но пришлось бы больше программисту писать, а компьютеру выполнять.

Ну и напоследок, чтобы окончательно всё запутать - функция может возвращать больше одного значения. Так, например, поступает функция pop - возвращает и вытолкнутый из стека элемент, и количество оставшихся...
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне