14

On trying to write this answer I reached the frustrating realization that I didn't have an efficient way to delete a list of columns or deeper level components in a simple way as Part gives.

Given

MatrixForm[m = Partition[Partition[Range[4 4 3], 3], 4]]

Mathematica graphics

I can Delete rows {2,3} by

Delete[m, List /@ {2, 3}] // MatrixForm

Mathematica graphics

But to delete the columns or deeper levels I would need to Transpose twice. For instance using something like this

rDelete[m_, row_, col_] := Delete[
  Transpose[
   Delete[
    Transpose[m]
    , List /@ col
    ]
   ], List /@ row
  ]

Mathematica graphics

On the other hand to get a Part at any level I can easily use

Part[m, All, {1, 4}, {2, 3}] // MatrixForm

Mathematica graphics

Unfortunately, All and Span are not available for Delete.

Question: How can we delete columns or whole higher levels elegantly and efficiently, as we do with Part?

rhermans
  • 36,518
  • 4
  • 57
  • 149
  • 3
    This removes the second "column" of m: Drop[m, None, {2}, None]. Drop[m, None, None, {2}] gives the complement of your Part[] example. You can judiciously combine this with Map[]/MapAt[] if you need to do several positions, since unlike Delete[], Drop[] can only remove from either a single dimension or a range of dimensions. – J. M.'s missing motivation Feb 25 '16 at 16:12
  • @J.M. I guess the problem with Drop is the fact that you need to Map over the list of indexes to delete, as you say. – rhermans Feb 25 '16 at 16:55

2 Answers2

13

You can actually use Part for that:

ClearAll[delete];
delete[expr_, specs___] :=
  Module[{copy = expr},
    copy[[specs]] = Sequence[];
    copy
  ];

So that for example

delete[m, All, All, 2]

(* 
   {
     {{1, 3}, {4, 6}, {7, 9}, {10, 12}}, 
     {{13, 15}, {16, 18}, {19, 21}, {22, 24}}, 
     {{25, 27}, {28, 30}, {31, 33}, {34, 36}}, 
     {{37, 39}, {40, 42}, {43, 45}, {46, 48}}
   }
*)

Note that this is not exactly equivalent to Delete in all cases, since sequence splicing is an evaluation-time effect, so the results will be different if you delete inside held expressions - in which case the method I suggested may not work.

Here is a version that would probably be free of the mentioned flaw, but will be slower:

ClearAll[deleteAlt];
deleteAlt[expr_, specs___] :=
  Module[{copy = Hold[expr], tag},
    copy[[1, specs]] = tag;
    ReleaseHold@Delete[copy, Position[copy, tag]]
  ];

You can test both on say, Hold[Evaluate[m]], with the spec 1, All, All, 2, to see the difference.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • This opens for me a whole a new understanding of Sequence. Very clever! +1 – rhermans Feb 25 '16 at 16:11
  • 7
    The Sequence[] is replaceable with Nothing in version 10.2, I suppose. Speaking of which: MapAt[Nothing, m, {All, All, 2}]. – J. M.'s missing motivation Feb 25 '16 at 16:15
  • @J.M. I am aware of Nothing, but haven't yet gained extensive experience with it, unlike with Sequence. As to MapAt, this is a possibility, but it was known to have efficiency issues in the past, for large number of positions mapped. I didn't have the time to check it now, so I didn't go that way - for in-place assignments with Part, it has been known that it has excellent efficiency in all cases. – Leonid Shifrin Feb 25 '16 at 16:24
  • @rhermans Glad it helped. – Leonid Shifrin Feb 25 '16 at 16:26
  • It works well for a list at a single level. The output of using list at more than one level is not exactly what I had in mind, but this is already better than my own solution. Example delete[m, All, {2, 3}, {1, 3}] – rhermans Feb 25 '16 at 18:20
  • @rhermans I tested delete[m, All, {2, 3}, {1, 3}], and the result looks logical to me: from all first-level elements (sub-matrices), delete columns 1 and 3 from rows 2 and 3. The resulting first-level elements are, of course, no longer matrices (rectangular arrays). What result did you expect from this code? – Leonid Shifrin Feb 25 '16 at 18:32
  • 1
    I don't want to move the goalpost, I'm grateful for your answer and the code is unambiguous. What I was expecting (naively?) is deleted columns, rows and higher orders all across the array, like in my example with rDelete, where all the elements have the same length. But this already solves most of what I wanted nicely. – rhermans Feb 25 '16 at 18:41
  • 1
    @rhermans This is then different semantics. Here is a version that works that way: ClearAll[delete2];delete2[expr_, part_, rest___] :=Module[{copy = expr}, copy[[part]] = Sequence[];Map[delete2[#, rest] &, copy]]; delete2[expr_] := expr;. But it won't be as efficient as the ones I posted before. – Leonid Shifrin Feb 25 '16 at 19:04
10

You may use ReplacePart and Nothing with Blank (_) for All.

Delete second entry of all submatrices

ReplacePart[m, {_, _, 2} -> Nothing] // MatrixForm

enter image description here

Delete last column

ReplacePart[m, {_, -1} -> Nothing] // MatrixForm

enter image description here

and so on.

Hope this helps.

Edmund
  • 42,267
  • 3
  • 51
  • 143