Версия PascalABC.NET 3.2

Спасибо - очень точное объяснение.

Это очень характерно для нашей страны - сломать ребенку судьбу потому что лень учиться самому. Главное - что тебе за это ничего не будет.

Да да да - мы о том же :))

Не вполне согласен. Наш опыт обучения школьников говорит о том, что если ребёнка сразу учить правильно, то он как раз думает над алгоритмом в терминах вот этих новых примитивов и применяет нужный, выбирая из нескольких. А при подготовке к ЕГЭ школьников до сих пор заставляют писать неоптимальную сортировку вручную.

3 лайка

В задачах олимпиад для школьников, на Интернет-ресурсах, предназначенных для самообучения, а также в школьных задачниках часто встречаются формулировки следующего вида:

“На вход программе подаётся последовательность символов, заканчивающаяся точкой (точка – признак конца и в последовательность не входит).”

“Программа получает на вход целые числа, количество введенных чисел неизвестно, последователньость чисел заканчивается числом 0 (0 - признак окончания ввода, не входит в последовательность)”

“Дана последовательность вещественных чисел, заканчивающаяся нулем (ноль - признак окончания ввода).”

Иными словами, предлагается ввод неопределенного количества данных, ограниченных неким терминирующим символом (реже - терминирующим условием по типу “до первого отрицательного числа”). Такой ввод приходится организовывать посредством циклов с условием (while, repeat). Нельзя ли сделать какие-то процедуры ввода по аналогии с Read[ln]Integer, Read[ln]Real, ReadlnString и т.д., в которых задается не количество вводимых элементов, а терминирующее условие, как вариант, в форме предиката (x->x=0) ?

ReadSeqIntegerWhile(x->x>0)

О!!! И как давно? И как насчет символов (char)?

Давно.

Символов и просто с ReadArr нет. Непонятно, чем их разделять - пробелами?

Мне тоже непонятно. Вообще, в паскале вопрос ввода символьных данных изначально криво решен. Это же надо какое воображение иметь, чтобы не заложить в язык строковый тип и при этом отдельные символы вводить, разделяя их признаком смены строки? Фактически “предложение” вводится по одной букве на строке - о чем Вирт с Йенсеном думали? Но поскольку никакого иного разделителя в Паскале при вводе символов нет, можно пойти по такому пути: по умолчанию разделителем сделать код, посылаемый нажатием Enter, и дать опциональную возможность указать свой разделитель, например, тот же пробел или нечто иное, которое можно выбрать, исходя из стоящей задачи.

Немного улыбнуться. Сегодня один школьник выдал (проверка суммы трех натуральных чисел на четность):

begin
write (((readinteger('Введите 3 нат. числа: ')+readinteger+readinteger) mod 2)=0);
end.

Вот сегодня попался такой “вопрос” от школьника.

Попробовал решить эти задачи в стиле “без циклов”. И ни одну не смог. Да, MatrRandom, Rows и Print жизнь несколько облегчили, спасибо разработчикам за то, что они есть. Но все равно осталось ощущение какой-то “недорешенности” вопроса о работе с матрицами. Я не так давно предлагал некий “минимальный набор” операций, которые бы хотелось иметь для работы с матрицами и который я оформил для себя в виде модуля, но как-то это предложение осталось без комментариев… вот и не пойму: то ли сочли ненужным, то ли несвоевременным.

Давайте продолжим обсуждение. Я уже подзабыл - помню превращение в последовательность и замену строки - столбца.

Вот - предлагаю это сделать стандартным:

function Enumerate<T>(Self: array [,] of T): sequence of (integer,integer,T); 
  extensionmethod;
begin
  for var i:=0 to Self.RowCount-1 do
  for var j:=0 to Self.ColCount-1 do
    yield (i,j,Self[i,j])
end;

begin
  var a := MatrRandom(4,5,1,10);
  Print(a.Enumerate.FirstOrDefault(x->x[2]=3));

  var n := 9;
  a := MatrRandom(n,n,-20,20);
  Print(a.Enumerate.Where(x->(x[1]>x[0]) and (x[2]<10)).Count);
  Print(a.Enumerate.Where(x->x[1]<x[0]).Max);
end.

Вы предложили хороший, но сверхмощный метод. Превосходно понимаю желание “не плодить сущностей без надобности”, но как тогда насчет синтаксического сахара? Больно уж горькой пилюля получается.

Чисто умозрительно, двумерный массив превращается в последовательность трехэлементных кортежей. Во многих ли задачах они понадобятся? Все же не каждый раз удобно вспоминать, что x[2] - это x[i,j] “в девичестве”. Конечно, есть задачи, где без индексов не обойтись и там действительно нужно будет проецировать (i,j) в результирующую последовательность. Но есть и другие задачи, где индексы не нужны. А нужно или отобрать строки/колонки по условию (это мы умеем благодаря методам Rows/Cols с последующей фильтрацией), или просто свернуть все элементы в сумму, экстремум и т.д, найти число элементов, удовлетворяющих условию. Т.е. тот самый случай, когда про x[0],x[1] лучше не вспоминать.

Далее, у нас есть проблема с формированием матриц путем ввода с клавиатуры/чтения из файла. И там много вариантов из-за разных типов данных.

И наконец, у нас есть “смешные задачи” по трансформации матриц без потери данных или с неизбежной потерей их части. Например, превращение матрицы 5х5 в 6х4 (последний элемент теряем) или 5х4 в 2х10. Безусловно, их можно решить на базе последовательности “3-tuple”, но насколько это будет сладко?

Что я предлагаю. Во-первых, конечно, Вашу мощную перегрузку для Enumerate. А кроме этого добавить несколько расширений для решения более простых задач коротеньким и наглядным путем. В конце-концов, если есть желание прекратить пополнять язык новыми методами, можно поступить, как с графикой. Сделать отдельный модуль Matr (но в комплекте поставки) и кто захочет, будет писать uses Matr;

unit Matr;

interface

/// Упаковка матрицы в последовательность по строкам
function MatrPackRows<T>(a:array[,] of T):sequence of T;
/// Упаковка матрицы в последовательность по столбцам
function MatrPackCols<T>(a:array[,] of T):sequence of T;
/// Формирование матрицы из последовательности построчное
function MatrUnpack<T>(s:sequence of T; cols:integer):array[,] of T;
/// Срез матрицы (пока жесткий)
function Slice<T>(a:array[,] of T; rfrom,rto,cfrom,cto:integer):array[,] of T;
/// Главная диагональ матрицы в последовательность
function MatrMainDiag<T>(a:array[,] of T):sequence of T;
/// Побочная диагональ матрицы в последовательность
function MatrSecondDiag<T>(a:array[,] of T):sequence of T;

implementation

function MatrPackRows<T>(a:array[,] of T):sequence of T;
begin
  var jm:=a.ColCount-1;
  for var i:=0 to a.RowCount-1 do
    for var j:=0 to jm do
      yield a[i,j]
end;

function MatrPackRows<T>(Self:array[,] of T):sequence of T; extensionmethod;
begin
  var jm:=Self.ColCount-1;
  for var i:=0 to Self.RowCount-1 do
    for var j:=0 to jm do
      yield Self[i,j]
end;

function MatrPackCols<T>(a:array[,] of T):sequence of T;
begin
  var im:=a.RowCount-1;
  for var j:=0 to a.ColCount-1 do
    for var i:=0 to im do
      yield a[i,j]
end;

function MatrPackCols<T>(Self:array[,] of T):sequence of T; extensionmethod;
begin
  var im:=Self.RowCount-1;
  for var j:=0 to Self.ColCount-1 do
    for var i:=0 to im do
      yield Self[i,j]
end;

function MatrUnpack<T>(s:sequence of T;cols:integer):array[,] of T;
begin
  var r:=new T[s.Count div cols,cols];
  var n:=r.Length;
  var k:=0;
  foreach var elem in s do begin
    r[k div cols, k mod cols]:=elem;
    k+=1;
    if k=n then break
    end;
  Result:=r
end;

function MatrUnpack<T>(Self:sequence of T;cols:integer):array[,] of T;
  extensionmethod;
begin
  var r:=new T[Self.Count div cols,cols];
  var n:=r.Length;
  var k:=0;
  foreach var elem in Self do begin
    r[k div cols, k mod cols]:=elem;
    k+=1;
    if k=n then break
    end;
  Result:=r
end;

function Slice<T>(a:array[,] of T;rfrom,rto,cfrom,cto:integer):array[,] of T;
begin
  var rows:=a.RowCount;
  var cols:=a.ColCount;
  Assert((rfrom>=0) and (rfrom<rows),'Недопустимый параметр rfrom');
  Assert((rto>=0) and (rto<rows) and (rto>rfrom),'Недопустимый параметр rto');
  Assert((cfrom>=0) and (cfrom<cols),'Недопустимый параметр cfrom');
  Assert((cto>=0) and (cto<cols) and (cto>cfrom),'Недопустимый параметр cto');
  var newrows:=rto-rfrom;
  var newcols:=cto-cfrom;
  var r:=new T[newrows,newcols];
  for var i:=rfrom to rto-1 do
    for var j:=cfrom to cto-1 do
      r[i-rfrom,j-cfrom]:=a[i,j];
  Result:=r
end;

function Slice<T>(Self:array[,] of T;rfrom,rto,cfrom,cto:integer):array[,] of T;
  extensionmethod;
begin
  var rows:=Self.RowCount;
  var cols:=Self.ColCount;
  Assert((rfrom>=0) and (rfrom<=rows),'Недопустимый параметр rfrom');
  Assert((rto>=0) and (rto<=rows) and (rto>rfrom),'Недопустимый параметр rto');
  Assert((cfrom>=0) and (cfrom<=cols),'Недопустимый параметр cfrom');
  Assert((cto>=0) and (cto<=cols) and (cto>cfrom),'Недопустимый параметр cto');
  var newrows:=rto-rfrom;
  var newcols:=cto-cfrom;
  var r:=new T[newrows,newcols];
  for var i:=rfrom to rto-1 do
    for var j:=cfrom to cto-1 do
      r[i-rfrom,j-cfrom]:=Self[i,j];
  Result:=r
end;

function MatrMainDiag<T>(a:array[,]of T):sequence of T;
begin
  var n:=Min(a.RowCount,a.ColCount)-1;
  for var i:=0 to n do yield a[i,i]
end;

function MatrMainDiag<T>(Self:array[,]of T):sequence of T; extensionmethod;
begin
  var n:=Min(Self.RowCount,Self.ColCount)-1;
  for var i:=0 to n do yield Self[i,i]
end;

function MatrSecondDiag<T>(a:array[,]of T):sequence of T;
begin
  var n:=Min(a.RowCount,a.ColCount);
  for var i:=0 to n-1 do
    yield a[i,a.ColCount-i-1]
end;

function MatrSecondDiag<T>(Self:array[,]of T):sequence of T; extensionmethod;
begin
  var n:=Min(Self.RowCount,Self.ColCount);
  for var i:=0 to n-1 do
    yield Self[i,Self.ColCount-i-1]
end;

end;

Для чего так много? Упаковка решает вопрос со свертками, причем любыми, и любыми выборками, где не задействованы индексы. свертки требуются, увы, построчно или поколонно - это зависит от условий. Формирование матрицы из последовательности решает все вопросы со вводом (ввод-то для последовательностей уже есть), а также позволяет модифицировать матрицы (развернул, сделал Transform - и назад). Кроме того, тут легко, поменяв число колонок, изменить размеры матрицы без потери данных. Срезы - ну это ясно зачем. Пока они простые, без шага, просто вырезают нужный прямоугольничек. Не было времени сделать что-то посерьезнее, да и нужно редко. Ну и диагонали - тоже сахарок своего рода. В будущем - замах на треугольные матрицы “одним движением руки”.

И да, забыл. Еще бы надо уметь вычеркивать из матрицы строку/столбец. И, по-видимому, это должна быть функция, возвращающая матрицу с тем, чтобы самостоятельно определить, будет это другая или записанная на место старой. Kaк с Transpose (кстати, почему нет расширения, а только функция? Как-то выбивается из общего стиля…).

Что-то вот такое:

Function MatrDeleteRow<T>(Self:array[,] of T; row:integer):array[,] of T;
  extensionmethod;
begin
  var nrows:=Self.RowCount-2;
  var ncols:=Self.ColCount-1;
  var r:=new T[nrows+1,ncols+1];
  for var j:=0 to ncols do
    for var i:=0 to nrows do
      r[i,j]:=(i<row?Self[i,j]:Self[i+1,j]);
  Result:=r
end;

function MatrDeleteCol<T>(Self:array[,] of T; col:integer):array[,] of T;
  extensionmethod;
begin
  var nrows:=Self.RowCount-1;
  var ncols:=Self.ColCount-2;
  var r:=new T[nrows+1,ncols+1];
  for var i:=0 to nrows do
    for var j:=0 to ncols do
      r[i,j]:=(j<col?Self[i,j]:Self[i,j+1]);
  Result:=r
end;

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

Да, есть такое дело. Я сам как будто проваливаюсь в пустоту - думаю над этими функциями, но в таком виде не хочу реализовывать - не нравится. А как лучше - пока не придумалось. И даже Enumerate не хочется пока - нет единой концепции.

Что нравится из предложенного Вами и что - надо делать. Надо делать Enumerate и ввод матрицы - это универсальные методы. MatrPackRows и Cols - названия не нравятся, а так - конечно, красиво - в последовательность превратить. Slice - это сразу надо делать срезы, это мы сделаем, но позже - n-мерные срезы, там - морока. Диагонали квадратной матрицы - не нравятся совсем - это какая-то частная задача. Наверное, сделаем a.SetRow и a.SetCol - это вроде как надо раз есть Row и Col.

Уже имеющееся a.Transform не нравится, т.к. методы расширения обычно возвращают новый объект, а тут меняют исходный. Так что, хочется заменить это на Transform(a) - чтобы было одинаково с Sort(a) и Reverse(a).

Вычеркивание строки-столбца тоже не нравится как базовый сервис - это какие-то частные задачи, не хочется их в общую библиотеку помещать.

Если сделать (ну пусть имя не нравится, я о функции) Pack и UnPack, то Transform фактически будет не нужен: запаковали поколонно и распаковали (распаковка всегда по строкам). Pack и UnPack - это я не придумал, а взял имена из Fortran - там есть аналогичные вещи и они так называются. Изначально я их обзывал MatrSeqByRow, MatrSeqByCol, а вот для обратного преобразования удобного имени не нашел. Согласен, что Slice - это срезы, но когда они будут - непонятно, а для матриц нужно, поэтому вот пока там “чисто для себя” сделал. Что до вычеркивания строк и столбцов - конечно, срезы эту проблему уберут. Опять же, когда это еще будет…

И последнее - по диагоналям. Очень много задач на матрицы, где эти диагонали присутствуют. А также, различные треугольники, нарезанные из матриц. И, если вспомнить, что диагонали существуют не только у квадратных матриц, работа даже с диагоналями не так уж и тривиальна.

Мне справедливо коллеги указывают, что превращение матрицы в последовательность по строкам делается с помощью SelectMany:

a.Rows.SelectMany(x->x)

В специальной функции поэтому нет необходимости

1 лайк

Да, наверно тут все так. Но все равно нужна обратная операция. И по-прежнему очень хочется ввод… И какую-то замену строки-столбца. В части изменения значений в матрицах пока что очень печально все.

Обратил внимание, что есть Find(), FindLast(), FindAll() FindIndex(), FindIndexLast() но нет FindIndexAll() А вот понадобилась… была задача найти индексы второго и пятого элементов, удовлетворяющих условию…

В очередной раз порадовался, какой замечательный язык программирования удалось создать разработчикам. Подсунули “задачку на вшивость”

Перевести код из Java в Pascal:

import java.util.*;

public class Main
{
static long[] countPerfect(int n)
{
int i, j;
long cat[]= new long[51];
cat[0] = cat[1] = 1;
for(i = 2; i <= n; i++)
for(j = 0; j < i; j++)
cat[i] += cat[j] * cat[i - j - 1];
return cat;
}

public static void main(String[] args)
{
long cat[] = countPerfect(50);
Scanner con = new Scanner(System.in);
while(con.hasNext())
{
int n = con.nextInt();
System.out.println(cat[n/2]);
}
}
} 

Видно, что наворочено специально: динамические массивы с огромными целыми значениями, возвращаемые процедурой, сканер входных данных на строке и .т.д. Но получилось даже короче, чем в оригинале.

// PascalABC.NET 3.2, сборка 1407 от 18.03.2017
// Внимание! Если программа не работает, обновите версию!
function countPerfect(n:integer):array of BigInteger;
begin
  var cat:=new BigInteger[51];
  (cat[0],cat[1]):=(1,1);
  for var i:=2 to n do
    for var j:=0 to i-1 do
      cat[i]+=cat[j]*cat[i-j-1];
  Result:=cat
end;

begin
  var cat:=countPerfect(50);
  ReadlnString.ToIntegers.Select(n->cat[n div 2]).Println(NewLine);
end.

И результат:

1 43 19 42 7
1
24466267020
4862
24466267020
5

Превосходно, что лямбда смогла захватить такой массив.

1 лайк

Обнаружил в сборке 1412 процедуры ReadMatrInteger и ReadMatrReal. Больше спасибо за них. Больше ничего пока не обнаружил. Может, что-то пропустил? Например, превращение одномерного массива в матрицу или замены какие-то в матрицах? ))))

И заодно. Я не смог угадать, когда и как пользоваться .СonvertAll() для массивов.