17
$\begingroup$

I quite often run into a situation where I want to treat a list as a circular (repeating) list, and want to take a specific sublist of it, such as...

One past the end of the list:

Append[#, First@#] & @ {a, b, c}

{a, b, c, a}

An item preceding the list included:

Prepend[#, Last@#] & @ {a, b, c}

{c, a, b, c}

Three rounds of the circular list from the start:

Join[#, #, #] & @ {a, b, c}

{a, b, c, a, b, c, a, b, c}

Or even every second item on the list, for two rounds:

Join[#, #][[;; ;; 2]] & @ {a, b, c}

{a, c, b}

Obviously there are more of these where you can combine extended features of Part (the [[ ... ]] syntax) over circular lists.

What would be the most practical (short, clean, efficient, maybe even elegant) ways to do this, without writing one-off code every time such a small need arises?

$\endgroup$
22
$\begingroup$

Perhaps ArrayPad:

ClearAll[f1]
f1  = ArrayPad[##, #] &;

Examples:

f1[{a, b, c, d}, {0, 1}]

{a, b, c, d, a}

f1[{a, b, c, d}, {1, 0}]

{d, a, b, c, d}

f1[{a, b, c, d}, {12, 0}]

{a, b, c, d, a, b, c, d, a, b, c, d, a, b, c, d}

Alternatively, you can use "Periodic" as the third argument in ArrayPad:

ClearAll[f2]
f2 = ArrayPad[##, "Periodic"] &

Update: We can combine ArrayPad and Part:

ClearAll[f0]
f0[a_, b_, c_: All] := ArrayPad[a, b, a][[c]]

Examples:

f0[{a, b, c, d}, {0, 1}](* append first *)

{a, b, c, d, a}

f0[{a, b, c, d}, {1, 0}] (* prepend last *)

{d, a, b, c, d}

f0[{a, b, c, d}, {1, 1}](*append first and prepend last*)

{d, a, b, c, d, a}

f0[{a, b, c, d}, {1, -1}](* rotate right *)

{d, a, b, c}

f0[{a, b, c, d}, {-1, 1}] (* rotate left *)

{b, c, d, a}

f0[{a, b, c, d}, {0, 8}] (* repeat *)

{a, b, c, d, a, b, c, d, a, b, c, d}

f0[{a, b, c, d}, {9, -1}] (* rotate right and repeat *)

{d, a, b, c, d, a, b, c, d, a, b, c}

f0[{a, b, c, d}, {-1, 9}] (* rotate left and repeat *)

{b, c, d, a, b, c, d, a, b, c, d, a}

f0[{a, b, c, d}, {0, 0}, -1 ;; 1 ;; -1] (* reverse *)

{d, c, b, a}

f0[{a, b, c, d}, {0, 8}, ;; ;; 2] (*repeat and take odd parts*)

{a, c, a, c, a, c}

f0[{a, b, c, d}, {0, 8}, {1, 3, 4, 7, 9}] (*repeat and take parts 1,3,4,7 and 9*)

{a, c, d, c, a}

$\endgroup$
16
$\begingroup$

It turns out we can do all the required transformations using only Partition using the (undocumented) sixth argument to do arbitrary-post-processing:

ClearAll[☺]
☺[a_, b_, c_, d_: ;;] := Partition[a, b, b, c, a, ## & @@ {##}[[d]] &];

Examples:

☺[{a, b, c, d}, 5, 1] (* append first*)

{a, b, c, d, a}

☺[{a, b, c, d}, 5, 2] (* prepend last*)

{d, a, b, c, d}

☺[{a, b, c, d}, 4, 2] (*rotate right *)

{d, a, b, c}

☺[{a, b, c, d}, 4, -1] (*rotate left *)

{b, c, d, a}

☺[{a, b, c, d}, 4, 1, -1 ;; 1 ;; -1] (* reverse *)

{d, c, b, a}

☺[{a, b, c, d}, 12, 1] (* repeat *)

{a, b, c, d, a, b, c, d, a, b, c, d}

☺[{a, b, c, d}, 12, 1, ;; ;; 2] (* repeat and take odd parts *)

{a, c, a, c, a, c}

☺[{a, b, c, d}, 12, 1, {1, 3, 4, 7, 9}] (* repeat and take parts 1,3,4,7 and 9*)

{a, c, d, c, a}

NOTE: Unfortunately, works only in versions prior to 12.0.0; it doesn't work in version 12.0.0 (thanks: Christopher Lamb).

Update: The same idea used with Part as in MannyC's answer:

ClearAll[☺☺]
☺☺[a_, b_, c_, d_: All] := a[[Mod[c - 1 + Range[b], Length@a, 1][[d]]]];

Examples:

☺☺[{a, b, c, d}, 5, 1] (* append first *)

{a, b, c, d, a}

☺☺[{a, b, c, d}, 5, 4] (* prepend last *)

{d, a, b, c, d}

☺☺[{a, b, c, d}, 4, 4](* rotate right *)

{d, a, b, c}

☺☺[{a, b, c, d}, 4, 2](* rotate left *)

{b, c, d, a}

☺☺[{a, b, c, d}, 4, 1, -1 ;; 1 ;; -1] (* reverse*)

{d, c, b, a}

☺☺[{a, b, c, d}, 12, 1] (* repeat *)

{a, b, c, d, a, b, c, d, a, b, c, d}

☺☺[{a, b, c, d}, 12, 1, ;; ;; 2] (*repeat and take odd parts*)

{a, c, a, c, a, c}

 ☺☺[{a, b, c, d}, 12, 1, {1, 3, 4, 7, 9}] (*repeat and take parts 1,3,4,7 and 9*)

{a, c, d, c, a}

$\endgroup$
  • 2
    $\begingroup$ I really think this solution wins in generality what it loses in elegance! $\endgroup$ – kirma Mar 5 '20 at 19:06
  • 1
    $\begingroup$ Chin-scratchingly, the only single-smiley for which I get the expected result is reverse. E.g., append first gives {a, b, c, d, {a, b, c, d}}, rotate right gives {{a, b, c, d}, a, b, c}, and repeat gives {a, b, c, d, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}, {a, b, c, d}} $\endgroup$ – Christopher Lamb Mar 11 '20 at 15:59
  • $\begingroup$ @ChristopherLamb, thank you. Single-smiley works in versions prior to 12.0.0 but it doesn't work in version 12.0.0 (Wolfram Cloud) (yet another reminder not to rely on undocumented "features"). I added a note on version-dependence. $\endgroup$ – kglr Mar 11 '20 at 16:51
12
$\begingroup$

This requires a bit of redundant memory but it also opens the door to vectorization:

this = {a, b, c};
next = RotateLeft[this];
prev = RotateRight[this];

Now you can do cute things like this:

(next - 2 this + prev)/h^2
$\endgroup$
11
$\begingroup$

Another possibility is Partition

Partition[{a, b, c},4,4,1]//Flatten

{a, b, c, a}

Partition[{a, b, c},4,5,2]//Flatten

{c, a, b, c}

Partition[{a, b, c},9,9,1]//Flatten

{a, b, c, a, b, c, a, b, c}

$\endgroup$
  • $\begingroup$ This is quite elegant and short, apart from the need of Flatten... $\endgroup$ – kirma Mar 5 '20 at 10:10
  • 2
    $\begingroup$ @kirma Flatten should come for free for packed arrays... $\endgroup$ – Henrik Schumacher Mar 5 '20 at 11:15
  • $\begingroup$ ... but you are probably speaking about elegance. $\endgroup$ – Henrik Schumacher Mar 5 '20 at 15:43
9
$\begingroup$

I would define a part function as follows

part[list_, n_Integer] := list[[Mod[n, Length@list, 1]]];
part[list_, n__Span]   := With[{nn = {m, Sequence @@ n}}, 
                              Table[list[[Mod[m, Length@list, 1]]], nn]];

This version of part works exactly like Part but uses Mod to wrap around. There is an obvious difference: with this definition part[x,0] returns the last element of x and not his Head like Part would do.

Let's see some examples. Setting

list = {1,2,3,4};

we have

part[list, 7]

3

part[list, 0 ;; 5]

{4, 1, 2, 3, 4, 1}

part[list, 2 ;; 11 ;; 3]

{2, 1, 4, 3}

If we want to use an empty Span to indicate "until the end" we need to come up with some syntax to tell the function when to stop. So we can add an extra argument for the number of rounds.

part[list_, n__Span, rounds_] := part[list, n /. All -> rounds*Length[list]];

Which results in

part[list,2 ;;, 2]

{2, 3, 4, 1, 2, 3, 4}

part[list, 0 ;; ;; 3, 3]

{4, 3, 2, 1, 4}


For completeness I report here in order the examples of the question

part[{a, b, c}, 1 ;; 4]       (*Apppend first*)
part[{a, b, c}, 0 ;;, 1]      (*Prepend last*)
part[{a, b, c}, 1 ;;, 3]      (*Three rounds*)
part[{a, b, c}, 1 ;; ;; 2, 2] (*Every second item two rounds*)

{a, b, c, a}
{c, a, b, c}
{a, b, c, a, b, c, a, b, c}
{a, c, b}

$\endgroup$
3
$\begingroup$

One convenient solution is to alter the way Part works instead of creating a new structure. An easy way to achieve this is as follows:

Unprotect[Part];
Part /: Part[List[a_, b__], 1] := a;
Part /: Part[List[a__, b_], -1] := b;
Part /: Part[List[a_, b__], n_?Positive] := With[{m = n - 1}, Part[List[b, a], m]];
Part /: Part[List[a__, b_], n_?Negative] := With[{m = n + 1}, Part[List[b, a], m]];
Protect[Part];

With this overloading, Part behaves as before except it treats the list as circular:

{a, b, c}[[4]]
(* a *)
{a, b, c}[[-1]]
(* c *)

The zeroth component still refers to the head:

{a, b, c}[[0]]
(* List *)

We can of course overload other properties of Part to achieve greater flexibility:

Unprotect[Part];
Part /: Part[List[a__], {b__}] := Table[Part[List[a], c$], {c$, {b}}];
Protect[Part];

enables

{a,b,c}[[{1,2,3,4}]]
(* {a, b, c, a} *)

whereas

Unprotect[Part];
Part /: Part[List[a__], Span[min_, max_]] := Table[Part[List[a], b$], {b$, min, max}];
Part /: Part[List[a__], Span[min_, max_, steps_]] := Table[Part[List[a], b$], {b$, min, max, steps}];
Protect[Part];

enables

{a, b, c}[[1 ;; 10]]
(* {a, b, c, a, b, c, a, b, c, a} *)
{a, b, c}[[1 ;; 7 ;; 2]]
(* {a, c, b, a} *)

which are standard utilities of Part, except now all lists are treated as compactified.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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