Funkenextraktspalten aus String

Nov 05 2020

Benötigen Sie Hilfe beim Parsen einer Zeichenfolge, die Werte für jedes Attribut enthält. Unten ist meine Beispielzeichenfolge ...

otherPartofString Name=<Series VR> Type=<1Ac4> SqVal=<34> conn ID=<2>

Manchmal kann die Zeichenfolge andere Werte mit einem anderen Trennzeichen enthalten

otherPartofString Name=<Series X> Type=<1B3> SqVal=<34> conn ID=<2> conn Loc=sfo dest=chc bridge otherpartofString.. 

Die Ausgabespalten werden sein

Name      | Type | SqVal | ID | Loc  | dest 
-------------------------------------------
Series VR | 1Ac4 | 34    | 2  | null | null
Series X  | 1B3  | 34    | 2  | sfo  | chc 

Antworten

2 jxc Nov 05 2020 at 20:39

Wie bereits besprochen, verwenden str_to_map Funktion auf Ihrer Beispieldaten, können wir Setup pairDelim und keyValueDelim auf die folgenden:

pairDelim: '(?i)>? *(?=Name|Type|SqVal|conn ID|conn Loc|dest|$)'
keyValueDelim: '=<?'

Wenn bei pariDelim die Groß- und Kleinschreibung nicht berücksichtigt wird, gefolgt von (?i)einem optionalen >Schlüssel, gefolgt von null oder mehr Leerzeichen, gefolgt von einem der vordefinierten Schlüssel (wir verwenden ihn '|'.join(keys), um ihn dynamisch zu generieren) oder dem Ende des String-Ankers $. keyValueDelim ist ein '=' mit einem optionalen <.

from pyspark.sql import functions as F

df = spark.createDataFrame([                                               
   ("otherPartofString Name=<Series VR> Type=<1Ac4> SqVal=<34> conn ID=<2>",),   
   ("otherPartofString Name=<Series X> Type=<1B3> SqVal=<34> conn ID=<2> conn Loc=sfo dest=chc bridge otherpartofString..",)
],["value"])

keys = ["Name", "Type", "SqVal", "conn ID", "conn Loc", "dest"]

# add the following conf for Spark 3.0 to overcome duplicate map key ERROR
#spark.conf.set("spark.sql.mapKeyDedupPolicy", "LAST_WIN")

df.withColumn("m", F.expr("str_to_map(value, '(?i)>? *(?={}|$)', '=<?')".format('|'.join(keys)))) \
    .select([F.col('m')[k].alias(k) for k in keys]) \
    .show()
+---------+----+-----+-------+--------+--------------------+
|     Name|Type|SqVal|conn ID|conn Loc|                dest|
+---------+----+-----+-------+--------+--------------------+
|Series VR|1Ac4|   34|      2|    null|                null|
| Series X| 1B3|   34|      2|     sfo|chc bridge otherp...|
+---------+----+-----+-------+--------+--------------------+

Wir müssen die Werte des letzten zugeordneten Schlüssels nachbearbeiten, da es keinen Anker oder Muster gibt, um sie von anderen nicht verwandten Texten zu unterscheiden (dies könnte ein Problem sein, da es bei allen Schlüsseln auftreten kann) Ich weiß, ob Sie ein Muster angeben können.

Bearbeiten: Wenn die Verwendung der Karte für die Suche ohne Berücksichtigung der Groß- und Kleinschreibung weniger effizient ist, da eine teure Vorverarbeitung erforderlich ist, versuchen Sie Folgendes:

ptn = '|'.join(keys)
df.select("*", *[F.regexp_extract('value', r'(?i)\b{0}=<?([^=>]+?)>? *(?={1}|$)'.format(k,ptn), 1).alias(k) for k in keys]).show()

Falls die spitzen Klammern <und >nur verwendet werden, wenn Werte oder deren nächster benachbarter Schlüssel Nichtwortzeichen enthalten, kann dies mit einigen Vorverarbeitungen vereinfacht werden:

df.withColumn('value', F.regexp_replace('value','=(\w+)','=<$1>')) \
    .select("*", *[F.regexp_extract('value', r'(?i)\b{0}=<([^>]+)>'.format(k), 1).alias(k) for k in keys]) \
    .show()

Edit-2: Ein Wörterbuch für Schlüssel-Aliase wurde hinzugefügt:

keys = ["Name", "Type", "SqVal", "ID", "Loc", "dest"]

# aliases are case-insensitive and added only if exist
key_aliases = {
    'Type': [ 'ThisType', 'AnyName' ],
    'ID': ['conn ID'],
    'Loc': ['conn Loc']
}

# set up regex pattern for each key differently
key_ptns = [ (k, '|'.join([k, *key_aliases[k]]) if k in key_aliases else k) for k in keys ]  
#[('Name', 'Name'),
# ('Type', 'Type|ThisType|AnyName'),
# ('SqVal', 'SqVal'),
# ('ID', 'ID|conn ID'),
# ('Loc', 'Loc|conn Loc'),
# ('dest', 'dest')]  

df.withColumn('value', F.regexp_replace('value','=(\w+)','=<$1>')) \
    .select("*", *[F.regexp_extract('value', r'(?i)\b(?:{0})=<([^>]+)>'.format(p), 1).alias(k) for k,p in key_ptns]) \
    .show()
+--------------------+---------+----+-----+---+---+----+
|               value|     Name|Type|SqVal| ID|Loc|dest|
+--------------------+---------+----+-----+---+---+----+
|otherPartofString...|Series VR|1Ac4|   34|  2|   |    |
|otherPartofString...| Series X| 1B3|   34|  2|sfo| chc|
+--------------------+---------+----+-----+---+---+----+