7
$\begingroup$

As the title, if I have a list:

{"", "", "", "2$70", ""}

I will expect:

{"", "", "", "2$70", "2$70"}

If I have

{"", "", "", "3$71", "", "2$72", ""}

then:

{"", "", "", "3$71", "3$71", "2$72", "2$72"}

And

{"", "", "", "3$71", "","", "2$72", ""}

should give

{"", "", "", "3$71", "3$71", "", "2$72", "2$72"}

This is my try:

{"", "", "", "2$70", ""} /. {p : Except["", String], ""} :> {p, p}

But I don't know why it doesn't work. Poor ability of pattern match. Can anybody give some advice?

$\endgroup$
5
  • $\begingroup$ most probably a duplicate but hard to find it. here is one way: Module[{last = "", f}, f[""] := last; f[x_] := last = x; f /@ # ] &@{"", "", "", "2$70", ""} $\endgroup$ Commented Feb 11, 2017 at 20:33
  • $\begingroup$ @Kuba Can you give a pattern matching method for it? $\endgroup$ Commented Feb 11, 2017 at 20:35
  • $\begingroup$ What you do expect as output for {"", "x", "y", "", "z", ""} ? $\endgroup$ Commented Feb 11, 2017 at 21:28
  • $\begingroup$ @Mr.Wizard {"", "x", "y", "y", "z", "z"} $\endgroup$ Commented Feb 11, 2017 at 21:50
  • $\begingroup$ Related: (23454) $\endgroup$ Commented Feb 11, 2017 at 22:50

6 Answers 6

7
$\begingroup$

As I presently interpret the question

(Now with refinements after considering Chris Degnen's simultaneous answer)

fn[list_] :=
  Partition[list, 2, 1, -1, ""] // Cases[{p_, ""} | {_, p_} :> p]

Test:

 {"", "x", "y", "", "z", ""} // fn
{"", "x", "y", "y", "z", "z"}

Patterns

Since you seem only to be interested in a pattern-matching solution here is my proposal to avoid the extremely slow use of ReplaceRepeated while still using pattern-matching as the core of the operation.

fn2[list_] :=
  list // ReplacePart @ 
   ReplaceList[list, {a___, p_, "", ___} :> (2 + Length@{a} -> p)]

Recursive replacement

I just realized that this is a perfect time to use a self-referential replacement:

fn3[list_] :=
  list /. {a___, p_, "", b___} :> {a, p, p, ##& @@ fn3@{b}}

Benchmark

All three methods are much faster than kglr's foo (note the log-log scale).

Now with Carl Woll's fc, the fastest method yet. ( but no patterns ;-) )

Needs["GeneralUtilities`"]

$RecursionLimit = 1*^5;

BenchmarkPlot[{foo, fn, fn2, fn3, fc},
  RandomChoice[{"", "", "", "a", "b"}, #] &
]

enter image description here

$\endgroup$
6
  • $\begingroup$ The pattern matching method is slower in generally. $\endgroup$ Commented Feb 11, 2017 at 22:08
  • $\begingroup$ @yode Indeed it can be, but the main issue here is the use of a single-pass method versus //. which has to reprocess the entire list for every element to be duplicated. $\endgroup$ Commented Feb 11, 2017 at 22:15
  • $\begingroup$ @yode Please see my update. $\endgroup$ Commented Feb 11, 2017 at 22:38
  • $\begingroup$ Funny~the ReplaceList is a good lesson.Upvote! :) $\endgroup$ Commented Feb 11, 2017 at 22:45
  • 1
    $\begingroup$ Wow,I have to say that flash my eyes(the last method). $\endgroup$ Commented Feb 12, 2017 at 2:08
8
$\begingroup$

In place modification of a list works well here:

fc[list_] := Module[{out=list},
    With[{empty = Pick[Range[2,Length@list], Rest@list,""]},
        out[[empty]]=out[[empty-1]]
    ];
    out
]

A comparison with Mr Wizard's fn:

data = RandomChoice[{"","","","a","b"}, 10^5];
r1 = fn[data]; //AbsoluteTiming
r2 = fc[data]; //AbsoluteTiming
r1 === r2

{0.049858,Null}

{0.01092,Null}

True

$\endgroup$
3
  • $\begingroup$ Now I feel silly yet again. +1 of course. :^) $\endgroup$ Commented Feb 12, 2017 at 0:54
  • $\begingroup$ Added to my benchmark. $\endgroup$ Commented Feb 12, 2017 at 0:58
  • $\begingroup$ haha i tried using a method i thought and i got 67 seconds on my tablet. That really shows how important is being able to write a clear and fast code $\endgroup$ Commented Feb 12, 2017 at 11:49
4
$\begingroup$

Update:

foo = # //. {a___, p : Except["", _String], Longest[b : "" ..],   c___} :>
   {a, p, p, ## & @@ ConstantArray["☺", Length[{b}] - 1], c} /. "☺" -> "" &;

{"", "", "", "3$71", "", "", "2$72", "", "", "", ""} // foo

{"", "", "", "3$71", "3$71", "", "2$72", "2$72", "", "", ""}

Previous post:

rule = {a___, p : Except["", _String], Longest["" ..],  b___} :> {a, p, p, b};

{"", "", "", "3$71", "", "", "2$72", "", "", "", ""} //. rule

{"", "", "", "3$71", "3$71", "2$72", "2$72"}

$\endgroup$
8
  • 1
    $\begingroup$ Thanks very much.But {"", "", "", "3$71", "", "", "2$72", ""} will give three "3$71".I just expect two here. $\endgroup$ Commented Feb 11, 2017 at 20:39
  • $\begingroup$ @yode, fixed... $\endgroup$ Commented Feb 11, 2017 at 20:51
  • $\begingroup$ I'm sorry,I expect two "3$71" but not three. $\endgroup$ Commented Feb 11, 2017 at 20:53
  • $\begingroup$ @yode, hope i finally got it. $\endgroup$ Commented Feb 11, 2017 at 21:04
  • 1
    $\begingroup$ {"", "", "", "3$71", "","", "2$72", ""} should give {"", "", "", "3$71", "3$71", "", "2$72", "2$72"}.I'm sorry,actually your code have given me enough information now. :) $\endgroup$ Commented Feb 11, 2017 at 21:17
4
$\begingroup$

Borrowing kglr's pattern

x = {"", "", "1$71", "3$71", "", "", "2$72", "", "", "", ""};

Prepend[
 Map[Last, Partition[x, 2, 1] /. {p : Except[""], ""} :> {p, p}],
 First[x]]

{"", "", "1\$71", "3\$71", "3\$71", "", "2\$72", "2\$72", "", "", ""}

user might consider to use Apply rather than Map:

Last@@@(Partition[x, 2, 1] /. {p : Except[""], ""} :> {p, p})//Prepend[First@x]
$\endgroup$
6
  • $\begingroup$ I was just posting a Partition method myself. +1 and I hope I don't step on any toes. $\endgroup$ Commented Feb 11, 2017 at 21:51
  • $\begingroup$ No worries. :-) $\endgroup$ Commented Feb 11, 2017 at 21:56
  • $\begingroup$ @ChrisDegnen @Mr.Wizard what if you use Last@@@ .... instead of Map[Last ...] . I assume the former will be faster $\endgroup$ Commented Feb 11, 2017 at 23:21
  • 1
    $\begingroup$ @AliHashmi I avoided Last entirely by hard-coding it into f[{_, x_}] := x. For this code I would use Part[. . ., All, 2] as that is usually fastest. $\endgroup$ Commented Feb 11, 2017 at 23:23
  • 1
    $\begingroup$ @AliHashmi Since you care about refinements I realized that this code could be made shorter; see my updated answer. $\endgroup$ Commented Feb 11, 2017 at 23:49
3
$\begingroup$

Using SequenceReplace (new in 11.3)

f = SequenceReplace[{a : Except[""], ""} :> Sequence[a, a]] @ #&;

f @ {"", "", "", "270", ""}

{"", "", "", "270", "270"}

f @ {"", "", "", "371", "", "272", ""}

{"", "", "", "371", "371", "272", "272"}

f @ {"", "", "", "371", "", "", "272", ""}

{"", "", "", "371", "371", "", "272", "272"}

Addendum

As Syed commented a better solution would be

f = SequenceReplace[{a_String, ""} :> Sequence[a, a]] @ #&;

f @ {"", "", "", "270", ""}

{"", "", "", "270", "270"}

$\endgroup$
2
  • $\begingroup$ This works, albeit it is making redundant substitutions by not checking for empty strings: SequenceReplace[list, {a_String, ""} :> Sequence[a, a]] where list is your input. $\endgroup$ Commented Jan 12, 2024 at 6:39
  • $\begingroup$ Thanks, Syed, I updated my answer $\endgroup$ Commented Jan 12, 2024 at 6:47
1
$\begingroup$

Using SequenceSplit:

sp[lst_] := SequenceSplit[lst, s : {Except[""], ""} :> s]

pos1 = Position[sp[l1], {a_String, _} /; DigitQ[a]];

Flatten@ReplacePart[sp[l1], Thread[pos1 -> Cases[sp[l1], {a_, _} :> {a, a}]]]

(*{"", "", "", "270", "270"}*)

pos2 = Position[sp[l2], {a_String, _} /; DigitQ[a]];

Flatten@ReplacePart[sp[l2], Thread[pos2 -> Cases[sp[l2], {a_, _} :> {a, a}]]]

(*{"", "", "", "371", "371", "272", "272"}*)

pos3 = Position[sp[l3], {a_String, _} /; DigitQ[a]];

Flatten@ReplacePart[sp[l3], Thread[pos3 -> Cases[sp[l3], {a_, _} :> {a, a}]]]

(*{"", "", "", "371", "371", "", "272", "272"}*)
$\endgroup$

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.