14
$\begingroup$
list = {1, x, 3, x, 4, x, x, 3, 4, 3, x, 2, 3, x, 8, 7};

If there is exactly one integer between two x I want to replace it with y:

list //. {a___, x, _Integer, x, b___} :> {a, x, y, x, b}

{1, x, y, x, y, x, x, 3, 4, 3, x, 2, 3, x, 8, 7}

How can I replace one or more integers with y?

Desired result with n = 2:

{1, x, y, x, y, x, x, 3, 4, 3, x, y, y, x, 8, 7}

With n = 3:

{1, x, y, x, y, x, x, y, y, y, x, y, y, x, 8, 7}

$\endgroup$
15
$\begingroup$

Let's try:

list = {1, x, 3, x, 4, x, x, 3, 4, 3, x, 2, 3, x, 8, 7};

list //. {a___, x, s : Repeated[_Integer, 2], x, b___} :>
  {a, x, Sequence @@ ConstantArray[y, Length[{s}]], x, b}
(* -> {1, x, y, x, y, x, x, 3, 4, 3, x, y, y, x, 8, 7} *)

list //. {a___, x, s : Repeated[_Integer, 3], x, b___} :>
  {a, x, Sequence @@ ConstantArray[y, Length[{s}]], x, b}
(* -> {1, x, y, x, y, x, x, y, y, y, x, y, y, x, 8, 7} *)

Seems to do the job. I'm not sure if it's the most efficient approach.

$\endgroup$
12
$\begingroup$

This is almost a perfect application for the new SequencePosition function, which lets you pick patterns out of lists in a straightforward way. It's made a bit more awkward than necessary by a weird performance issue, but it allows for a solution that is both reasonably elegant and reasonably efficient:

replaceBetween[list_, n_Integer?Positive, sym_Symbol, new_Symbol] :=
   With[{
    positions =
     Flatten@Map[
        Range[#1 + 1, #2 - 1] & @@@ 
          SequencePosition[list, 
           (* this weird construction is necessary because 
              SequencePosition performs horribly with patterns 
              containing Repeated *)
           Flatten@{sym, ConstantArray[_Integer, #], sym}] &][
       Range[n]]},
 ReplacePart[list, Thread[positions -> new]]];
$\endgroup$
11
$\begingroup$

A somewhat quicker method in the same rule-based paradigm would be to use ReplaceList to find all starting positions of all groups at once, and then make a batch replacement. Here is a possible implementation:

startPos[lst_, n_, sym_: x] :=
  ReplaceList[
    lst, 
    {l___, sym, Repeated[_Integer, {n}], sym, ___} :> Length[{l}] + 2
  ];

and the main function:

replace[lst_, n_, obj_: x, newobj_: y] :=
  Module[{copy = lst},
    copy[[Apply[Join]@Map[Range[#, # + n - 1] &]@startPos[lst, n, obj]]] = newobj;
    copy
  ];

I did not benchmark, but the method based on ReplaceRepeated must have a quadratic complexity in the number of groups being replaced, while the one presented here should be linear time.

$\endgroup$
8
$\begingroup$

Using SequencePosition I create a function called sequenceReplace:

sequenceReplace[list_List, pattern:(pat:{p_}:> rhs_)]:=Replace[list,p :> rhs, 1]
sequenceReplace[list_List, pattern:(pat_List:> rhs_)]:=Block[{position, positionRules, newList = list},
    position = SequencePosition[list, pat, Overlaps->False];
    positionRules = <|"pos"-> First@#, "del"-> (#[[1]]+1;;#[[2]]), "new"-> FirstCase[{Take[list,#]}, pattern]|>&/@position;
    Map[(newList[[#["del"]]] = Nothing; newList[[#["pos"]]] = #["new"])&, Reverse[positionRules]];
    newList
]

The idea is to make easier to handle sequence replacements like:

list = {1, "a", 1, "b", 2, 2, 3}
sequenceReplace[list, {s_String, n_Integer} :> StringRiffle[{s, n}, ":"]]

{1, "a:1", "b:2", 2, 3}

For this question, It can be used as:

list = {1, x, 3, x, 4, x, x, 3, 4, 3, x, 2, 3, x, 8, 7};
n = 2;
sequenceReplace[list, {x,z:Repeated[_Integer,n],x}:> Sequence@@Flatten@{x,ConstantArray[y, Length@{z}],x}]

{1, x, y, x, 4, x, x, 3, 4, 3, x, y, y, x, 8, 7}

Strangely, as mentioned by @Pillsy, SequencePositon has a weird performance with Repeated, that makes @Oleksandr R. response with a better response time.

$\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.