www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Multidimensional array

reply "Oleksiy" <oleksiy.pavlyuk gmail.com> writes:
Hi,

I'm new to the language and would appreciate if anybody could 
clarify the following:

1. What is the rationale behind "prefix declaration" of an array? 
Using right-to-left order to declare an array and left-to-right 
order to access elements seems confusing.

2. Consider this code:
     dchar[3][5] arr = '.';
     arr[2][] = '!';
     writeln();
     writeln(arr);

Result: ["...", "...", "!!!", "...", "..."]
Which is expected. According to Ali Çehreli's tutorial, omitting 
the index of an array will result in operation being applied to 
the whole array (in this case element of another array).

change the code:
-   arr[2][] = '!';
+   arr[][2] = '!';

Still getting the same result: ["...", "...", "!!!", "...", "..."]

I would expect to get: ["..!", "..!", "..!", "..!", "..!"]
since omitted index would apply the operation to all elements of 
the first dimension of the array.

What am I missing?


Thanks,
Oleksiy
Jul 04 2013
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, July 05, 2013 00:39:47 Oleksiy wrote:
 Hi,
 
 I'm new to the language and would appreciate if anybody could
 clarify the following:
 
 1. What is the rationale behind "prefix declaration" of an array?
 Using right-to-left order to declare an array and left-to-right
 order to access elements seems confusing.
Because the brackets are part of the type, and the type goes to the left of the variable name. The fact that C put in on the right-hand side is actually quite bizarre, but they also did nonsense like make it so that in this declaration int* p1, p2; p1 is an int*, whereas p2 is an int, which D fixed (in D, both are int*). Now, the downside to putting them on the left is the order of the sizes. Types are read outwards from variable name. This becomes particularly important when you try and understand stuff like C function pointer declarations, since the name ends up in the middle of the declaration. Take int* i; for example. It's a pointer to an int, not an int to a pointer. It's read right-to-left. Static array declarations are doing the same thing. int[4][5] arr; It's a static array of length 5 which holds static arrays of length 4 which hold ints. So, the ordering is completely consistent with how the rest of the type system works. Reading it from left-to-right would be inconsistent, much as most people tend to think of types that way. Now, we've already broken that rule in it least one case - const and immutable. In C and C++, const int* p; and int const* p; are identical, and technically, the second one would be more correct - because it gives you pointer to a const int, not a pointer to an int const. But the first one is allowed and what is frequently used, if nothing else, because people tend to try and read type declarations left-to-right. In D, however, we have const int* p; and const(int*) p; but no int const* p; So, in this case, we arguably already broke the right-to-left rule. As such, it arguably would have been better to also break that rule with regards to static arrays, but the decision on static arrays far predates even adding const into the language, so at the time static arrays were introduced, making them read left-to-right would have been inconsistent with everything else, and while const is now inconsistent with everything else, most everyone is used to writing const on the left anyway, so I doubt that much of anyone thought about it being inconsistent when it was added. The whole "read the type outward from the variable name" deal is not as critical in D as it is in C/C++, because we don't declare function pointers in the same way (which is where it's truly critical in C/C++), but we inherited it from C/C++, and for the most part, D still follows it. Fixing it so that the sizes for static arrays were read left-to-right would definitely be a usability improvement, but even if we were to decide that the whole "read the type outward from the variable name" was unimportant enough to make the change desirable, it would silently break all kinds of code at this point, so we're stuck with it. - Jonathan M Davis
Jul 04 2013
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Thursday, 4 July 2013 at 23:02:10 UTC, Jonathan M Davis wrote:
 On Friday, July 05, 2013 00:39:47 Oleksiy wrote:
 Hi,
 
 I'm new to the language and would appreciate if anybody could
 clarify the following:
 
 1. What is the rationale behind "prefix declaration" of an 
 array?
 Using right-to-left order to declare an array and left-to-right
 order to access elements seems confusing.
Because the brackets are part of the type, and the type goes to the left of the variable name. The fact that C put in on the right-hand side is actually quite bizarre, but they also did nonsense like make it so that in this declaration int* p1, p2; p1 is an int*, whereas p2 is an int, which D fixed (in D, both are int*). - Jonathan M Davis
Arguably, this all comes from C's "value centric" scheme. When you write (not the C-like placement of the *): int *p, a; It means: with *p and a, you get an int. Ditto, when you write: int arr[5]; It is written that way to reflect that usage is: *p = arr[0]; As stated in the first question, it makes sense that way in the context of: #define WIDTH = 10 #define HEIGHT = 5 int matrix[WIDTH][HEIGHT]; a = matrix[w][h]; I'm just saying that's the explanation for it. Once you have to start making the distinction between "array of pointers" and "pointer to array", then clearly, things start crumbling apart, and D's approach is simply better. I'm just saying, there *is* an historical reason for this, not just arbitrary stupidness. The fact that in D, declaration and indexing is "reversed" is something I've truly never even noticed, since to me, naturally, declarations read right to left, then parsing is left to right. So no problem there.
Jul 05 2013
prev sibling parent "Oleksiy" <oleksiy.pavlyuk gmail.com> writes:
 Fixing it so that the sizes for static arrays were read 
 left-to-right would
 definitely be a usability improvement, but even if we were to 
 decide that the
 whole "read the type outward from the variable name" was 
 unimportant enough to
 make the change desirable, it would silently break all kinds of 
 code at this
 point, so we're stuck with it.
The way D declares static arrays makes sense and I was actually trying to access elements in the same fashion until I realized that it's reversed. I than found a remark in Andrei's book pointing out the difference. Thanks for your reply Jonathan. --Oleksiy
Jul 05 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Oleksiy:

 1. What is the rationale behind "prefix declaration" of an 
 array? Using right-to-left order to declare an array and 
 left-to-right order to access elements seems confusing.
I think the way Go language declares arrays and pointers is a bit better. But for the rationale of this part of D design others should answer you.
 2. Consider this code:
     dchar[3][5] arr = '.';
     arr[2][] = '!';
     writeln();
     writeln(arr);

 Result: ["...", "...", "!!!", "...", "..."]
 Which is expected. According to Ali Çehreli's tutorial, 
 omitting the index of an array will result in operation being 
 applied to the whole array (in this case element of another 
 array).

 change the code:
 -   arr[2][] = '!';
 +   arr[][2] = '!';

 Still getting the same result: ["...", "...", "!!!", "...", 
 "..."]

 I would expect to get: ["..!", "..!", "..!", "..!", "..!"]
 since omitted index would apply the operation to all elements 
 of the first dimension of the array.

 What am I missing?
Ali Çehreli's tutorial is not correct, or you have not understood it correctly. In D there are dynamic arrays and fixed sized arrays. When you write: dchar[3][5] arr; You are allocating a fixed sized matrix in place (often on the stack or you are defining one inside another class instance or struct instance). The array-wise (vector operation) is done only one the last array, so this works: arr[2][] = '!'; But: arr[][2] = '!'; is a totally different thing. You are slicing the rows, and then you are assigning something to all the items of the third row. Always compile your D code with "-wi", it gives you a warning here, helping you avoid your mistake. I am asking all the time to produce warnings on default, but Walkter&Andrei don't even answer "No" to my request. Bye, bearophile
Jul 04 2013
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 07/04/2013 03:39 PM, Oleksiy wrote:

 1. What is the rationale behind "prefix declaration" of an array? Using
 right-to-left order to declare an array and left-to-right order to
 access elements seems confusing.
It seems to be confusing to people who are used to C and C++'s inside-out definition. In D, it is always "type and then the square brackets".
 2. Consider this code:
      dchar[3][5] arr = '.';
That is an array of 5 elements where the type of each element is dchar[3]. However, that is a confusing syntax because the right-hand side is not the same type as the elements, which is dchar[3]. Perhaps D supports it for C compatibility? It doesn't match the following. Here, the right-hand side is the same as the element type: int[2] arr2 = 42; assert(arr2 == [ 42, 42 ]); But this doesn't compile: char[3][5] arr = [ '.', '.', '.' ]; Error: mismatched array lengths, 15 and 3 I see that as a bug but can't be sure.
      arr[2][] = '!';
arr[2] is the third dchar[3] of arr. When [] is applied to it all of its elements are set to '!'.
      writeln();
      writeln(arr);

 Result: ["...", "...", "!!!", "...", "..."]
Looks correct.
 Which is expected. According to Ali Çehreli's tutorial, omitting the
 index of an array will result in operation being applied to the whole
 array (in this case element of another array).
The book either fails to mention the term "array-wise" or does not use it sufficiently but Ali Çehreli is correct in that arr[] means "all of the elements of arr".
 change the code:
 -   arr[2][] = '!';
 +   arr[][2] = '!';
arr[] is a slice to all of the elements of arr. When [2] is applied to it then you again get the third element.
 Still getting the same result: ["...", "...", "!!!", "...", "..."]
Still makes sense.
 I would expect to get: ["..!", "..!", "..!", "..!", "..!"]
Like C and C++, D does not have multi-dimensional arrays. You must do that yourself. One of the most straight-forward is by foreach: import std.stdio; void main() { dchar[3][5] arr = '.'; foreach (ref e; arr) { e[2] = '!'; } writeln(arr); } ["..!", "..!", "..!", "..!", "..!"]
 since omitted index would apply the operation to all elements of the
 first dimension of the array.
Sorry about that confusion but "a slice to all of the elements" and "array-wise operation" syntaxes are very similar. a[] alone is a slice to all of the elements of 'a'. When you use that syntax in an operation, then that operation is applied "array-wise": a[] = b[] + c[]; That is the same as a[i] = b[i] + c[i] for all valid values of 'i'. Ali
Jul 04 2013
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Ali Çehreli:

 However, that is a confusing syntax because the right-hand side 
 is not the same type as the elements, which is dchar[3]. 
 Perhaps D supports it for C compatibility?

 It doesn't match the following. Here, the right-hand side is 
 the same as the element type:

     int[2] arr2 = 42;
     assert(arr2 == [ 42, 42 ]);

 But this doesn't compile:

     char[3][5] arr = [ '.', '.', '.' ];

 Error: mismatched array lengths, 15 and 3

 I see that as a bug but can't be sure.
In D char literals as 'x' or even string literals as "xx" are seen as instances of all the types of strings and chars, it's not a bug, it's a feature: void main() { char x1 = 'x'; wchar x2 = 'x'; dchar x3 = 'x'; string s1 = "xx"; wstring s2 = "xx"; dstring s3 = "xx"; } There is a way to specify the type of a string, so this gives errors: void main() { string s1 = "xx"d; string s2 = "xx"w; wstring s3 = "xx"d; } Bye, bearophile
Jul 04 2013
next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 07/04/2013 06:43 PM, bearophile wrote:

 Ali Çehreli:

 However, that is a confusing syntax because the right-hand side is not
 the same type as the elements, which is dchar[3]. Perhaps D supports
 it for C compatibility?

 It doesn't match the following. Here, the right-hand side is the same
 as the element type:

     int[2] arr2 = 42;
     assert(arr2 == [ 42, 42 ]);

 But this doesn't compile:

     char[3][5] arr = [ '.', '.', '.' ];

 Error: mismatched array lengths, 15 and 3

 I see that as a bug but can't be sure.
In D char literals as 'x' or even string literals as "xx" are seen as instances of all the types of strings and chars, it's not a bug, it's a feature: void main() { char x1 = 'x'; wchar x2 = 'x'; dchar x3 = 'x'; string s1 = "xx"; wstring s2 = "xx"; dstring s3 = "xx"; } There is a way to specify the type of a string, so this gives errors: void main() { string s1 = "xx"d; string s2 = "xx"w; wstring s3 = "xx"d; } Bye, bearophile
Thanks for the refresher. My comment was about array initialization. We can define an array by a value on the right-hand side that is the same as the element types: int[2] arr2 = 42; assert(arr2 == [ 42, 42 ]); Good: Every element has the value 42. Now I have another array where the elements are of type int[3]: int[3][2] arr = [ 10, 20, 30 ]; Error: mismatched array lengths, 6 and 3 Do you see the inconsistency? The element type is int[3] and the initial value is [ 10, 20, 30 ]. However there is a compilation error. Further, the code compiles even if I use a different type as the initial value: int[3][2] arr = 10; I would expect that to be an error because the element type is not an int but int[3]. We know that an int[3] can be initialized by 10 but a 10 should not be allowed to be used as an int[3]. That was my point. Ali
Jul 04 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Ali Çehreli:

     int[3][2] arr = 10;

 I would expect that to be an error because the element type is 
 not an int but int[3]. We know that an int[3] can be 
 initialized by 10 but a 10 should not be allowed to be used as 
 an int[3].

 That was my point.
I see. That's another feature :-) Bye, bearophile
Jul 05 2013
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 5 July 2013 at 01:53:22 UTC, Ali Çehreli wrote:
 Now I have another array where the elements are of type int[3]:

     int[3][2] arr = [ 10, 20, 30 ];

 Error: mismatched array lengths, 6 and 3

 Ali
Combined with the fact that *this* works: int[3][2] arr = [ 10, 20, 30, 10, 20, 30 ]; Seems like a bug to me. I'd file it, personally.
Jul 05 2013
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 5 July 2013 at 01:43:45 UTC, bearophile wrote:
 There is a way to specify the type of a string, so this gives 
 errors:

 void main() {
     string s1  = "xx"d;
     string s2  = "xx"w;
     wstring s3 = "xx"d;
 }

 Bye,
 bearophile
Also, don't forget the often forgotten explicit c suffix: dstring s = "hello"c; //Error: cannot implicitly convert expression ("hello"c) of type string to immutable(dchar)[]
Jul 05 2013
prev sibling next sibling parent reply "Oleksiy" <oleksiy.pavlyuk gmail.com> writes:
 However, that is a confusing syntax because the right-hand side 
 is not the same type as the elements, which is dchar[3]. 
 Perhaps D supports it for C compatibility?
Yes, I noticed: arr = '!'; Error: cannot implicitly convert expression ('!') of type char to dchar[3u][] Look like there is a consequence - can't perform array-wise operations on multidimensional arrays? And that's why a need to touch the elements themselves, like in the example you provided: import std.stdio;
 void main()
 {
     dchar[3][5] arr = '.';

     foreach (ref e; arr) {
         e[2] = '!';
     }

     writeln(arr);
 }
   a[] alone is a slice to all of the elements of 'a'. When you 
 use that syntax in an operation, then that operation is applied 
 "array-wise":

   a[] = b[] + c[];

 That is the same as

   a[i] = b[i] + c[i]

 for all valid values of 'i'.
I truly got what the problem is after this part. Thanks for a very detailed explanation. Also huge thank you for your book - holy grail for beginners. --------------------------------
 But this doesn't compile:

     char[3][5] arr = [ '.', '.', '.' ];

 Error: mismatched array lengths, 15 and 3

 I see that as a bug but can't be sure.
Others seem to agree with you, will you be submitting this bug? --Oleksiy
Jul 05 2013
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 07/05/2013 09:00 PM, Oleksiy wrote:

 Look like there is a consequence - can't perform array-wise operations
 on multidimensional arrays?
I don't want to accept that conclusion because there is no such thing as a multidimensional array in D. :) (To be fair, they don't exist in C and C++.) What we call a multidimensional array is achieved by defining arrays where the elements are arrays themselves. But D does not know or care about that. As a result, array-wise operations apply only to the outermost layer of so-called multidimensional arrays. Let's have the following two-dimensional arrays: int[][] a; int[][] b; int[][] c; We can still use the array-wise syntax: c[] = a[] OP b[]; Let's think about what can appear as an operator instead of OP above... Since the elements of the outer layers of the array are themselves arrays (more correctly, slices), then OP must be an operator that can have two slices on its side. The concatenation operators comes to mind but I could not make it work: c[] = a[] ~ b[]; That should have the same effect as concatenating the corresponding elements of a and b and assigning the result to c. It does not work.
 And that's why a need to touch the elements
 themselves, like in the example you provided:

 import std.stdio;
 void main()
 {
     dchar[3][5] arr = '.';

     foreach (ref e; arr) {
         e[2] = '!';
     }

     writeln(arr);
 }
That example is doing what you needed to do: Assign a specific value to a column of the multidimensional array. Unfortunately, there is no special syntax in D to do that.
 Also huge thank you for your book - holy grail for beginners.
Thank you very much for the kind words; makes me very happy.
 --------------------------------
 But this doesn't compile:

     char[3][5] arr = [ '.', '.', '.' ];

 Error: mismatched array lengths, 15 and 3

 I see that as a bug but can't be sure.
Others seem to agree with you, will you be submitting this bug?
As TommiT did, it is better to try a code that has a fixed-length array on the right-hand side: void main() { int[3] values = [ 1, 2, 3 ]; int[3][2] a = values; } Error: mismatched array lengths, 6 and 3 Reported: http://d.puremagic.com/issues/show_bug.cgi?id=10562 Ali
Jul 06 2013
prev sibling parent "TommiT" <tommitissari hotmail.com> writes:
On Friday, 5 July 2013 at 01:31:00 UTC, Ali Çehreli wrote:
 But this doesn't compile:

     char[3][5] arr = [ '.', '.', '.' ];

 Error: mismatched array lengths, 15 and 3

 I see that as a bug but can't be sure.
I'd file a bug report, but since [x, y, z] is primarily a dynamic array literal, I'd file it like this: struct S { int[3] values; } void main() { alias A = S; alias B = int[3]; A[2] a = A.init; // OK B[2] b = B.init; // Error: mismatched array lengths, 6 and 3 }
Jul 05 2013