9
$\begingroup$

Suppose I have a list of arbitrary length like

{1, 2, 3, "Open", 3, 2, "Close", 9, 3, 4, "Open", 1, 0, "Close", 3, 5}

and I am trying to extract the sequences inside the open/close tags, i.e. the answer I want is

{{3, 2}, {1, 0}}

What's the right way to do this? The data I am actually working is a large XML document and I am trying to extract sections of it by identifying certain tags as boundaries. I've fiddled with different pattern matching functions but can't figure out how to operate on a list in this way since I am trying to match patterns on an intermediate level between individual elements and the entire list.

EDIT

To clarify, I don't know in advance how many such sequences the data will contain or how many elements will be inside any particular set of tags.

$\endgroup$
  • 1
    $\begingroup$ ugly: Take[list, #] & /@ (Partition[ Flatten[Position[list, "Open" | "Close"]], 2] /. {a_?NumberQ, b_?NumberQ} -> {a + 1, b - 1}) $\endgroup$ – chuy Apr 30 '13 at 20:37
  • $\begingroup$ Closely related: (941) $\endgroup$ – Mr.Wizard Jul 29 '17 at 14:13
7
$\begingroup$

Try this one

ReplaceList[expr, {__, PatternSequence["Open",v__ /; Count[{v}, _String] == 0, 
            _String], __} -> {v}]
$\endgroup$
  • $\begingroup$ If you don't Flatten the data from the xml Case will be a good alternative. $\endgroup$ – Spawn1701D Apr 30 '13 at 20:51
  • $\begingroup$ Good call on ReplaceList $\endgroup$ – mfvonh Apr 30 '13 at 20:53
  • $\begingroup$ Maybe change PatternSequence["Open", v__ /; Count[{v}, _?(# == "Close" &)] == 0, "Close"] in case the list contains other strings than "Open"|"Close". $\endgroup$ – b.gates.you.know.what Apr 30 '13 at 21:02
  • $\begingroup$ @b.gatessucks yes the correct structure of the pattern will depend on the actual data, if the tags contain also in the data strings the OP should exclude just the xml tags. My opinion is that the XMLObject and XMLElement objects must be exploited instead of a flattened list. $\endgroup$ – Spawn1701D Apr 30 '13 at 21:09
  • $\begingroup$ I would use the pattern {__, PatternSequence["Open", v : Except["Close"] .., "Close"], __} from the answer below. That should be more efficient that using a test function. $\endgroup$ – Sjoerd Smit Jul 29 '17 at 19:26
7
$\begingroup$

Here's a plain pattern approach, I'm not quite sure how robust it is:

ReplaceList[expr, {___, "Open", x : Except["Close"] ..., "Close", ___} :> {x}]

Also take a look at Longest and Shortest, which may come in handy.

$\endgroup$
4
$\begingroup$

In versions 10.1+, you can use SequenceCases:

 lst={1, 2, 3, "Open", 3, 2, "Close", 9, 3, 4, "Open", 1, 0, "Close", 3, 5};
 SequenceCases[lst, {"Open", x:Except["Close"].., "Close"} :> {x}]

{{3, 2}, {1, 0}}

$\endgroup$
2
$\begingroup$

My humble attempt:

list = {1, 2, 3, "Open", 3, 2, "Close", 9, 3, 4, "Open", 1, 0, 
   "Close", 3, 5};
SplitBy[
 Select[list, 
  (open = # != "Close" && (# == "Open" || open)) &], # == "Open" &] 
    //. "Open" | {} -> Sequence[]
$\endgroup$
1
$\begingroup$

I feel this is way too complicated, but anyway:

l = {1, 2, 3, "Open", 3, 4, 5, 2, "Close", 9, 3, 4, "Open", 0, 
   "Close", "Close", 3, 5};

Reap[l //. {a___, 
     PatternSequence["Open", mid : _?NumericQ .., "Close"], 
     b___} :> {a, Sow[{mid}]; mid, b}][[2, 1]]

{{3, 4, 5, 2}, {0}}

This example might have some limited instructional value showing one convoluted possible use of Sow and Reap.

$\endgroup$
1
$\begingroup$

What about this one?

ReplaceList[list, {___, "Open", x__ /; FreeQ[{x}, "Close"], "Close", ___} -> {x}]

(although amr's one seems better)

$\endgroup$
0
$\begingroup$
positions = (data // {PositionIndex[#]["Open"],PositionIndex[#]["Close"]} & 
// Transpose )

{{4, 7}, {11, 14}}

data[[Span[# + {1, -1}]]] & /@ positions

{{3, 2}, {1, 0}}

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