Ordinare elementi di un array

Ordinare numericamente gli elementi di un array alfanumerico.

A cura di Totò Fiandaca  | issue #196 | guida/e Andrea Borruso Giovanni Pirrotta Totò Fiandaca Korto19


caso d’uso

Lista numeri civici di una strada: ordinare la seguente lista 2,2/B,2/A,1,3 in modo da ottenere 1,2,2/A,2/B,3, ovvero ordinarla numericamente anche se ha elementi alfanumerici.

script bash

mlr --csv nest --explode --values --across-records -f scritta --nested-fs "-" then \
put -S '$totoN=regextract($scritta,"[0-9]+");$totoT=regextract($scritta,"[a-zA-Z]+")' then \
unsparsify then sort -f den_estesa -n totoN -f totoT then cut -r -x -f "toto+" then \
nest --implode --values --across-records --nested-fs "-" -f scritta  ordinare_scritta.csv

ragionamento:

lo script mette tutto in colonne splittando il campo scritta usando come delimitatore - e popolando due nuove colonne totoN (numeri) e totoT (testo) con la parte numerica e la parte testuale di ogni elemento; successivamente ordina usando i campi den_estesa, totoN e totoT, a questo punto elimina i campi in più (toto+) e poi torna indietro implodendo tutto.

nota: con Miller 5

esempio:

risultato:

script python

prima versione

import pandas as pd
df = pd.read_csv('ordinare_scritta.csv')
df['scritta']=df.scritta.map(lambda x: 
                             "-".join([item.replace('/0','') 
                             for item in [item[1:] if item[0]=='0' else item 
                             for item in sorted([t.zfill(2)+"/0" if t.isdigit() else t 
                             for t in [f'0{t}' if t.find('/')==1 else t 
                             for t in x.split('-')]], key=str)]]))   
df.to_csv('ordina_scritta_ok.csv',index=False)

seconda versione

import pandas as pd
df = pd.read_csv('ordinare_scritta.csv')
def ordina(x):
    tmp = x.split('-')
    a = [f"0{t}" if t.find('/')==1 else t for t in tmp]
    b = [t.zfill(2)+"/0" if t.isdigit() else t for t in a]
    tmp2=sorted(b, key=str)
    first_ord = [item[1:] if item[0]=='0' else item for item in tmp2]
    second_ord =  [item.replace('/0','') for item in first_ord]
    return "-".join(second_ord)
    
df['scritta']=df.scritta.map(ordina)   
df.to_csv('ordina_scritta_ok.csv',index=False)

QGIS

espressione per il fielc calc

array_to_string(
array_foreach(
	array_sort(
		with_variable('lista',
			string_to_array('5/A-5-4-8-3-14-6-9-7-1-10-7/B-2-7/A' ,'-'),
			array_foreach(generate_series(0, array_length(@lista)-1),
			lpad(regexp_substr( (@lista[@element]),'(\\d+)'),3,'0')
			||'|'||
			if(regexp_substr((@lista[@element]),'([a-zA-Z/]+)') !='',
			   regexp_substr((@lista[@element]),'([a-zA-Z/]+)'),
			   '/A')
			||'|'||
			@lista[@element]))),
regexp_replace( @element,'^.+\\|(.+)$','\\1'))
)

NB: 1) il separatore deve essere - ; 2) ogni elemento della lista deve iniziare con un numero; 3) il separatore tra numero e stringa è /

ragionamento:

Crea un nuovo array con lo stesso numero di elementi, ma ogni nuovo elemento (x|y|z) dell’array è generato a partire dall’elemento corrente esploso (es: 5/A → 5|/A|5/A); successivamente, il primo valore del nuovo elemento (5), viene sottoposto alla funzione lpad in modo da avere 005 e ottenere 005|/A|5/A, questo permette di poter ordinare gli elementi dell’array nel modo desiderato: 001,002,003….010…100. Infine, dopo aver ordinato, estrarre il terzo valore della tripletta (x|y|z) che rappresenta il valore iniziale. La condizione if serve solo a ordinare bene il caso 7/A, 7, altrimenti metterebbe sempre prima l’elemento alfanumerico e non il numero.

funzione personalizzata per il field calc

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def array_sort_special(value1, feature, parent):
    """
    Ordina un array alfanumerico numericamente
    <h2>Example usage:</h2>
    <ul>
      <li>array_sort_special('5/A-5-4-8-3-6-9-7-1-10-7/B-2-7/A') -> ['1','2','3','4','5','5/A', '6','7','7/A','7/B','8','9','10']</li>
    </ul>
    """
    data = value1.split('-')
    r = sorted(data, key=lambda item: (int(item.partition('/')[0])
    if item[0].isdigit() else float('inf'), item))
    #return (','.join(r))
    return r

NB: 1) il separatore deve essere - ; 2) ogni elemento della lista deve iniziare con un numero; 3) il separatore tra numero e stringa è /

note: commentando l’ultima riga e decommentando la penultima restituisce una stringa.

Riferimenti utili

Ultima modifica 15/01/2022: update (20e365c)