【问题标题】:Split dotted path but replace two dots with a symbol拆分虚线路径,但用符号替换两个点
【发布时间】:2022-10-13 11:15:07
【问题描述】:

在我的特定领域语言中,我可以使用虚线路径引用树状结构内的某些内容,除了我也可以连续两个点“向上”:

"sibling"         // gets sibling
".sibling"        // same as above
".sibling.nested" // indexes into sibling with 'nested'
"..down"          // goes up the hierarchy, then down the key 'down'
"down.."          // doesn't do anything, really, goes down and then back up
".."              // references parent
"down..down"      // same as "down"

我需要像这样拆分上面的内容:

["sibling"]
["sibling"]
["sibling", "nested"]
[symbol, "down"]
["down", symbol]
[symbol]
["down", symbol, "down"]

本质上,将.. 替换为符号,但通常由单个. 拆分。我可以像这样拆分普通的点状嵌套路径(甚至尊重转义句点):

path.split(/(?<!\\)\./)

键可能包含任何字符,因此单词边界有时不起作用。

但是我很难找到一个可以替代.. 的干净解决方案。我目前的解决方案是用符号替换所有空字符串(空字符串表示一行中有两个句点),并将一个字符附加到路径的末尾,在拆分完成后删除:

const symbol = Symbol();

function split(path) {
  path = "$" + path + "$"; // Any character can work; I just used '$' here
  // Split by dots, replacing empty strings with the symbol
  const split = path.split(/(?<!\\)\./).map((part) => (!part ? symbol : part));
  // Remove prefixed character from start
  split[0] = split[0].slice(1);
  // Remove suffixed character from end
  split[split.length - 1] = split[split.length - 1].slice(0, -1);
  // Remove start or end if they are empty
  if (!split[0]) split.shift();
  if (!split[split.length - 1]) split.pop();
  // Done
  return split;
}

const tests = [
  "sibling"        , // gets sibling
  ".sibling"       , // same as above
  ".sibling.nested", // indexes into sibling with 'nested'
  "..down"         , // goes up the hierarchy, then down the key 'down'
  "down.."         , // doesn't do anything, really, goes down and then back up
  ".."             , // references parent
  "down..down"     , // same as "down"
];
// (Stack Overflow snippet console displays null instead of Symbol)
console.log(tests.map(split));

它完成了工作,通过了所有测试用例,但它过于冗长和笨拙。我正在寻找一个比这更短更容易的解决方案。

【问题讨论】:

    标签: javascript


    【解决方案1】:

    我的技巧是拆分. 和..,但如果存在最后一个.,则包括它

    const symbol = Symbol();
    
    // Your function
    function split(path) {
      path = "$" + path + "$"; // Any character can work; I just used '$' here
      // Split by dots, replacing empty strings with the symbol
      const split = path.split(/(?<!\)./).map((part) => (!part ? symbol : part));
      // Remove prefixed character from start
      split[0] = split[0].slice(1);
      // Remove suffixed character from end
      split[split.length - 1] = split[split.length - 1].slice(0, -1);
      // Remove start or end if they are empty
      if (!split[0]) split.shift();
      if (!split[split.length - 1]) split.pop();
      // Done
      return split;
    }
    
    function split2(path) {
      return path.split(/(?<!\).(.)?/g) // Split on . and .., but include the last . if exists
                 .filter(s => s) // Remove falsey things
                 .map(s => s === '.' ? symbol : s)
    }
    
    const tests = [
      "sibling"        , // gets sibling
      ".sibling"       , // same as above
      ".sibling.nested", // indexes into sibling with 'nested'
      "..down"         , // goes up the hierarchy, then down the key 'down'
      "down.."         , // doesn't do anything, really, goes down and then back up
      ".."             , // references parent
      "down..down"     , // same as "down"
    ];
    console.log(tests.map(split));
    console.log(tests.map(split2))
    
    

    输出

    [
      [ 'sibling' ],
      [ 'sibling' ],
      [ 'sibling', 'nested' ],
      [ Symbol(), 'down' ],
      [ 'down', Symbol() ],
      [ Symbol() ],
      [ 'down', Symbol(), 'down' ]
    ]
    [
      [ 'sibling' ],
      [ 'sibling' ],
      [ 'sibling', 'nested' ],
      [ Symbol(), 'down' ],
      [ 'down', Symbol() ],
      [ Symbol() ],
      [ 'down', Symbol(), 'down' ]
    ]
    

    编辑:

    1. 更好的算法,更好的写作
    2. 从包含第一个 . 更改为最后一个 .,因为这样做听起来更自然。 (用法应该没有区别)

    【讨论】:

    • 这很好,我没想到这么简单的东西可以工作。我编辑了正则表达式以尊重转义句点。
    • @catgirlkelly 很高兴我能帮上忙!我不完全确定(?&lt;!\) 是如何工作的,但一个简单的\? 不也能做到这一点吗?制作正则表达式\?(.)?.
    • 这只是一个可选的反斜杠,(?&lt;!\) 被称为否定的后向断言.它断言在点之前没有反斜杠。你可以找到更多关于这个here。
    【解决方案2】:

    如果我理解正确(至少我的解决方案也通过了所有测试),这是我的较短版本:

    let f = str => str.split(//g).filter(x=> x!='.').map(x=> x=='..'? Symbol() :x);
    const tests = [
      "sibling"        , // gets sibling
      ".sibling"       , // same as above
      ".sibling.nested", // indexes into sibling with 'nested'
      "..down"         , // goes up the hierarchy, then down the key 'down'
      "down.."         , // doesn't do anything, really, goes down and then back up
      ".."             , // references parent
      "down..down"     , // same as "down"
    ];
    console.log(tests.map(f));

    快速解释: 我建议每个树节点都是一个“单词”,然后我将字符串按单词边界拆分,这在正则表达式 的帮助下很容易,其余的很明显我猜

    UPD。

    我的 2d 版本甚至可以处理 'asd.$..a#123s.asdasd' 以将其正确转换为 [ 'asd', '$', Symbol(), 'a#123s', 'asdasd' ] 在这里

    const f = str => str.split(/(?<!.).(?!.)/gm).map(x=> x.split(/../).flatMap(e => [e,Symbol()]).slice(0,-1)).flat();
    
    const tests = [
      "sibling"        , // gets sibling
      ".sibling"       , // same as above
      ".sibling.nested", // indexes into sibling with 'nested'
      "..down"         , // goes up the hierarchy, then down the key 'down'
      "down.."         , // doesn't do anything, really, goes down and then back up
      ".."             , // references parent
      "down..down"     , // same as "down"
      "asd.$..a#123s.asdasd", //add test with any symbol inside
    ];
    
    console.log(tests.map(f));

    我的第二个变体背后的主要思想是首先找到单个.(之前或之后没有多余的点)并被它分割。
    因此,在我的示例中,我们将把"asd.$..a#123s.asdasd" 转换为["asd", "$..a#123s","asdasd]。
    第二部分和第一部分一样简单。我们必须用.. 分割其余部分,并在两者之间插入Symbol()。

    P.S.slice(0,-1) 防止在每个子数组末尾出现多余的Symbol()。

    【讨论】:

    • 这真的很短很甜,效果很好,但很抱歉我忘了包括键可能包含任何字符。我会更新这个问题。
    • @catgirlkelly 用长长的单行函数 (xD) 更新了我的答案,该函数可以处理来自任何字符的名称
    【解决方案3】:

    我不确定这是否适合您,但我在您的测试中得到相同的输出。 不过,它只少了 2 行,可读性也差了一点。

    function split2(path: string) {
        path = path.replace('..', '.$.')
        let split = path.split('.').filter(x => x)
        split = split.map(function(x){return x === '$' ? symbol : x});
        return split;
    }
    

    这是playground。

    【讨论】:

    • 这是一个好主意,但如果有一个最初名为 $ 的键,那么这将产生错误的输出(键名中可能出现的任何字符)。
    猜你喜欢
    • 2017-02-18
    • 1970-01-01
    • 2014-08-27
    • 2013-08-26
    • 2015-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多