Главная страница

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


Скачать 4.43 Mb.
НазваниеНастоящий учебник посвящен системе Mathematica прикладному пакету компьютерной алгебры, при помощи которого можно решать любые задачи, в которых в той или иной форме встречается математика
АнкорМатематика
Дата11.05.2022
Размер4.43 Mb.
Формат файлаpdf
Имя файла106-108.pdf
ТипУчебник
#521834
страница13 из 38
1   ...   9   10   11   12   13   14   15   16   ...   38
— ну в самом деле, не спрашивать же Timing[x]; — кстати, почему?
Время показываемое Timing[x] отвечает приращению TimeUsed[] по- сле вычисления x.
Это можно сформулировать и в обратную сторону:
TimeUsed[] суммирует Timing[x] для всех вычисленных на протяжении сессии выражений x.
Команда Timing[x] контролирует только время работы CPU необходи- мое собственно для вычисления x. Время, затраченное на форматирование и вывод результата, общение с другими программами и пр. не учитыва- ется. Если Вы хотите измерить все время необходимое для вычисления и представления результата, используйте AbsoluteTiming[x].
К порождаемым командами Timing и AbsoluteTiming результатам не следует относиться слишком серьезно. Конечно, разницу между 0.002 се- кундами, 2 секундами и 2000 секундами в большинстве случаев трудно игнорировать. В то же время разница между 2 и 3 секундами обычно не является показательной. Дело в том, что это время зависит не только от качества алгоритма, но и от используемой компьютерной системы, объ- ема доступной памяти, особенностей операционной системы, эффективно- сти компиляции используемого фрагмента кода, других процессов, которые одновременно исполняются на том же компьютере, и — не в последнюю очередь!!! — от того, в какой момент сессии это вычисление производится.
Обычно необходимое для вычисления время может быть определено лишь с точностью до некоторой константы, описывающей степень само- контроля компьютера, на котором производится вычисление. Наименьшее время, которое может быть эффективно замерено, называется $TimeUnit.
Для большинства бытовых компьютеров, работающих под Windows, это вре- мя равно 1 миллисекунде, т.е. 1/1000 секунды. Таким образом, в этом слу- чае относительно вычисления, про которое команда Timing заявляет, что оно требует 0 секунд работы CPU, можно с уверенностью утверждать лишь то, что оно занимает меньше 1 миллисекунды. Многие машины, рабо- тающие под UNIX, пользуются более мелким зерном.
Команда TimeConstrained[x,t] пытается вычислить x за t секунд и пре- рывает вычисление, если расчет не укладывается в отведенное время. Яс- но, что в диалоговом режиме эта команда практически бесполезна, так как мы и так можем прервать вычисление в любое время. Эта команда стано- вится однако вполне осмысленной в составе длинной программы, в которой мы предлагаем попробовать несколько различных подходов к вычислению одного и того же выражения.

193
§ 6. Модификация значения переменной
В отличие от математиков, программисты вопринимают переменную действительно как переменную, которая в разные моменты времени при- нимает разные значения. Команды n = n + 1 и n = n
1 встречаются в традиционном программировании настолько часто, что им удобно присво- ить специальные названия и обозначения.
n++
Increment[n]
увеличить n на 1 после вычисления n--
Decrement[n]
уменьшить n на 1 после вычисления
++n
PreIncrement[n]
увеличить n на 1 перед вычислением
--n
PreDecrement[n]
уменьшить n на 1 перед вычислением
Различие между Increment[n] и Decrement[n] совершенно понятно. По- сле выполнения первой из этих команд значение n увеличивается на 1, а после выполнения второй — уменьшается на 1. Различие между Increment и PreIncrement гораздо тоньше, по крайней мере с точки зрения того, кто никогда не практиковался в процедурном программировании. В отличие от предыдущей эта оппозиция не является математической, а целиком ле- жит в области flow of calculation. Ее можно объяснить так. Все эти команды берут старое значение переменной n и перерабатывают его в новое значение, равное n + 1 или n
1. При этом ++ как в постпозиции,
так и в препозиции увеличивает его на 1, так что как n=1; n++; n, так и n=1; ++n; n дадут 2 — проверьте это!!! Так в чем же тогда разница между ними? Дело в том, что мы спросили про значение n слишком поздно, чтобы заметить эту разницу!! До выполнения ++ используется старое значение,
после выполнения — новое. Существует ровно один момент, обращение к n
во время инкрементации, который покажет эту разницу. А именно, в этот момент n++ использует старое значение, в то время как ++n — уже новое.
Тем самым, n=1; n++ даст 1, в то время как n=1; ++n даст 2. Подробнее за работой этих команд можно проследить по следующему диалогу, в ко- тором мы предлагаем выдать 10 инкрементаций или декрементаций (вызов итератора в формате
{10} без имени итератора интерпретируется просто как количество повторений), после чего смотрим на получившееся значе- ние i. Мы видим, что 10 инкрементаций увеличивают значение на 10, а 10
декрементаций уменьшают его на 10, но вот текущие значения в процессе выполнения этих процедур различны:
In[7]:=
{i=1; Table[i++,{10}], i}
Out[7]=
{{1,2,3,4,5,6,7,8,9,10},11}
In[8]:=
{i=1; Table[++i,{10}], i}
Out[8]=
{{2,3,4,5,6,7,8,9,10,11},11}
In[9]:=
{i=1; Table[i--,{10}], i}
Out[9]=
{{1,0,-1,-2,-3,-4,-5,-6,-7,-8},-9}
In[10]:=
{i=1; Table[--i,{10}], i}

194
Out[10]=
{{0,-1,-2,-3,-4,-5,-6,-7,-8,-9},-9}
Обратите внимание на формат этих команд.
Мы вызываем список из минипрограммы i=1; Table[i++,
{10}] и i. Почему мы не могли спросить просто i=1; Table[i++,
{10}]; i, без всякого списка? Потому что в этом случае мы увидели бы только окончательные значения i после выполнения вычисления, а не сам ход вычислений. Еще раз обратите внимание на тот факт, что на уровне окончательных значений никакой разницы между i++
и ++i нет!!!
Примерно так же как инкремент и декремент используются и другие команды модификации значений переменной:
x+=d
AddTo[x,d]
прибавить d к x
x-=d
SubtractFrom[x,y]
вычесть d из x
x*=c
TimesBy[x,c]
умножить x на c
x/=c
DivideBy[x,c]
разделить x на c
Таким образом, x+=d является просто сокращением для x=x+d, а x-=d —
сокращением для x=x-d. Точно так же x*=с является сокращением для x=x*с, а x/=d — сокращением для x=x/с. Эти сокращения бывают удобны в тех случаях, когда переменная x длинное имя, которое мы не хотим пе- репечатывать. Во всех остальных случаях мы предпочитаем пользоваться явными присваиваниями x=x+d, x=x-d, x=x*с и x=x/с. Между модификаци- ей значения переменной и присваиванием имеется существенное различие:
Чтобы значение переменной можно было модифицировать,
она должна уже иметь значение. Попытка выполнить что-нибудь в духе x+=y не приведет к успеху, если x до этого не присваивалось зна- чения.
Результатом такой попытки будет сообщение AddTo:
a is not a variable with a value, so its value cannot be changed.
С другой стороны, x=y; x+=z даст y+z. То же самое относится, конечно, и ко всем остальным операциям модификации значений, в том числе к инкременту и декременту.
Вот, например, программа для вычисления 1
m
+ . . . + n
m
, выполненная с помощью команды +=:
summa[m ,n ]:=Block[
{i,sum=0}, Do[sum+=i^m,{i,1,n}]; Return[sum]]
Использование остальных идиом нам уже известно. Однако в Махабхарате утверждается, что Брахма так выражался по этому поводу:
повтореба мать учебы, глупец не поймет и после 10000 повторений, а муд- рому достаточно всего 2500 повторений. Поэтому давайте еще раз резюмируем все лингвистические моменты:
Бланк показывает, что переменные m и n здесь являются пустыш- ками (dummy variables = фиктивные или немые переменные), которые можно заменить на любое другое выражение.

195
Отложенное присваивание := показывает, что правая часть является не окончательным значением, которое мы хотим присвоить левой части, а программой для вычисления этого значения. Эта программа будет запу- щена в тот момент, когда мы вместо пустышек подставим в левую часть настоящие численные значения.
Блок Block служит для локализации значений переменных i,sum. В
действительности локализация i излишня, так как Do сама локализует свой итератор — но мы можем этого не знать!! С другой стороны, локализация sum тоже не обязательна, если мы не будем называть наших переменных sum— а мы этого делать не будем, так как никогда не используем в глобаль- ном контексте имен, лишь в одной позиции отличающихся от внутренних имен. Но локализация промежуточных переменных/значений за- ведомо не может ничему помешать, а вот отсутствие локализации там, где она необходима, может привести к серьезным проблемам.
Де- виз серьезного пользователя:
sicher ist sicher, sicuro ´e sicuro, better safe, than sorry.
Block вызывается с двумя аргументами, списком локальных перемен- ных и собственно программой, отдельные команды этой программы разде- ляются точкой с запятой. Использование запятой вместо точки с запятой для разделения команд является грубой синтаксической ошибкой.
Команда Do имеет два аргумента, вначале один, описывающий упраж- нение, которое эта команда намеревается проделывать, а потом второй,
который задает имя итератора, с которым команда собирается это проде- лать, его начальное и конечное значение. Как всегда, по умолчанию шаг итерации равен 1. Кроме того, по умолчанию и начальное значение итера- тора равно 1, так что в принципе мы могли бы задать второй аргумент в виде
{i,n}, но мы считаем, что это bad form (= дурной стиль).
Команда Return[sum] служит для возвращения текущего значения sum из блока на глобальный уровень.
Упражнение. Напишите программу, вычисляющую сумму m-х степеней первых n нечетных натуральных чисел.
§ 7. Немедленная и отложенная подстановка:
-> Rule versus :> RuleDelayed
Следует отдавать себе отчет, что присваивание x = 3 имеет глобальный характер, и сохраняет силу на протяжении всей сессии, до тех пор, пока x
не было присвоено новое значение или пока значение x не было модифи- цировано. Поэтому если Вы хотите просто посмотреть, чему равно значе- ние f (x) какой-то функции при x = 3, значительно удобнее пользоваться не присваиванием, а другой конструкцией, подстановкой. Подстановка оформляется посредством Rule и Replace или их вариантов. В простей- шем виде для вычисления значения f (3) можно ввести, например, текст,
f[x] /. x->3, который с тем же успехом, что x=3; f[x] вычисляет f (3),

196
но при этом обладает тем неоспоримым преимуществом, что не присваивает
x никакого перманентного значения.
Хороший стиль программирования. Старайтесь вообще избегать явно- го присваивания значений тем переменным, которые не имеют уникальных имен. Вместо этого пользуйтесь подстановками.
x->y
Rule[x,y]
немедленная подстановка x:>y
RuleDelayed[x,y]
отложенная подстановка
Различие между Rule и RuleDelayed в смысле flow of calculation в точ- ноcти такое же, как между Set и SetDelayed. А именно,
Когда мы выполняем непосредственную подстановку lhs->rhs, ее пра- вая часть rhs вычисляется один раз в тот момент, когда мы задаем подстановку;
Когда мы выполняем отложенную подстановку lhs:>rhs, ее правая часть rhs заново вычисляется каждый раз, когда мы обращаемся к значению ее левой части lhs.
Приведем пример наглядно демонстрирующий драматическое разли- чие между Rule и RuleDelayed с точки зрения flow of calculation. Опре- делим список xxx посредством Table[x,
{10}]. Тогда xxx===
{x,x,x,x,x,x,x,x,x,x}.
Посмотрим на два следующих диалога:
In[11]:=n=1; xxx /. x->n++
Out[11]=
{1,1,1,1,1,1,1,1,1,1}
In[12]:=n=1; xxx /. x:>n++
Out[12]=
{1,2,3,4,5,6,7,8,9,10}
В нашем инпуте использована команда инкремент n++ Increment, которая увеличивает значение n на 1 после использования текущего значения n в вычислении. Мы видим, что при применении Rule значение n вычисляется один раз, в тот момент, когда задано правило x->n++. С другой сто- роны при применении RuleDelayed значение n вычисляется каждый раз заново при каждом обращении к x.
Упражнение. Ответьте, не включая компьютер, что получится, если за- менить в этих программах инкремент на преинкремент ++n который увели- чивает значение n на 1 перед использованием текущего значения n; а так- же на декремент n-- или предекремент --n, которые уменьшают значение
n на 1 после использования или, соответственно, перед использованием текущего значения n в вычислении.
Правила lhs->rhs и lhs:>rhs применяются при помощи описанной в следующем параграфе команды Replace и ее усиленных вариантов /.
ReplaceAll, //. ReplaceRepeated и т.д.

197
Описанное в § 4 немедленное присваивание lhs=rhs трактуется систе- мой как приказ на протяжении всей сессии применять немедленную подста- новку lhs->rhs всюду, где ее можно применить, в то время как варианты команды Replace применяет ее только в явно указанных местах.
Точно так же описанное в предыдущем параграфе отложенное при- сваивание lhs:=rhs трактуется системой как приказ на протяжении всей сессии применять отложенную подстановку lhs:>rhs всюду, где ее можно применить, в то время как команда Replace применяет ее только в явно указанных местах.
Модификация опций большинства встроенных функций осуществляет- ся путем включения подстановки Option->Choice в тело функции. Чтобы изменить значения нескольких опций, нужно написать
Option1->Choice1, Option2->Choice2, ...
Вот типичнейший пример изменения опции посредством команды Rule. По умолчанию команда Factor ищет разложение многочлена f над
Z, но задав опцию Extension->
{a,b,c} можно предложить ей раскладывать многочлен над
Z[a, b, c]:
Factor[x^4+1] предлагает разложить этот многочлен над Z, но над Z
он неприводим, так что мы снова увидим 1+x^4;
Factor[x^4+1,Extension->{I}] предлагает разложить тот же много- член над кольцом
Z[i] целых гауссовых чисел. В этом случае мы получим ответ (-I+x^2)(I+x^2);
Factor[x^4+1,Extension->{Sqrt[2]}] предлагает разложить тот же многочлен над кольцом
Z[

2].
В этом случае мы получим совершенно другое разложение, -(-1+

2-x^2)(1+

2+x^2).
Многие команды выдают ответ в формате x->c. Например, вычисляя при помощи Solve[a*x+b==0,x] решение линейного уравнения ax + b = 0
мы получим ответ в виде
{{x->-b/a}}.
В выражении можно одновременно произвести несколько подстановок.
Самый удобный формат для этого состоит в том, чтобы задавать подста- новки как список . Это можно сделать заранее. Например, вычисление
In[13]:=rules=
{x->a,y->b}; f[x+y] /. rules даст нам f[a+b] но при этом, как всегда, сами x и y не получают никаких постоянных значений. С другой стороны, список подстановок сохранится и чтобы вычислить g[a,b] нам достаточно будет напечатать g[x,y] /.
rules.
В подстановках и отложенных подстановках можно использовать блан- ки и паттерны для обозначения того, что подстановка должна осуществ- ляться для произвольных объектов из некоторого домена. Подстановка,
применямая не к индивидуальным объектам, а к паттернам — которые мо- гут в свою очередь заменяться на произвольные объекты!!! — называется правилом преобразования (transformation rule).

198
Приведем игрушечный пример применения правил преобразования. На- пример, Mathematica автоматически не пользуется формулой для танген- са суммы для вычисления чего-нибудь наподобие Tan[a+Pi/3].
Приме- нив к этой функции TrigExpand с последующим Simplify, мы получим что-нибудь в духе (

3*Cos[a]+Sin[a])/(Cos[a]-

3*Sin[a]). Но мы хо- тим явно увидеть, что получается в результате применения к Tan[a+Pi/3]
именно формулы для тангенса суммы. Это позволяет сделать следующая конструкция:
In[14]:=Hold[Tan[a+Pi/3]] /.
Tan[x +y ]->(Tan[x]+Tan[y])/(1-Tan[x]*Tan[y])
Функция Hold здесь использована, чтобы удержать Mathematica от неко- торых лишних в данном случае преобразований. Последующее применение
ReleaseHold[%] даст желаемое представление:
Out[14]=(

3+Tan[a])/(1-

3*Tan[a])
Обратите внимание на синтаксис lhs[x ,y ]->rhs[x,y]:
левая часть правила преобразования имеет формат lhs[x ,y ], бланки при переменных указывают, что это пустышки, называемые также немы- ми или фиктивными переменные (dummy variables), вместо которых можно подставить что угодно;
правая часть правила преобразования имеет формат rhs[x,y], но при этом бланки при немых переменных не ставятся!!!
Если бы мы хотели применять это правило и в дальнейшем, мы бы,
скорее всего, задали его как отложенное правило преобразования, в формате lhs[x ,y ]:>rhs[x,y].
Как мы узнаем в Главе 5, применение отложенных правил преобра- зования имеет то преимущество, что они могут принимать условия, вве- денные посредством оператора /; Condition. Языковая тонкость здесь состоит в том, что если мы описываем правило преобразования само по себе, оно имеет формат lhs[x ,y ] :>rhs[x,y] /; conditions
Однако если мы хотим применить это правило при помощи оператора заме- ны /. ReplaceAll, то условный оператор вносится в левую часть правила преобразования:
(lhs[x ,y ] /; conditions):>rhs[x,y]
Mathematica автоматически не упрощает таких выражений, как ln(). До- пустим, мы хотим провести вычисление в духе школьной математики, ис- пользующее формулу ln(xy) = ln(x) + ln(y) только в том случае, когда
x, y > 0. Для этого правило нужно задавать в виде
In[15]:=Log[E*Pi]/.(Log[x *y ] /; N[x]>0&&N[y]>0):>Log[x]+Log[y]

199
даст 1+Log[Pi].
То, что мы сейчас увидели, представляет собой первую крошечную де- монстрацию тех огромных возможностей, которые появятся у читателя в тот момент, когда он овладеет функциональным программировани- ем, которое позволит ему определять функции и правила преобразования для них, а также управлять применением функций к разным фрагментам выражений и правил преобразования на разных этапах вычисления.
1   ...   9   10   11   12   13   14   15   16   ...   38


написать администратору сайта