19
$\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$
1

6 Answers 6

24
$\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$
18
$\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: doesn't work in version 12.0.0 (thanks: Christopher Lamb). It works in version 12.3 and in versions prior to 12.0.

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
  • 2
    $\begingroup$ I really think this solution wins in generality what it loses in elegance! $\endgroup$
    – kirma
    Commented Mar 5, 2020 at 19:06
  • $\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
    Commented Mar 11, 2020 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$
12
$\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$
3
  • $\begingroup$ This is quite elegant and short, apart from the need of Flatten... $\endgroup$
    – kirma
    Commented Mar 5, 2020 at 10:10
  • 2
    $\begingroup$ @kirma Flatten should come for free for packed arrays... $\endgroup$ Commented Mar 5, 2020 at 11:15
  • $\begingroup$ ... but you are probably speaking about elegance. $\endgroup$ Commented Mar 5, 2020 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$
4
$\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 and acknowledge you have read our privacy policy.

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