【发布时间】:2016-05-14 15:01:15
【问题描述】:
我意识到有许多问题与这个问题有不同程度的相似性。我已经对它们进行了详细搜索(使用:[ruby] 键上的哈希值合并数组),并且我尝试了每个答案的点点滴滴,试图自己解决这个问题。在来到 StackOverflow 之前,我什至与同样被难住的同事分享了我的问题。这似乎是一个独特的问题,或者我们都只是盯着它看太近而看不到其他明显的答案。
基本要求
- 该解决方案必须使用 Ruby 1.8.7 标准库(无 gem)。请随意额外说明其他版本的 Ruby 的解决方案,但这样做不会自动使一个答案比另一个更好。
- 输入数据的结构不能被它的提供者改变;整个数据结构按原样交付。如果需要临时重新排列数据以提供最有效的答案,那么只要输出与下面所需的样本匹配就可以了。此外,该解决方案不能对排序键在哈希中的位置做出任何假设。
- 不能以任何方式更改源变量;它在运行时是不可变的(已选中),因此必须将结果提供给新变量。
- 下面的示例数据是虚构的,但问题是真实的。还有其他级别的 Arrays-of-Hashes 也必须以相同的方式合并到其他键上;因此,最佳答案通常可以应用于数据结构的任意级别。
- 最好的解决方案是易于阅读、维护和应用于任意(尽管相似)数据结构。它不必是单行的,但如果您可以在一行 Ruby 代码中满足所有要求,那么对您表示敬意。
样本数据
如果我们将 Apache Tomcat server.xml 文件视为 Ruby 数据结构而不是 XML,那么它可以很好地模拟这个问题。进一步假设默认配置已在上游合并 - 在交付给您之前 - 与您必须合并的数据,然后一些后续操作会使用生成的数据结构。源数据看起来很像这样:
source = {
:Server => {
:'attribute.port' => 8005,
:'attribute.shutdown' => 'SHUTDOWN',
:Listener => [
{ :'attribute.className' => 'org.apache.catalina.startup.VersionLoggerListener' },
{ :'attribute.className' => 'org.apache.catalina.core.AprLifecycleListener',
:'attribute.SSLEngine' => 'off'},
{ :'attribute.className' => 'org.apache.catalina.core.JasperListener' },
{ :'attribute.className' => 'org.apache.catalina.core.JreMemoryLeakPreventionListener' },
{ :'attribute.className' => 'org.apache.catalina.core.AprLifecycleListener',
:'attribute.SSLEngine' => 'on'}
],
:Service => [
{ :'attribute.name' => 'Catalina',
:Connector => [
{ :'attribute.port' => 8080,
:'attribute.protocol' => 'HTTP/1.1'},
{ :'attribute.port' => 8009,
:'attribute.protocol' => 'AJP/1.3'}
],
:Engine => {
:'attribute.name' => 'Catalina',
:'attribute.defaultHost' => 'localhost',
:Realm => {
:'attribute.className' => 'org.apache.catalina.realm.LockOutRealm',
:Realm => [
{ :'attribute.className' => 'org.apache.catalina.realm.UserDatabaseRealm',
:'attribute.resourceName' => 'UserDatabase'}
]
},
:Host => [
{ :'attribute.name' => 'localhost',
:'attribute.appBase' => 'webapps',
:Valve => [
{ :'attribute.className' => 'org.apache.catalina.valves.AccessLogValve',
:'attribute.directory' => 'logs'}
]
}
]
}
},
{ :'attribute.name' => 'Catalina',
:Connector => [
{ :'attribute.port' => 8080,
:'attribute.protocol' => 'HTTP/1.1',
:'attribute.secure' => true,
:'attribute.scheme' => 'https',
:'attribute.proxyPort' => 443}
]
},
{ :'attribute.name' => 'JSVCBridge',
:Connector => [
{ :'attribute.port' => 8010,
:'attribute.protocol' => 'HTTP/2'}
]
},
{ :'attribute.name' => 'Catalina',
:Engine => {
:Host => [
{ :'attribute.name' => 'localhost',
:Valve => [
{ :'attribute.className' => 'org.apache.catalina.valves.RemoteIpValve',
:'attribute.internalProxies' => '*',
:'attribute.remoteIpHeader' => 'X-Forwarded-For',
:'attribute.protocolHeader' => 'X-Forwarded-Proto',
:'attribute.protocolHeaderHttpsValue' => 'https'}
]
}
]
}
}
]
}
}
挑战是从中产生这个结果:
result = {
:Server => {
:'attribute.port' => 8005,
:'attribute.shutdown' => 'SHUTDOWN',
:Listener => [
{ :'attribute.className' => 'org.apache.catalina.startup.VersionLoggerListener' },
{ :'attribute.className' => 'org.apache.catalina.core.AprLifecycleListener',
:'attribute.SSLEngine' => 'on'},
{ :'attribute.className' => 'org.apache.catalina.core.JasperListener' },
{ :'attribute.className' => 'org.apache.catalina.core.JreMemoryLeakPreventionListener' },
],
:Service => [
{ :'attribute.name' => 'Catalina',
:Connector => [
{ :'attribute.port' => 8080,
:'attribute.protocol' => 'HTTP/1.1',
:'attribute.secure' => true,
:'attribute.scheme' => 'https',
:'attribute.proxyPort' => 443},
{ :'attribute.port' => 8009,
:'attribute.protocol' => 'AJP/1.3'}
],
:Engine => {
:'attribute.name' => 'Catalina',
:'attribute.defaultHost' => 'localhost',
:Realm => {
:'attribute.className' => 'org.apache.catalina.realm.LockOutRealm',
:Realm => [
{ :'attribute.className' => 'org.apache.catalina.realm.UserDatabaseRealm',
:'attribute.resourceName' => 'UserDatabase'}
]
},
:Host => [
{ :'attribute.name' => 'localhost',
:'attribute.appBase' => 'webapps',
:Valve => [
{ :'attribute.className' => 'org.apache.catalina.valves.AccessLogValve',
:'attribute.directory' => 'logs'},
{ :'attribute.className' => 'org.apache.catalina.valves.RemoteIpValve',
:'attribute.internalProxies' => '*',
:'attribute.remoteIpHeader' => 'X-Forwarded-For',
:'attribute.protocolHeader' => 'X-Forwarded-Proto',
:'attribute.protocolHeaderHttpsValue' => 'https'}
]
}
]
}
},
{ :'attribute.name' => 'JSVCBridge',
:Connector => [
{ :'attribute.port' => 8010,
:'attribute.protocol' => 'HTTP/2'}
]
}
]
}
}
问题
我们需要source 变成result。为了到达那里,:Listener 被attribute.className 合并; :Service 被 attribute.name 合并; :Connector 的结果数组由 attribute.port 合并;等等。数据结构中 Arrays-of-Hashes 的位置以及每个要合并的键的标识应该很容易提供给解决方案。
这个问题的真正本质是找到一个通用的解决方案,它可以应用于像这样的复杂数据结构的多个任意级别,通过提供的键合并 Arrays-of-Hashes,并在设置位置后产生合并的结果并提供密钥对。
非常感谢大家对这个问题的时间和兴趣。
【问题讨论】:
-
什么规则决定了 :Listener 的变化?仅仅是在散列数组中,如果键重复,则该键的最新实例是唯一剩下的吗?如果是这样,为什么“attribute.className sill 在结果中出现两次?
-
对于这个问题,所有 Arrays-of-Hashes 必须在 key 上合并,以便 Array 中后面的 Hashes 中的值覆盖早期 Hashes 中的值。
:ListenerArray-of-Hashes 在attribute.className上合并,因此结果中attribute.SSLEngine是onfororg.apache.catalina.core.AprLifecycleListener。attribute.className的值在:Listeners的两个幸存元素之间有所不同;只有org.apache.catalina.core.AprLifecycleListener需要合并。 -
您能否添加一个您可能应用该方法的更复杂结构的示例?
-
@ian 我扩展了数据样本以添加更多
:Listener元素并进一步说明额外的复杂性和预期行为。