13
$\begingroup$

I want to write a function that generates a square matrix from sublists. My sublists are

a = Range[0, x, 0.5]; b = Range[0.25, x + 0.25, 0.5];

Suppose x=2, then I can manually evaluate {a,b,a,b,a} to generate an 5x5 matrix. I want to make the matrix automatically, of course, but without using a loop.

I've tried different ways to do it, with Nest, Range and Table, but I can't make it work.

Do I absolutely have to use a loop?

$\endgroup$
0

9 Answers 9

9
$\begingroup$

Perhaps this:

x = 2;
a = Range[0, x, 0.5];
b = Range[0.25, x + 0.25, 0.5];

PadRight[#, Length@#[[1]], #] & @ {a, b}

Could also be written:

PadRight[{a,b}, Length@a, {a,b}]

Performance

There is a reason to use the form I proposed over that which Heike gave. If the first argument of PadRight is a packed array, and the padding list is packable (but not necessarily packed) the result will also be packed, and it is produced more quickly than if it were not packed.

foo = Range[5]; (* packed array *)

(r1 = PadRight[{},  5*^6, foo];) // RepeatedTiming
(r2 = PadRight[foo, 5*^6, foo];) // RepeatedTiming

Developer`PackedArrayQ /@ {r1, r2}
Divide @@ ByteCount /@ {r1, r2} // N
{0.047, Null}

{0.0096, Null}

{False, True}

2.99999

So using the list to pad as the seed instead of {} can result in five times better speed and three times better memory consumption.

However as Karsten 7. notes this doesn't actually help with the question example. In either case the vector elements are kept packed but the outer list is not:

Needs["Developer`"]

PackedArrayQ /@ PadRight[#,  Length@#[[1]], #] &@{a, b}
PackedArrayQ /@ PadRight[{}, Length@#[[1]], #] &@{a, b}
{True, True, True, True, True}
{True, True, True, True, True}

So while in principle it is better to pad the input list rather than {} as some cases greatly benefit from it, this case does not.

$\endgroup$
4
  • 3
    $\begingroup$ The version listed in Heike's answer is slightly shorter than (and not too different from) the one given here... $\endgroup$ Feb 11, 2012 at 23:38
  • 2
    $\begingroup$ I ran AbsoluteTiming[] on every answer given, and this is the one that was the fastest on my system (with x=35). I felt like I had to choose, and well.. I accepted this answer. The difference between Heike's answer and this one is that Mr.Wizard's is a pure function, a concept that I had not encountered before. $\endgroup$
    – CHM
    Feb 12, 2012 at 2:38
  • $\begingroup$ Unfortunately {a,b} is not a packed array. I tried replacing {a,b} with ab defined as ab = Developer`ToPackedArray[{a, b}];, but PadRight[ab, Length@a, ab] // Developer`PackedArrayQ is False. Is there any way to get the result as a packed array? $\endgroup$
    – Karsten7
    Jul 29, 2015 at 22:01
  • $\begingroup$ @Karsten It seems that the individual elements (a, b) are packed but the outer list is not, in either case. I amended my answer to note this. $\endgroup$
    – Mr.Wizard
    Jul 29, 2015 at 22:24
13
$\begingroup$

Since b is a+0.25, you could use Outer like this:

Outer[Plus, PadRight[{}, Length[a], {0, 0.25}], a]

You could also create the list of 0s and 0.25s using Riffle as in the other answers

(you guys are so fast! It makes iPad use a real handicap :)

$\endgroup$
0
12
$\begingroup$

You could do something like

mat[x_] := Module[{a = Range[0, x, .5], b = Range[.25, x + .25, .5]},
  Riffle[ConstantArray[a, x + 1], {b}]]

Then mat[2] gives

{{0., 0.5, 1., 1.5, 2.}, {0.25, 0.75, 1.25, 1.75, 2.25}, {0., 0.5, 1.,
   1.5, 2.}, {0.25, 0.75, 1.25, 1.75, 2.25}, {0., 0.5, 1., 1.5, 2.}}

Edit

Perhaps more elegant. This should also work if x isn't an integer.

mat[x_] := Module[{a = Range[0, x, .5], b = Range[.25, x + .25, .5]},
  PadRight[{}, Floor[2 x + 1], {a, b}]]
$\endgroup$
15
  • 1
    $\begingroup$ The function is defined as f[x_Integer] :) $\endgroup$
    – CHM
    Feb 11, 2012 at 22:44
  • 1
    $\begingroup$ @Mr.Wizard I think you owe me 15 rep ;-) $\endgroup$
    – Heike
    Feb 11, 2012 at 23:46
  • 1
    $\begingroup$ Heike I would have deleted my answer but CHM already Accepted it. I have to leave right now but I think "what to do in this situation" is worthy of a Meta post. Sorry. :-/ $\endgroup$
    – Mr.Wizard
    Feb 11, 2012 at 23:55
  • 1
    $\begingroup$ @Mr. Wizard: you do know that moderators are capable of deleting accepted answers, no? $\endgroup$ Feb 12, 2012 at 0:14
  • 1
    $\begingroup$ @Mr. Wizard, as you wish. The situation just doesn't sit well with me, but I leave this to your judgment. $\endgroup$ Feb 12, 2012 at 4:03
9
$\begingroup$

One possibility is this:

Riffle[
 ConstantArray[a, Ceiling[Length[a]/2]], 
 ConstantArray[b, Floor[Length[a]/2]]
]
$\endgroup$
4
  • $\begingroup$ @CHM Actually you could replace the second ConstantArray[...] by {b} to simplify things a bit. $\endgroup$
    – Szabolcs
    Feb 11, 2012 at 22:56
  • 1
    $\begingroup$ Floor[Length[a]/2] is more conventionally written as Quotient[Length[a], 2]. $\endgroup$ Feb 11, 2012 at 22:59
  • $\begingroup$ @J.M. Well, conventions change from place to place so it depends who you ask :-) I use that form as well sometimes, but here what I really needed was the Ceiling, and the Floor is more symmetrical with that. Also, I realized the Floor part wasn't necessary as a simple {b} would do as in @Heike's answer ... $\endgroup$
    – Szabolcs
    Feb 11, 2012 at 23:08
  • $\begingroup$ This is the method I was thinking of, or at least close enough that I don't have to bother putting an answer together. $\endgroup$
    – rcollyer
    Feb 12, 2012 at 1:54
8
$\begingroup$

Here is one way without a loop:

Take[Flatten[ConstantArray[{a, b}, {Ceiling[Length[a]/2]}], 1], Length[a]]

You could also use Riffle, I guess.

$\endgroup$
7
  • $\begingroup$ @CHM You mean I used loops for this kind of stuff in the book ?! I thought better of myself. $\endgroup$ Feb 11, 2012 at 22:44
  • $\begingroup$ Quite the opposite - you suggest NOT to use loops, and make use of Mathematica's functional programming capabilities instead. As I'm learning to use Mathematica, I'd rather learn to code efficiently. $\endgroup$
    – CHM
    Feb 11, 2012 at 22:45
  • $\begingroup$ @CHM Well, that's a relief. I started thinking I did use loops, which would mean that I wasn't in the right mind when writing that part. $\endgroup$ Feb 11, 2012 at 22:48
  • 1
    $\begingroup$ @CHM In fact, today is an anniversary - it is exactly 3 years that I released the book to the general audience (it was written one year prior to that, but I did not have a web version). $\endgroup$ Feb 11, 2012 at 22:51
  • $\begingroup$ Congratulations on being first to 5000! :-) $\endgroup$
    – Mr.Wizard
    Feb 12, 2012 at 4:32
7
$\begingroup$

I find using Band in a SparseArray interesting.

mat[x_] :=
 Block[{a = Range[0, x, .5],
   b = Range[.25, x + .25, .5], ln},
   ln = Length[a];
   SparseArray[Band[{1, 1}, {ln, ln}] -> {a, b}, {ln, ln}]
  ]
$\endgroup$
6
$\begingroup$

You've gotten several good answers already. Another way of doing it is to use MapIndexed as:

MapIndexed[If[EvenQ@First@#2, b, a] &, a];

This should be reasonably fast, but certainly not as fast as the PadRight answer. In the same vein, you can use related conditional constructs, Switch and Which as:

 MapIndexed[Switch[EvenQ@First@#2, True, b, False, a] &, a]; 
 MapIndexed[Which[Mod[#2, 2] == {1}, a, Mod[#2, 2] == {0}, b] &, a];
$\endgroup$
5
$\begingroup$

Or ...

Using ConstantArray:

 mat0 := Most@Flatten[#, 1] & /@ 
 ({#, .25 + #} & /@ ConstantArray[#, Ceiling[(Length@#)/2]]&@
 Range[0, #, .5] &) ;
 [email protected]
 (* gives *)

{{0., 0.5, 1., 1.5, 2.}, {0.25, 0.75, 1.25, 1.75, 2.25}, {0., 0.5, 1., 1.5, 2.}, {0.25, 0.75, 1.25, 1.75, 2.25}, {0., 0.5, 1., 1.5, 2.}}

Using Table:

 mat2:=Most@Flatten[#, 1] & /@ (Transpose /@ 
  Table[{#, .25 + #} & /@ #, {Ceiling[(Length@#1)/2]}] &@ Range[0, #, .5] &)

Using NestList:

 mat3:=Most@Flatten[#, 1] & /@ 
(NestList[Join, {#1, #2}, Floor[(Length@#1)/2]] & @@ {#, .25 + #} &
 @Range[0, #, .5] &)

Using Table again (less cluttered and more general):

 mat4:= Table[{#2, #1}[[1 + Mod[i, 2]]], {i, #3}] &;
 mat5 := Table[{#1, #2}[[1 + Mod[i, 2]]], {i, 0, #3 - 1}] &;
 mat4[{a, b, c, d}, {e, f, g}, 5]
 (* and  *)
 mat5[{a, b, c, d}, {e, f, g}, 5]
 (* both give *)
{{a, b, c, d}, {e, f, g}, {a, b, c, d}, {e, f, g}, {a, b, c, d}}
$\endgroup$
1
  • $\begingroup$ Nice. Clearly, my methods using Table and Nest(List) weren't as involved as yours, but they didn't work either. $\endgroup$
    – CHM
    Feb 12, 2012 at 2:17
3
$\begingroup$

Let me join. This variant avoids explicit a and b initialization.

x = 2; n = 2 x + 1;
SparseArray[{
   {i_, j_} /; OddQ@i -> 0.5 (j - 1),
   {i_, j_} /; EvenQ@i -> 0.5 j - 0.25},
{n, n}] // Normal

(* ==>
    {{0, 0.5, 1., 1.5, 2.}, 
     {0.25, 0.75, 1.25, 1.75, 2.25}, 
     {0, 0.5, 1., 1.5, 2.},
     {0.25, 0.75, 1.25, 1.75, 2.25},
     {0, 0.5, 1., 1.5, 2.}} *)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.