Эй, сценарист!!

Сценаристы корпорации Майкрософт

Загрузка кода возможна по следующей ссылке: HeyScriptingGuy2008_09.exe(150 KB)

Если и существует нечто , что обобщает суть нашего современного мира, то оно заключено в следующих двух словах: «Оставайтесь на связи». Благодаря сотовым телефонам вам не нужно больше быть дома для того, чтобы люди могли вам позвонить; они теперь могут найти вас всюду, где вы находитесь, в любое время дня и ночи. (Хм. Как … приятно.) Благодаря беспроводной компьютерной связи не нужно быть в офисе, чтобы выполнять свою работу; сейчас можно работать дома, на пляже, – везде, где можно себе представить.

История из жизни: родители редактора сценариев недавно отправились в туристический поход, но им пришлось поволноваться — как великим первооткрывателям Льюису и Кларку, — когда они столкнулись с проблемами подключения к беспроводной сети кампуса. Слава создателю, что спутниковое телевидение все еще работало!

И это еще только цветочки. Устройства GPS позволяют точно знать, где вы находитесь, в пределах нескольких десятков сантиметров; в зависимости от устройства, они также могут сообщить эту информацию другим людям. (Старая фраза «Вы можете убежать, но не спрятаться» никогда еще не была так точна, как сегодня.) Если было бы желание, сценарист, который пишет эту статью, смог бы получать телефонный звонок от чекового счета каждый раз, когда оплачивается чек; также он мог бы получать ежемесячный отчет по электронной почте от своего автомобиля о его состоянии. А если бы и этого было мало, тостеру получил бы предложение прогуляться с собакой и полить цветы каждый раз, когда сценарист едет в оттпуск.

Хорошо, может быть, это еще не произошло .. пока. Но при желании сценарист смог бы купить тостер с возможностью подключения к Интернету. Затем он смог бы позвонть своему тостеру по пути домой и, когда он войдет в дверь, получить только что приготовленный горячий тост. Честно говоря, мы не знаем, почему ему бы этого захотелось. Но если б захотелось..

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

Замечание. Согласно опросу, проведенному службой Harris Interactive, 43 процента американцев используют переносной компьютер в отпуске для получения и отправки рабочей корреспонденции. И более 50 процентов американцев используют свои сотовые телефоны в отпуске для проверки электронной почты и/или голосовых сообщений. И сюда не включены 40 процентов американцев, у которых нет отпуска на протяжении года.

Это происходит без разговоров о том, что огромное количество людей были бы счастливы добавить несвязанные наборы записей в арсенал сценариев, исключая одно соображение: они не знают, что собой представляют несвязанные набры записей. Хорошо, в том случае, если вы не знакомы с этой концепцией, несвязанный набор записей (более или менее) представляет собой таблицу базы данных, которая не привязана к реальной базе данных; вместо этого она создается сценарием, существует только в памяти и исчезает, когда сценарий завершается. Другими словами, несвязанные наборы записей — это созданная структура, которая существует только в течение нескольких минут и исчезает, унося с собой информацию. Ха, это, похоже действительно полезно, сценаристы. Спасибо за помощь!

Ладно, допустим: слова «несвязанные наборы записей» звучат не слишком увлекательно. Да, это так, они не слишком увлекательны. Но они могут быть чрезвычайно полезны. Как прекрасно знают все составители сценариев Visual Basic (VBScript) со стажем, VBScript совершенно точно не имеет лучших в мире способностей по сортировке данных. (Хорошо, пока вы не рассматриваете отсутствие способности сортировки данных как лучшую способность сортировки данных в мире.) Способность VBScript иметь дело с большими объемами данных точно так же, в лучшем случае, ограничена. За пределами объекта словаря (Dictionary) (который ограничивает программиста работой с элементами, которые имеют максимум два свойства) или массива (который сильно ограничен до списков данных, имеющих одно свойство), ну… это все о нем.

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

Стоит заметить: может быть, это всё делается для того, чтобы только показать вам, о чем мы говорим (предполагая, что хотя бы известен предмет разговора) Для начинающих, скажем, у нас есть статистика по бейсболу, показанная на рис. 1: это статистика, взятая с веб-узла MLB.com и сохраненная в файле значений, разделенных табуляцией, с названием C:\Scripts\Test.txt.

Рис. 1 Статистика, сохраненная в файле со значениями, разделенными табуляцией.

Игрок Хоумраны RBI Средний
Д. Педройа (D Pedroia) 4 28 .276
К. Кузманофф (K Kouzmanoff) 8 25 .269
Дж. Франкуе (J Francouer) 7 35 .254
С. Гузман (C Guzman) 5 20 .299
Ф. Санчес (F Sanchez) 2 25 .238
И. Сузуки (I Suzuki) 3 15 .287
Дж. Гамильтон (J Hamilton) 17 67 .329
И. Кинслер (I Kinsler) 7 35 .309
М. Рамирес (M Ramirez) 12 39 .295
А. Гонзалес (A Gonzalez) 17 55 .299

Всё это хорошо и здорово, но представьте, что нам бы на самом деле хотелось показать список игроков, отсортированных по числу хоумранов, которые они выбили. Может тут помочь несвязанный набор записей? Попробуем выяснить; взглянем на рис. 2. Ага, здесь уйма кода, не так ли? Но не беспокойтесь, так как вы скоро увидите, что все это страшно только на первый взгляд.

Рис.2. Несвязанный набор записей

Const ForReading = 1
Const adVarChar = 200
Const MaxCharacters = 255
Const adDouble = 5

Set DataList = CreateObject("ADOR.Recordset")
DataList.Fields.Append "Player", _
  adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", adDouble
DataList.Open

Set objFSO = _
  CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile _
  ("C:\Scripts\Test.txt", ForReading)

objFile.SkipLine

Do Until objFile.AtEndOfStream
    strStats = objFile.ReadLine
    arrStats = Split(strStats, vbTab)

    DataList.AddNew
    DataList("Player") = arrStats(0)
    DataList("HomeRuns") = arrStats(1)
    DataList.Update
Loop

objFile.Close

DataList.MoveFirst

Do Until DataList.EOF
    Wscript.Echo _
        DataList.Fields.Item("Player") & _
        vbTab & _
        DataList.Fields.Item("HomeRuns")
    DataList.MoveNext
Loop

Для начала определяем четыре новых константы:

  • ForReading. Эта константа будет использоваться, когда мы откроем и прочитаем текстовый файл.
  • adVarChar. Это — стандартная константа ADO для создания поля, которое использует тип данных Variant.
  • MaxCharacters. Это — стандартная константа ADO, используемая для показа максимального количества символов, которое поле Variant может содержать (в данном случае 255).
  • adDouble. Последняя константа ADO для создания поля, которое использует тип данных с двойной точностью (с плавающей точкой).

После определения констант мы находим блок кода:

Set DataList = CreateObject _
    ("ADOR.Recordset")
DataList.Fields.Append "Player", _
    adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", _
    adDouble
DataList.Open

Это — часть сценария, где мы на самом деле устанавливаем и настраиваем несвязанный набор записей. Для выполнения этой задачи первое, что нужно сделать, — это создать экземпляр объекта ADOR.Recordset object; надо сказать, при этом и создается таблица виртуальной базы данных (то есть наш несвязанный набор записей).

Затем используем эту строку кода (и метод Append («Добавить»)) для добавления нового поля в набор записей:

DataList.Fields.Append "Player", adVarChar, MaxCharacters

Как видите, здесь совсем нет ничего волшебного: мы просто вызываем метод «Добавить» (Append), за которым следуют три параметра:

  • Имя поля (Players).
  • Тип данных для этого поля (adVarChar).
  • Максимальное количество символов, которые могут быть размещены в нем (MaxCharacters).

После добавления поля Players можно добавить следующее поле: Homeruns (Хоумраны), которое имеет численный (adDouble) тип данных. Покончив с этим, вызываем метод Open («Открыть») для объявления нашего набора записей открытым и готовым для дела.

Затем создаем экземпляр объекта Scripting.FileSystemObject и открываем файл C:\Scripts\Test.txt. Этой части сценария реально нечего делать с несвязанным набором записей, она тут только потому, что нужно получить данные из текстовоо файла. Первая строка текстового файла содержит информацию о заголовке:

Player     Home Runs     RBI        Average

Несвязанному набору записей эта информация не нужна, так что после открытия файла вызываем метод SkipLine («Пропустить строку») для пропуска первой строки:

objFile.SkipLine

Сейчас, когда мы перешли к первой строке, содержащей актуальную информацию, устанавливаем цикл Do Until, предназначенный для построчного чтения остатка файла. При каждом чтении строки из файла сохраняем ее в переменную strLine, затем используем функцию Split («Отделить») для перевода этой строки в массив значений (отделяя новую строку каждый раз, когда обнаруживается табуляция):

arrStats = Split(strStats, vbTab)

Заметим, что все это — что-то вроде общего обзора, но предполагаем, что к этому времени большинство из вас уже достаточно хорошо умеет извлекать информацию из текстовых файлов. Короче, в первый проход через цикл массив arrStats будет содержать элементы с рис. 3.

Рис. 3. Содержимое массива

Номер элемента Имя элемента
0 Д. Педройа (D Pedroia)
1 4
2 28
3 .276

А теперь можно и повеселиться:

DataList.AddNew
DataList("Player") = arrStats(0)
DataList("HomeRuns") = arrStats(1)
DataList.Update

Здесь мы добавляем информацию об игроке 1 (Д. Педройа) в несвязанный набор записей. Чтобы добавить запись в набор записей, начнем с вызова метода AddNew («Добавить новый»); он создает новую пустую запись, с которой мы будем работать. Используем следующие две строки кода для назначения значений двум полям набора записей (Player (Игрок) и HomeRuns (хоумраны)), затем мы вызываем метод Update («обновить») для официальной записи этой записи в набор. И затем переходим на начало цикла еще раз, где мы повторяем весь процесс с новой строкой — следующим игроком — в текстовом файле. Понимаете? Тут может быть много кода, но все это очень просто и прямолинейно.

И что же произойдет после того, как все игроки будут добавлены в набор записей? Хорошо, после того, как закрываем текстовый файл, исполняем блок кода:

DataList.MoveFirst

Do Until DataList.EOF
  Wscript.Echo _
    DataList.Fields.Item("Player") & _
    vbTab & _
    DataList.Fields.Item("HomeRuns")
  DataList.MoveNext
Loop

В строке 1 используется метод MoveFirst («Переместить в начало») для позиционирования курсора в начало набора записей; если мы это не сделаем, возникает риск увидеть только часть данных набора записей. Затем настраивается цикл Do Until, выполняющийся до конца потока данных (то есть пока свойство набора записей EOF — конец файла — не станет истинным).

Все, что мы делаем внутри цикла, это выдача на консоль значений полей игрока (Player) и хоумранов (HomeRuns) (заметим, что иногда используется нечеткий синтаксис для индикации конкретного поля): DataList.Fields.Item("Player"). Затем просто вызываем метод MoveNext («Переместиться на следующий») для перемещения на следующую запись в наборе.

Не нужно говорить, что все это на самом деле просто. Когда все сказано и сделано, следует вернуться к следующему моменту:

D Pedroia       4
K Kouzmanoff    8
J Francouer     7
C Guzman        5
F Sanchez       2
I Suzuki        3
J Hamilton      17
I Kinsler       7
M Ramirez       12
A Gonzalez      17

Как можно видеть, всё – ну, если задуматься, на самом деле не так уж всё и хорошо, верно? Допустим, мы вернули имена игроков и общее количество хоумранов, но сортировка по хоумранам не выполнена. Елки-палки, почему несвязанный набор записей не сделал этого за нас?

Этому есть хорошее объяснение: мы не сказали набору записей, по какому полю нужно сортировать. Это довольно легко исправить: просто изменим сценарий для добавления сведений о сортировке сразу перед вызовом метода MoveFirst («переместиться в начало»). Иными словами, сделайте эту часть сценария так:

DataList.Sort = "HomeRuns"
DataList.MoveFirst

Очевидно, тут нет трюка; просто присваиваем поле HomeRuns (Хоумраны) свойству Sort (Сортировать). Теперь посмотрим на результат, который получится после запуска этого сценария:

F Sanchez       2
I Suzuki        3
D Pedroia       4
C Guzman        5
J Francouer     7
I Kinsler       7
K Kouzmanoff    8
M Ramirez       12
J Hamilton      17
A Gonzalez      17

Намного лучше. Все это хорошо, за исключением одного «но»: обычно общее количество хоумранов показывается в нисходящем порядке, где игрок с максимальным их количеством показывается первым. Есть какой либо способ сортировать несвязанный набор записей в нисходящем порядке?

Конечно есть, все, что нужно сделать, – это вспомнить о полезном параметре DESC, например, так:

DataList.Sort = "HomeRuns DESC"

А что параметр DESC делает для нас? Получилось:

A Gonzalez      17
J Hamilton      17
M Ramirez       12
K Kouzmanoff    8
I Kinsler       7
J Francouer     7
C Guzman        5
D Pedroia       4
I Suzuki        3
F Sanchez       2

Между прочим, совершенно справедливо сортировать по нескольким свойствам; всё, что нужно сделать, – это присвоить каждому из этих свойств порядок сортировки. Например, предположим, что вам нравится сортировать сначала по хоумранам, а затем по RBI. Не проблема; эта команда все сделает:

DataList.Sort = "HomeRuns DESC, RBI DESC"

Попробуйте и посмотрите сами. Это не так радует, как просмотр электронной почты в отпуске, но близко к тому.

Замечание. Помните о том, что нельзя сортировать по полям, которые не были добавлены в набор записей. Что это значит? Это значит, что перед добавлением свойства, как, например, RBI, в оператор Sort, нужно добавить в сценарий в соответствующие места следующие строки:

DataList.Fields.Append "RBI", adDouble

DataList("RBI") = arrStats(2)

И, если вы посмотрите на результат, то поймете, что нужно исправить и оператор Wscript.Echo:

Wscript.Echo _
  DataList.Fields.Item("Player") & _
  vbTab & _
  DataList.Fields.Item("HomeRuns") & _
  vbTab & DataList.Fields.Item("RBI")

Посмотрим, что еще можно сделать при помощи несвязанных наборов записей. О, есть еще кое-что. Предположим, что мы извлекли всю информацию для всех игроков и затем сортируем данные по среднему числу ударов. (Среди прочего, это означает, что нужно изменить изначальный сценарий для создания полей RBI и BattingAverage (среднее число ударов).) Результат выглядит следующим образом:

J Hamilton      17      67      0.329
I Kinsler       7       35      0.309
A Gonzalez      17      55      0.304
C Guzman        5       20      0.299
M Ramirez       12      39      0.295
I Suzuki        3       15      0.287
D Pedroia       4       28      0.276
K Kouzmanoff    8       25      0.269
J Francouer     7       35      0.254
F Sanchez       2       25      0.238

Хорошо, но если нужен только список игроков, выбивающих 0,300 и выше? Как можно ограничить отображаемые данные только игроками, которые соответствуют указанным критериям? Ну, один способ — назначить фильтр набору записей:

DataList.Filter = "BattingAverage >= .300"

Фильтр набора записей служит тем же общим целям, что и запрос к базе данных: он предоставляет механизм по ограничению возвращаемых данных до подмножества всех записей в наборе. В этом случае просто просим фильтр отобрать все записи, за исключением тех, где поле BattingAverage имеет значение, большее или равное 0,300. И что вы думаете? Фильтр делает в точности то, что его просят:

J Hamilton      17      67      0.329
I Kinsler       7       35      0.309
A Gonzalez      17      55      0.304

Если бы наши дети так отвечали, а?

Кстати, можно использовать несколько критериев в одном фильтре. Например, приведенная ниже команда ограничивает возвращаемые данные записями, где поле BattingAverage больше или равно 0,300 и поле HomeRuns больше 10:

DataList.Filter = _
  "BattingAverage >= .300 AND HomeRuns > 10"

И наоборот, приведенный ниже фильтр ограничивает данные записями, где поле BattingAverage больше или равно 0,300 или поле HomeRuns field больше 10:

DataList.Filter = "BattingAverage >= .300 OR HomeRuns > 10"

Испробуйте оба варианта, и вы увидите, в чём отличие. А собака зарыта здесь: просто для интереса, вот еще один фильтр на пробу:

DataList.Filter = "Player LIKE 'I*'"

Как видно, можно использовать в фильтре подстановочные символы. Чтобы это сделать, используйте оператор LIKE (вместо знака равенства) и затем звездочку так же, как бы вы это сделали в MS-DOS® в команде типа dir C:\Scripts\*.txt. В предыдущем примере будет получен список игроков, чье имя начинается с буквы I; просто потому, что используемый синтаксис говорит: «Показать список всех записей, где значение поля Player (Игрок) начинается с I и затем идет, ну, скажем, что угодно». Попробуйте .. хорошо; вы сейчас уже знаете эту процедуру.

Кстати, вас еще не утомили средние числа ударов типа 0.309. (Обычно среднее число ударов записывается без ведущего нуля ,309, например.) Но это хорошо; вы можете просто использовать функцию FormatNumber для форматирования среднего числа ударов в любой традиционный вид, который вы хотите:

FormatNumber (DataList.Fields.Item("BattingAverage"), 3, 0)

Просто включите эту функцию в оператор Wscript.Echo, когда отображаете число (или, наоборот, можно присвоить результат переменной и передать ее оператору Echo):

Wscript.Echo _
  DataList.Fields.Item("Player") & _
  vbTab & _
  DataList.Fields.Item("HomeRuns") & _
  vbTab & DataList.Fields.Item("RBI") & _
  vbTab & _
  FormatNumber _
  (DataList.Fields.Item("BattingAverage"), _
   3, 0)

Интересная штука, а?

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

По-любому, хочется отметить, что … о, звонит уже мобильный телефон. И только что получено сообщение от тостера. Важно: горячий тост уже готов, и что мы хотим: масло или джем? Должен идти, увидимся через месяц!

Головоломка-сценарий доктора Сценарио.

Задача на этот месяц, требующая не только умения решать головоломки, но и навыков создания сценариев.

Сентябрь 2008 г.: Сценарный поиск

Вот простой (или может быть не очень) поиск слова. Найти все функции VBScript и операторы в списке. Но вот уловка: оставшиеся буквы сложатся в скрытое слово, которое окажется просто — вы угадали — командлетом Windows PowerShell™.

Список слов: Abs, Array, Atn, CCur, CLng, CInt, DateValue, Day, Dim, Else, Exp, Fix, InStr, IsEmpty, IsObject, Join, Len, Log, Loop, LTrim, Mid, Month, MsgBox, Now, Oct, Replace, Set, Sin, Space, Split, Sqr, StrComp, String, Timer, TimeValue, WeekdayName.

fig08.gif

ОТВЕТ:

Головоломка-сценарий доктора Сценарио

Ответ: Сентябрь 2008 г.: Сценарный поиск

puzzle_answer.gif

Сценаристы работают... ну хорошо, получают зарплату в корпорации Майкрософт. Когда они не играют в бейсбол, не тренируют бейсбольную команду, не смотрят бейсбол и не занимаются другими делами, они ведут проект Script Center в журнале TechNet Magazine. Проверьте это по адресу www.scriptingguys.com.