0

Is it possible to reverse a string in SPARQL, so, e.g., "abc" becomes "cba". Alternatively, is it possible to sort based on strings in reverse.

I am particularly interested in a solution that would work on Wikidata Query Service.

logi-kal
  • 7,107
  • 6
  • 31
  • 43
Finn Årup Nielsen
  • 6,130
  • 1
  • 33
  • 43
  • 1
    impossible in SPARQL see, https://www.w3.org/TR/sparql11-query/#func-strings - in general, those functions are basically based on the [XPath functions](https://www.w3.org/TR/xpath-functions/#string-value-functions) and even there you can't reverse strings. Indeed, SPARQL allows for custom functions, which in fact is implementation specific. For Wikidata aka Balzegraph, the answer is also "not possible" – UninformedUser Jun 12 '19 at 20:23

4 Answers4

7

Well, you asked whether it's possible, not whether it's practical...

SELECT ?s (group_concat(?letter; separator='') AS ?r) {
  BIND ("abcdefghijkl" AS ?s)
  VALUES ?d1 { 0 1 2 3 4 5 6 7 8 9 }
  VALUES ?d2 { 0 1 2 3 4 5 6 7 8 9 }
  VALUES ?d3 { 0 1 2 3 4 5 6 7 8 9 }
  BIND (100 * ?d3 + 10 * ?d2 + ?d1 + 1 AS ?i)
  BIND (SUBSTR(?s, ?i, 1) AS ?letter)
}
GROUP BY ?s

Live link

The approach is similar to that in Finn's response, but it works for strings up to a length of 1000 characters. This would be used as a subquery within a larger query. Aggregates like group_concat don't guarantee a particular order, so this query relies on the implementation-dependent ordering used by Blazegraph, and may jumble up the string on a different implementation.

If the use case is sorting by the last characters of a string in reverse order, then this could be tweaked so that it's guaranteed to include the last n characters of the string.

cygri
  • 9,412
  • 1
  • 25
  • 47
3

This pathological implementation apparently works (up to a certain string length, here 12) in Blazegraph/Wikidata Query Service:

SELECT ?s ?r {
  BIND("abcdefghijkl" AS ?s)
  BIND(CONCAT(SUBSTR(?s, 12, 1), SUBSTR(?s, 11, 1), SUBSTR(?s, 10, 1),
              SUBSTR(?s, 9, 1), SUBSTR(?s, 8, 1), SUBSTR(?s, 7, 1),
              SUBSTR(?s, 6, 1), SUBSTR(?s, 5, 1), SUBSTR(?s, 4, 1),
              SUBSTR(?s, 3, 1), SUBSTR(?s, 2, 1), SUBSTR(?s, 1, 1)) AS ?r)
}
Finn Årup Nielsen
  • 6,130
  • 1
  • 33
  • 43
  • I'm not sure that's actually ever useful, but you have my vote for pure cleverness. – Jeen Broekstra Jun 12 '19 at 23:58
  • well, this isn't a generic solutions that's why I didn't suggest such a thing. I mean, the length of the string is the problem and you don't have something like loops in SPARQL. How would you reverse a string that is longer than 15 here? But ok, if this is sufficient for you, why not. I mean, if you're crazy enough, you could determine the max. string length of a literal in Wikidata (if the query terminates ...), and then write your hack based on the max. length – UninformedUser Jun 13 '19 at 05:16
  • 1
    If the use case is sorting by the reversed string, then taking the last _n_ characters and reversing their order could actually be a perfectly fine approximation for a reasonable value of _n_. The query can be made less verbose, but probably slower, by use of `VALUES` and `group_concat`, as I show in my response. – cygri Jun 13 '19 at 08:32
2

There is no "reverse string" function in SPARQL 1.1.

There is no ORDER BY modifier that delivers the ordering you're looking for.

Many if not most programming and scripting languages do have "reverse string" and "sort" methods, so I'd suggest investigating whether your (unnamed) development environment has such.

TallTed
  • 9,069
  • 2
  • 22
  • 37
2

Here's a query targeting Virtuoso that provides the desired solution, courtesy of a sub-query.

SELECT ?str (GROUP_CONCAT(?letter; separator="") AS ?reverse)
WHERE {
          {
              SELECT ?str ?charAt 
              WHERE {
                      VALUES ?str { "abcdefghijk" }
                      VALUES ?d1 { 0 1 2 3 4 5 6 7 8 9 10 }
                      BIND(STRLEN(?str) - ?d1 AS ?charAt)
                      FILTER(?charAt > 0)
                    }
           }

          BIND(SUBSTR(?str,?charAt,1) AS ?letter) 
     }

Live Query Results Page.