Skript Sortierung Altsignaturen

Bisher hab ich das immer irgendwie hinbekommen, dass man eine Sortierung von Altsignaturen hat, wie sie dann im Regal zu finden sind.
Jetzt sitzte ich seit gestern an einem Fall indem sehr viel verschiedene Syntax von Altsignaturen vorkommen. Bisher hab ich es immer auf einzelne Spalten zerteilt und dann drüber sortiert. Das funktioniert aber leider in dem Fall gar nicht, weil in den Spalten dann sehr verschiedene Sachen in einer Spalte zu finden sind: string, integers und römische Zahlen :sob:
Hat jemand eventuell ein Skript, dass das alles sinnvoll zerteilt und sortiert?
Python wäre gut, aber ich nehm auch alles andere!
das sind Beispiele für die Signaturen:

KThD:III Ae 9,40,1
OKI:R/203-1,1

und so sieht es aus nach meiner Bearbeitung

|KThD|III|Ae|9|40|1|
|OKI|R|203|1|1||

Ich kann es ja überhaupt nicht leiden, wenn man soviel schreibt, dann noch mit festen Strings (siehe die römischen Zahlen) arbeitet, aber das ist erstmal meine Lösung (nach einem kleinen nervous Breakdown). Wenn jemand was schöneres hat, hab ich immer noch Interesse, das Thema begleitet mich länger.

df = df.astype(str)
df['regexp_replace'] = df['regexp_replace'].str.replace(r'([a-zA-Z])(\d)', r'\1 \2', regex=True)
df['regexp_replace'] = df['regexp_replace'].str.replace('  ' , '')
df['regexp_replace'] = df['regexp_replace'].str.strip()
split_cols = df['regexp_replace'].str.split('\s', expand=True)
split_cols = split_cols.add_prefix('Sortierung_')
df = pd.concat([df.drop('regexp_replace', axis=1), split_cols], axis=1)

df['Sortierung_1'] = df['Sortierung_1'].str.replace('^I$', '1', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^II$', '2', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^III$', '3', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^IV$', '4', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^V$', '5', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^VI$', '6', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^VII$', '7', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^VIII$', '8', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^IX$', '9', regex=True)
df['Sortierung_1'] = df['Sortierung_1'].str.replace('^XQ$', '10', regex=True)
#ich weiß, dass das nicht 10 ist, aber so scheint es verwendet zu werden


df['Sortierung_1_1'] = df['Sortierung_1'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_1_2'] = df['Sortierung_1'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_2_1'] = df['Sortierung_2'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_2_2'] = df['Sortierung_2'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_3_1'] = df['Sortierung_3'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_3_2'] = df['Sortierung_3'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_4_1'] = df['Sortierung_4'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_4_2'] = df['Sortierung_4'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_5_1'] = df['Sortierung_5'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_5_2'] = df['Sortierung_5'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_6_1'] = df['Sortierung_6'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_6_2'] = df['Sortierung_6'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_7_1'] = df['Sortierung_7'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_7_2'] = df['Sortierung_7'].str.extract('([a-zA-Z]+)', expand=False)
df['Sortierung_8_1'] = df['Sortierung_8'].str.extract('(\d+)', expand=False).fillna(0).astype(int)
df['Sortierung_8_2'] = df['Sortierung_8'].str.extract('([a-zA-Z]+)', expand=False)

Jetzt kann ich einzeln über alle Spalten sortieren und am Ende scheint es zu passen.

Es gibt in Python ein kleines Paket roman um römische Zahlen umzuwandeln: https://pypi.org/project/roman/. Das könnte den Code etwas weniger lang machen. Eine alternative Herangehensweise für die Sortierung ohne das Aufteilen in verschiedene Spalten könnte auch eine individuelle Vergleichsfunktion in Kombination mit sort/sorted sein.

1 Like

Was genau meinst Du mit individueller Vergleichsfunktion?
Das Paket hab ich gesehen, aber es wollte bei mir nicht laufen. Wenn man mehr römische Zahlen hat rentiert sich die Anstregung, das Paket zum laufen zu bringen auf alle Fälle.

Hier mein Versuch mit Bash. Die clevere Konvertierung der römischen Zahlen ist nicht von mir, sondern von Tim Birkett.

#!/bin/bash
set -eu -o pipefail

# we will create a temporary list
# each row contains two columns/fields
# first: call number with (converted) arabic numerals
# second: call number with (original) roman numerals
temp_list=""
list_separator="\n"

while read line; do
    arabic_call_number=""
    
    roman_call_number=$(echo $line | sed 's/[: ,\/-]/|/g')

    # check each part of call number
    # if it only contains roman numerals
    # then replace with arabic numerals
    parts=$(echo $line | sed 's/[: ,\/-]/\n/g')
    for part in $parts
    do 
        # to uppercase
        chars=$(echo $part | tr a-z A-Z)
        valid_chars="IVXLCDM"
        if [[ $chars == +(["$valid_chars"]) ]]; then
            #echo "$chars ONLY contains chars in $valid_chars"
            # We want to replace all tokens to eventually have
            # all I's, remove new lines and count the characters.
            # https://gist.github.com/pysysops/7596f1c0a5c6d14151bbd987a2fb95e5
            number=$(
                echo ${chars} |
                sed 's/CM/DCD/g' |
                sed 's/M/DD/g' |
                sed 's/CD/CCCC/g' |
                sed 's/D/CCCCC/g' |
                sed 's/XC/LXL/g' |
                sed 's/C/LL/g' |
                sed 's/XL/XXXX/g' |
                sed 's/L/XXXXX/g' |
                sed 's/IX/VIV/g' |
                sed 's/X/VV/g' |
                sed 's/IV/IIII/g' |
                sed 's/V/IIIII/g' |
                tr -d '\n' |
                wc -m
            )
            part=$number
        fi
        # now reassemble the call number from it's parts
        if [[ -z $arabic_call_number ]]; then
            arabic_call_number="${part}"
        else
            arabic_call_number="${arabic_call_number}|${part}"
        fi
    done
    # concatenate both call number variants
    # add them to list
    if [[ -z $temp_list ]]; then
        temp_list="${arabic_call_number}-${roman_call_number}"
    else
        temp_list="${temp_list}${list_separator}${arabic_call_number}-${roman_call_number}"
    fi
    arabic_call_number=""
done < $1

# sort by arabic call number (first column)
# keep only roman call number (second column)
echo -e "$temp_list" | sort -t '-' -k 1 | cut -d '-' -f 2

Man ruft das Skript z. B. so auf: bash signaturen_sortieren.sh signaturen.txt

Die Sortierungungsfunktion sorted kann man in Python auch mit einem eigenen Vergleich laufen laussen. Dazu braucht man cmp_to_key aus dem functools. Dann kann man etwa folgende Zeile machen

sorted_signaturen = sorted(signaturen, key=cmp_to_key(sortierung))

mit einer entsprechend definierten Funktion sortierung.

Ein ganzes Beispiel würde dann etwa wie folgt aussehen:

import roman
import re

from functools import cmp_to_key


signaturen = ["KThD:III Ae 9,40,1", "OKI:R/203-1,1", "KThD:III Ae 5,40,1", "KThD:IV Ae 7,40,1"

def sortierung(a, b):
    # cleaning
    a = a.replace('  ', '').strip()
    b = b.replace('  ', '').strip()
    # splitting
    parts_a = re.split(r"\W", a)
    parts_b = re.split(r"\W", b)

    for index in range(min(len(parts_a), len(parts_b))):
        if parts_a[index] == parts_b[index]:
            continue
        if re.fullmatch(r"[IVXQ]+", parts_a[index]) and re.fullmatch(r"[IVXQ]+", parts_b[index]):
            parts_a[index] = roman.fromRoman(parts_a[index])
            parts_b[index] = roman.fromRoman(parts_b[index])
            print(parts_a[index], parts_b[index])
        if parts_a[index] > parts_b[index]:
            return 1
        else:
            return -1
    if len(parts_a) == len(parts_b):
        return 0
    elif len(parts_a) < len(parts_b):
        return 1
    else:
        return -1

# sort signaturen
sorted_signaturen = sorted(signaturen, key=cmp_to_key(sortierung))
print(sorted_signaturen)

Danke, das sieht auch sehr gut aus!

Auch dafür danke, da hab ich schon wieder viel gelernt, was über Signaturenfragen hinaus nützlich ist. :+1: