6
$\begingroup$

Suppose I have a string like "a-1_b-2_c-3_d-4" with numbers, labels, and separators, and I want to get {a-> 1, b-> 2, c-> 3, d-> 4}, I can do something like:

string1 = "a-1_b-2_c-3_d-4";
vals = ToExpression /@ StringCases[string1, 
{"a-" ~~ a : NumberString -> a, 
"b-" ~~ b : NumberString -> b, 
"c-" ~~ c : NumberString -> c, 
"d-" ~~ d : NumberString -> d}];
result = Thread[{a, b, c, d} -> vals]

{a -> 1, b -> 2, c -> 3, d -> 4}

But sometimes my input has one or more parts missing and I would like the default value to be zero in those cases so that "a-1_d-4" becomes {a -> 1, b -> 0, c -> 0, d -> 4}. I can't think of a way to do this other than search for each value individually and then check to see if the result was empty. Is there a cleaner way to do it?

$\endgroup$

4 Answers 4

4
$\begingroup$

You could do something like this:

assoc = AssociationThread[{a, b, c, d}, {0, 0, 0, 0}];
string1 = "a-1_d-4";

AssociateTo[
 assoc, 
 AssociationThread @@ 
  Transpose[
   Partition[
    StringCases[StringReplace[string1, "-" -> " "], 
     x : NumberString | "a" | "b" | "c" | "d" :> ToExpression[x]], 
    2]]
 ];
assoc

<|a -> 1, b -> 0, c -> 0, d -> 4|>

You can use Normal on this if you really need a list of rules.

$\endgroup$
2
  • $\begingroup$ That definitely works for my example, except it treats the - as a negative sign but that is not really an issue. I will mark this as an answer because it does work for the test case. I need to tweak it a bit to get it to work for my more general input but AssociateTo was the key (pun intended) I needed. $\endgroup$ Commented Jun 15, 2018 at 19:42
  • 1
    $\begingroup$ Ah, I see. Fixed it now. Glad to be of help. $\endgroup$ Commented Jun 15, 2018 at 19:51
2
$\begingroup$

This code works for my actual input which is not as simple as the example data I gave. It is inspired by Schumacher's answer but not nearly as neat.

string2 = "0deg_5kV_60mA_10A_Y1-1.5A";
assoc2 = AssociationThread[{deg, kV, mA, A, X1, Y1, X2, Y2}, {0, 0, 0, 0, 0, 0, 0, 0}];
StringSplit[string2, "_"]
StringCases[#,""|"-"~~x:NumberString|"deg"|"kV"|"mA"|"A"|"X1"|"X2"|"Y1"|"Y2"
  :>ToExpression[x]] & /@ %
%[[;; , ;; 2]] (*Gets rid of the extra A*)
ReverseSort /@ %
AssociateTo[assoc2, AssociationThread @@ Transpose@%] 

{"0deg", "5kV", "60mA", "10A", "Y1-1.5A"}  
{{0, deg}, {5, kV}, {60, mA}, {10, A}, {Y1, 1.5, A}}  
{{0, deg}, {5, kV}, {60, mA}, {10, A}, {Y1, 1.5}}
{{deg, 0}, {kV, 5}, {mA, 60}, {A, 10}, {Y1, 1.5}}
<|deg -> 0, kV -> 5, mA -> 60, A -> 10, X1 -> 0, Y1 -> 1.5, X2 -> 0, Y2 -> 0|>
$\endgroup$
2
$\begingroup$

Using SequenceSplit (new in 11.3)

f =
  Cases[{a_, _, b__, _} :> Rule[a, ToExpression @ StringJoin[{b}]]] @
    SequenceSplit[
     Append["_"] @ Characters[#],
     x : {_?LetterQ, Except[_?LetterQ] ..} :> x] &;

f @ "a-1_b-2_c-3_d-4"

{"a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4}

Negative integers and integers > 9

f @ "a--1_b-280_c-3_d-4"

{"a" -> -1, "b" -> 280, "c" -> 3, "d" -> 4}

Integers and reals

f @ "a-1_b--28.8_c-3_d-4"

{"a" -> 1, "b" -> -28.8, "c" -> 3, "d" -> 4}

Default values

def = Thread[{"a", "b", "c", "d"} -> {0, 0, 0, 0}];

<|def, f @ "a-1_b--28.8_c-3"|>

<|"a" -> 1, "b" -> -28.8, "c" -> 3, "d" -> 0|>

KeyValueMap[Rule][%]

{"a" -> 1, "b" -> -28.8, "c" -> 3, "d" -> 0}

$\endgroup$
1
$\begingroup$

Using

s1 = "a-1_b-2_c-3_d-4";
s2 = "a-1_d-4";


Rule @@@ StringSplit[#, "-"] &@StringSplit[s1, "_"]

will deal with first example.

To deal with missing keys you can use Lookup and the default argument 0.

f[u_] := 
 Module[{a = 
    Association[Rule @@@ StringSplit[#, "-"] &@StringSplit[u, "_"]], 
   cr = CharacterRange["a", "d"]},
  Thread[cr -> Lookup[a, cr, 0]]]
f[s1]
f[s2]

enter image description here

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