Create an object/Native demonstration: Difference between revisions
(+ D entry) |
m (→{{header|Wren}}: Changed to Wren S/H) |
||
(54 intermediate revisions by 24 users not shown) | |||
Line 1: | Line 1: | ||
{{draft task}} |
{{draft task}} |
||
Create a Hash/Associative Array/Dictionary-like object |
Create a Hash/Associative Array/Dictionary-like object that can be initialized with key/value pairs. The object should behave like a native Hash/Associative Array/Dictionary of the language, but with the following differences: |
||
* No new |
* No new keys can be added; |
||
* Keys cannot be removed; |
|||
* Item cannot be deleted, (but native delete method may used to reset the item's value to default) ; |
|||
* Attempting to delete a key should set that keys value back to that used during initialisation. |
|||
(The value assigned to keys may be changed by normal assignment however). |
|||
If the language supports '''Magic Methods''', then show how these work. |
If the language supports '''Magic Methods''', then show how these work. |
||
=={{header|BASIC}}== |
|||
==={{header|BASIC256}}=== |
|||
<syntaxhighlight lang="basic256">map mapa |
|||
mapa["A"] = 65 |
|||
mapa["B"] = 66 |
|||
mapa["C"] = 67 |
|||
foreach valor in mapa |
|||
print valor |
|||
print mapa[valor] |
|||
next valor</syntaxhighlight> |
|||
{{out}} |
|||
<pre>A |
|||
65 |
|||
B |
|||
66 |
|||
C |
|||
67</pre> |
|||
==={{header|FreeBASIC}}=== |
|||
FB doesn't have Dict natively, but we can implement them via Type |
|||
<syntaxhighlight lang="freebasic">Type dict |
|||
m1 As String*1 |
|||
m2 As Integer |
|||
End Type |
|||
Dim mapOf(1 To 3) As dict => {("A", 65), ("B", 66), ("C", 67)} |
|||
For i As Integer = 1 To Ubound(mapOf) |
|||
Print mapOf(i).m1 |
|||
Print mapOf(i).m2 |
|||
Next i</syntaxhighlight> |
|||
{{out}} |
|||
<pre>A |
|||
65 |
|||
B |
|||
66 |
|||
C |
|||
67</pre> |
|||
=={{header|C++}}== |
|||
<syntaxhighlight lang="cpp">#include <iostream> |
|||
#include <map> |
|||
#include <utility> |
|||
using namespace std; |
|||
template<typename T> |
|||
class FixedMap : private T |
|||
{ |
|||
// Two standard maps are used to implement FixedMap. One as a private |
|||
// base class which will allow the values (but not the keys) to be modified. |
|||
// Members of a private base class are not exposed to the derived class which will |
|||
// prevent keys from being added or deleted. Another map will hold copies of |
|||
// the initial values. |
|||
T m_defaultValues; |
|||
public: |
|||
FixedMap(T map) |
|||
: T(map), m_defaultValues(move(map)){} |
|||
// Expose members of the base class that do not modify the map. |
|||
using T::cbegin; |
|||
using T::cend; |
|||
using T::empty; |
|||
using T::find; |
|||
using T::size; |
|||
// Also expose members that can modify values but not add or remove keys. |
|||
using T::at; |
|||
using T::begin; |
|||
using T::end; |
|||
// The [] operator will normally add a new key if the key is not already in the |
|||
// map. Instead, throw an error if the key is missing. |
|||
auto& operator[](typename T::key_type&& key) |
|||
{ |
|||
// Make it behave like at() |
|||
return this->at(forward<typename T::key_type>(key)); |
|||
} |
|||
// Instead of removing a key, change the sematics of erase() to restore |
|||
// the original value of the key. |
|||
void erase(typename T::key_type&& key) |
|||
{ |
|||
T::operator[](key) = m_defaultValues.at(key); |
|||
} |
|||
// Also change the sematics of clear() to restore all keys |
|||
void clear() |
|||
{ |
|||
// Reset the base class using the defaults |
|||
T::operator=(m_defaultValues); |
|||
} |
|||
}; |
|||
// Print the contents of a map |
|||
auto PrintMap = [](const auto &map) |
|||
{ |
|||
for(auto &[key, value] : map) |
|||
{ |
|||
cout << "{" << key << " : " << value << "} "; |
|||
} |
|||
cout << "\n\n"; |
|||
}; |
|||
int main(void) |
|||
{ |
|||
// Create a fixed map based on the standard map |
|||
cout << "Map intialized with values\n"; |
|||
FixedMap<map<string, int>> fixedMap ({ |
|||
{"a", 1}, |
|||
{"b", 2}}); |
|||
PrintMap(fixedMap); |
|||
cout << "Change the values of the keys\n"; |
|||
fixedMap["a"] = 55; |
|||
fixedMap["b"] = 56; |
|||
PrintMap(fixedMap); |
|||
cout << "Reset the 'a' key\n"; |
|||
fixedMap.erase("a"); |
|||
PrintMap(fixedMap); |
|||
cout << "Change the values the again\n"; |
|||
fixedMap["a"] = 88; |
|||
fixedMap["b"] = 99; |
|||
PrintMap(fixedMap); |
|||
cout << "Reset all keys\n"; |
|||
fixedMap.clear(); |
|||
PrintMap(fixedMap); |
|||
try |
|||
{ |
|||
// Adding or retrieving a missing key is a run time error |
|||
cout << "Try to add a new key\n"; |
|||
fixedMap["newKey"] = 99; |
|||
} |
|||
catch (exception &ex) |
|||
{ |
|||
cout << "error: " << ex.what(); |
|||
} |
|||
} |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre>Map intialized with values |
|||
{a : 1} {b : 2} |
|||
Change the values of the keys |
|||
{a : 55} {b : 56} |
|||
Reset the 'a' key |
|||
{a : 1} {b : 56} |
|||
Change the values the again |
|||
{a : 88} {b : 99} |
|||
Reset all keys |
|||
{a : 1} {b : 2} |
|||
Try to add a new key |
|||
error: map::at</pre> |
|||
=={{header|D}}== |
=={{header|D}}== |
||
{{trans|Python}} |
|||
<syntaxhighlight lang="d">struct DefaultAA(TK, TV) { |
|||
TV[TK] standard, current; |
|||
this(TV[TK] default_) pure /*nothrow*/ @safe { |
|||
The simplest way to solve this task is to create an immutable associative array: |
|||
this.standard = default_; |
|||
<lang d>void main() { |
|||
this.current = default_.dup; |
|||
immutable aa = ["red": 1, "green": 2, "blue": 3]; |
|||
} |
|||
alias current this; |
|||
assert(aa["green"] == 2); |
|||
void remove(in TK key) pure nothrow { |
|||
// Error: cannot remove key from immutable associative array aa |
|||
current[key] = standard[key]; |
|||
aa.remove("red"); |
|||
} |
|||
void clear() pure /*nothrow*/ @safe { |
|||
// Error: cannot modify immutable expression aa |
|||
current = standard.dup; |
|||
} |
|||
}</lang> |
|||
} |
|||
void main() { |
|||
import std.stdio; |
|||
auto d = ["a": 1, "b": 2].DefaultAA!(string, int); |
|||
d.writeln; // ["a":1, "b":2] |
|||
d["a"] = 55; d["b"] = 66; |
|||
d.writeln; // ["a":55, "b":66] |
|||
d.clear; |
|||
d.writeln; // ["a":1, "b":2] |
|||
d["a"] = 55; d["b"] = 66; |
|||
d["a"].writeln; // 55 |
|||
d.remove("a"); |
|||
d.writeln; // ["a":1, "b":66] |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
<pre>["a":1, "b":2] |
|||
["a":55, "b":66] |
|||
["a":1, "b":2] |
|||
55 |
|||
["a":1, "b":66]</pre> |
|||
=={{header|Go}}== |
|||
Go's built-in map type is mutable and so, to complete this task, we need to create a read-only wrapper for it which doesn't permit further items to be added or existing items to be deleted though does allow them to be reset to their default value. |
|||
First create a sub-directory, romap, of the project directory and place the following package in it: |
|||
<syntaxhighlight lang="go">package romap |
|||
type Romap struct{ imap map[byte]int } |
|||
// Create new read-only wrapper for the given map. |
|||
func New(m map[byte]int) *Romap { |
|||
if m == nil { |
|||
return nil |
|||
} |
|||
return &Romap{m} |
|||
} |
|||
// Retrieve value for a given key, if it exists. |
|||
func (rom *Romap) Get(key byte) (int, bool) { |
|||
i, ok := rom.imap[key] |
|||
return i, ok |
|||
} |
|||
// Reset value for a given key, if it exists. |
|||
func (rom *Romap) Reset(key byte) { |
|||
_, ok := rom.imap[key] |
|||
if ok { |
|||
rom.imap[key] = 0 // default value of int |
|||
} |
|||
}</syntaxhighlight> |
|||
This package can now be imported and used within the main package as follows: |
|||
<syntaxhighlight lang="go">package main |
|||
import ( |
|||
"./romap" |
|||
"fmt" |
|||
) |
|||
func main() { |
|||
// create a normal map |
|||
m := map[byte]int{'A': 65, 'B': 66, 'C': 67} |
|||
// place it in a read-only wrapper so no new item can be added or item deleted. |
|||
rom := romap.New(m) |
|||
// retrieve value represented by 'C' say |
|||
i, _ := rom.Get('C') |
|||
fmt.Println("'C' maps to", i) |
|||
// reset this to default value (doesn't actually delete the key) |
|||
rom.Reset('C') |
|||
i, _ = rom.Get('C') |
|||
fmt.Println("'C' now maps to", i) |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
'C' maps to 67 |
|||
'C' now maps to 0 |
|||
</pre> |
|||
=={{header|J}}== |
=={{header|J}}== |
||
Line 28: | Line 282: | ||
Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be: |
Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be: |
||
< |
<syntaxhighlight lang="j">lookup=: values {~ keys&i.</syntaxhighlight> |
||
For example: |
For example: |
||
< |
<syntaxhighlight lang="j"> lookup=: 10 20 30 40 50 {~ (;:'this is a test')&i. |
||
lookup ;:'a test' |
lookup ;:'a test' |
||
30 40</ |
30 40</syntaxhighlight> |
||
Notes: |
Notes: |
||
Line 43: | Line 297: | ||
3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else. |
3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else. |
||
=={{header|Java}}== |
|||
Java supports unmodifiable maps, sets, lists, and other more specialized unmodifiable collections. In this example, we have a unmodifiable map. We first create an ordinary map, modify as needed, then call the <code>Collections.unmodifiableMap</code>. We can subsequently read the map, but modification is not permitted. The returned map will subsequently throw a <code>UnsupportedOperationException</code> exception if a mutation operator is called. Several are demonstrated below. |
|||
<syntaxhighlight lang="java"> |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
// Title: Create an object/Native demonstration |
|||
public class ImmutableMap { |
|||
public static void main(String[] args) { |
|||
Map<String,Integer> hashMap = getImmutableMap(); |
|||
try { |
|||
hashMap.put("Test", 23); |
|||
} |
|||
catch (UnsupportedOperationException e) { |
|||
System.out.println("ERROR: Unable to put new value."); |
|||
} |
|||
try { |
|||
hashMap.clear(); |
|||
} |
|||
catch (UnsupportedOperationException e) { |
|||
System.out.println("ERROR: Unable to clear map."); |
|||
} |
|||
try { |
|||
hashMap.putIfAbsent("Test", 23); |
|||
} |
|||
catch (UnsupportedOperationException e) { |
|||
System.out.println("ERROR: Unable to put if absent."); |
|||
} |
|||
for ( String key : hashMap.keySet() ) { |
|||
System.out.printf("key = %s, value = %s%n", key, hashMap.get(key)); |
|||
} |
|||
} |
|||
private static Map<String,Integer> getImmutableMap() { |
|||
Map<String,Integer> hashMap = new HashMap<>(); |
|||
hashMap.put("Key 1", 34); |
|||
hashMap.put("Key 2", 105); |
|||
hashMap.put("Key 3", 144); |
|||
return Collections.unmodifiableMap(hashMap); |
|||
} |
|||
} |
|||
</syntaxhighlight> |
|||
{out}} |
|||
<pre> |
|||
ERROR: Unable to put new value. |
|||
ERROR: Unable to clear map. |
|||
ERROR: Unable to put if absent. |
|||
key = Key 1, value = 34 |
|||
key = Key 2, value = 105 |
|||
key = Key 3, value = 144 |
|||
</pre> |
|||
=={{header|JavaScript}}== |
=={{header|JavaScript}}== |
||
Line 49: | Line 364: | ||
{{works with|JavaScript|1.7}} |
{{works with|JavaScript|1.7}} |
||
< |
<syntaxhighlight lang="javascript">var keyError = new Error("Invalid Key Error (FixedKeyDict)") ; |
||
function FixedKeyDict(obj) |
function FixedKeyDict(obj) |
||
Line 111: | Line 426: | ||
return "FixedKeyDict{" + s + "}" ; |
return "FixedKeyDict{" + s + "}" ; |
||
} ; |
} ; |
||
}</ |
}</syntaxhighlight> |
||
Test run: |
Test run: |
||
< |
<syntaxhighlight lang="javascript"> |
||
const BR = "<BR>\n" |
const BR = "<BR>\n" |
||
Line 149: | Line 464: | ||
pl("error test : " + e.message) ; |
pl("error test : " + e.message) ; |
||
} |
} |
||
</syntaxhighlight> |
|||
</lang> |
|||
output : |
output : |
||
Line 171: | Line 486: | ||
</pre> |
</pre> |
||
=={{header| |
=={{header|jq}}== |
||
jq objects are JSON objects and can be created using JSON syntax, e.g. <syntaxhighlight lang="jq">{"language": "jq"}</syntaxhighlight> |
|||
<lang Mathematica>a[1] = "Do not modify after creation"; |
|||
Objects can also be created programmatically, e.g. <syntaxhighlight lang="jq">{"one": 1} + {"two": 2}</syntaxhighlight> |
|||
jq objects, however, are really just values: they are immutable, and cannot be "deleted" any more than the number 1 can be deleted. |
|||
=={{header|Julia}}== |
|||
<syntaxhighlight lang="julia"> |
|||
using BackedUpImmutable |
|||
function testBackedUpImmutableDict() |
|||
fibr = BackedUpImmutableDict{String,Int64}(["a" => 0, "b" => 1, "c" => 1, "d" => 2, |
|||
"e" => 3, "f" => 5, "g" => 8, "h" => 13, "i" => 21, "j" => 34, "extra" => -1]) |
|||
x = fibr["extra"] |
|||
@test x == -1 |
|||
fibr["extra"] = 0 |
|||
y = fibr["extra"] |
|||
@test y == 0 |
|||
restore!(fibr, "extra") |
|||
z = fibr["extra"] |
|||
@test z == -1 |
|||
@test_throws String begin fibr["k"] = 55 end |
|||
fibr["a"] = 9 |
|||
fibr["b"] = 7 |
|||
# test restore all to default |
|||
restoreall!(fibr) |
|||
@test fibr["a"] == 0 |
|||
end |
|||
</syntaxhighlight> |
|||
All tests pass. |
|||
=={{header|Kotlin}}== |
|||
<syntaxhighlight lang="scala">// version 1.1.2 |
|||
fun main(args: Array<String>) { |
|||
// This line creates a read-only map which cannot be changed in any way nor cleared |
|||
val map = mapOf('A' to 65, 'B' to 66, 'C' to 67) |
|||
println(map) |
|||
}</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
{A=65, B=66, C=67} |
|||
</pre> |
|||
=={{header|M2000 Interpreter}}== |
|||
{{trans|C sharp}} |
|||
<syntaxhighlight lang="m2000 interpreter"> |
|||
Module CheckIt { |
|||
Class LockedHash { |
|||
Private: |
|||
inventory Vars ' no same keys |
|||
unlock |
|||
module nosuchvariable { |
|||
Error "No such value:"+letter$ |
|||
} |
|||
module NoNewItem { |
|||
Error "No new item, use unlock method before" |
|||
} |
|||
module NoRemoveItem { |
|||
Error "Can't remove item, use unlock method before" |
|||
} |
|||
Public: |
|||
module Unlock { |
|||
.unlock<=True |
|||
} |
|||
module Writeln { |
|||
m=each(.Vars) |
|||
while m { |
|||
Print Quote$(Eval$(m, m^));",";Eval(m), |
|||
} |
|||
Print |
|||
} |
|||
Value (st$){ |
|||
st$=Ucase$(st$) |
|||
if exist(.Vars, st$) then =Eval(.Vars) : Exit |
|||
.nosuchvariable st$ |
|||
} |
|||
Set (st$){ |
|||
st$=Ucase$(st$) |
|||
Read val |
|||
if exist(.Vars, st$) then Return .Vars, st$:=val : Exit |
|||
If .unlock then { Append .Vars, st$:=val} Else .NoNewItem |
|||
} |
|||
module Remove (st$) { |
|||
if not .unlock then .NoRemoveItem |
|||
st$=Ucase$(st$) |
|||
Try { |
|||
delete .Vars, st$ |
|||
} |
|||
} |
|||
module Clear { |
|||
Clear .Vars |
|||
} |
|||
Class: ' this part exist only at construction |
|||
module LockedHash { |
|||
While match("SN") { |
|||
read st$, val |
|||
st$=ucase$(st$) |
|||
\\ if we append key which exist we get error |
|||
Append .Vars, st$:=val |
|||
} |
|||
} |
|||
} |
|||
d=LockedHash("a", 1, "b", 2) |
|||
d.writeln |
|||
d("a")=55 : d("b")=66 |
|||
d.writeln |
|||
d.clear |
|||
d.writeln |
|||
d.unlock |
|||
d("a")=55 : d("b")=66 |
|||
Print d("a")=55, d("a")/d("b")<1 |
|||
d.remove "a" |
|||
d.writeln |
|||
} |
|||
Checkit |
|||
</syntaxhighlight> |
|||
=={{header|Mathematica}} / {{header|Wolfram Language}}== |
|||
<syntaxhighlight lang="mathematica">a[1] = "Do not modify after creation"; |
|||
a[2] = "Native demonstration"; |
a[2] = "Native demonstration"; |
||
Protect[a];</ |
Protect[a];</syntaxhighlight> |
||
Example usage: |
Example usage: |
||
<pre>a[3] = 2 |
<pre>a[3] = 2 |
||
->Set::write: Tag a in a[1] is Protected. >> |
->Set::write: Tag a in a[1] is Protected. >> |
||
</pre> |
</pre> |
||
=={{header|Nim}}== |
|||
We leverage native stdlib table as our own object by implementing limited actual native table functionalities. |
|||
<syntaxhighlight lang="nim">import tables, options |
|||
type |
|||
MyTable = object |
|||
table: TableRef[string, int] |
|||
# return empty if the key is not available |
|||
proc `[]`(m: MyTable, key: string): Option[int] = |
|||
if key in m.table: result = some m.table[key] |
|||
else: result = none int |
|||
# update an item, doing nothing if the key is available during first initialization |
|||
proc `[]=`(m: var MyTable, key: string, val: int) = |
|||
if key notin m.table: return |
|||
m.table[key] = val |
|||
proc reset(m: var MyTable) = |
|||
for _, v in m.table.mpairs: v = 0 |
|||
# sugar for defining MyTable object |
|||
proc toTable(vals: openarray[(string, int)]): MyTable = |
|||
result.table = newTable vals |
|||
proc main = |
|||
# MyTable construction |
|||
var myobj = {"key1": 1, "key2": 2, "key3": 3}.toTable |
|||
# test getting existing key |
|||
let val1 = myobj["key1"] |
|||
if val1.isSome: echo "val1: ", val1.get |
|||
# test adding new key |
|||
myobj["key4"] = 4 |
|||
let val4 = myobj["key4"] |
|||
if val4.isSome: echo val4.get |
|||
else: echo "val4 is empty" |
|||
# test reset and test whether its value is zero-ed |
|||
reset myobj |
|||
doAssert myobj["key3"].get == 0 |
|||
main() |
|||
</syntaxhighlight> |
|||
{{output}} |
|||
<pre> |
|||
val1: 1 |
|||
val4 is empty</pre> |
|||
=={{header|Perl}}== |
=={{header|Perl}}== |
||
<syntaxhighlight lang="perl">use strict; |
|||
<lang perl>package LockedHash; |
|||
use parent Tie::Hash; |
|||
package LockedHash; |
|||
use parent 'Tie::Hash'; |
|||
use Carp; |
use Carp; |
||
use strict; |
|||
sub TIEHASH { |
sub TIEHASH { |
||
Line 224: | Line 713: | ||
} |
} |
||
sub lock_hash(\%) { |
sub lock_hash :prototype(\%) { |
||
my $ref = shift; |
my $ref = shift; |
||
tie(%$ref, __PACKAGE__, %$ref); |
tie(%$ref, __PACKAGE__, %$ref); |
||
Line 249: | Line 738: | ||
# add a new key x: will die |
# add a new key x: will die |
||
eval { $h{x} = 1 }; |
eval { $h{x} = 1 }; |
||
if ($@) { print "Operation error: $@" }</ |
if ($@) { print "Operation error: $@" }</syntaxhighlight>output:<syntaxhighlight lang="text">a => 3 |
||
b => 4 |
b => 4 |
||
c => 5 |
c => 5 |
||
Line 258: | Line 747: | ||
operation error: Can't add key x at test.pl line 14 |
operation error: Can't add key x at test.pl line 14 |
||
LockedHash::STORE('LockedHash=HASH(0x8cebe14)', 'x', 1) called at test.pl line 66 |
LockedHash::STORE('LockedHash=HASH(0x8cebe14)', 'x', 1) called at test.pl line 66 |
||
eval {...} called at test.pl line 66</ |
eval {...} called at test.pl line 66</syntaxhighlight> |
||
=={{header|Phix}}== |
|||
There is no native "read-only" setting on phix dictionaries, so the following wraps a pair of them to |
|||
provide the requested functionality. |
|||
<!--<syntaxhighlight lang="phix">(phixonline)--> |
|||
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span> |
|||
<span style="color: #008080;">enum</span> <span style="color: #000000;">STD</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">CUR</span> |
|||
<span style="color: #004080;">sequence</span> <span style="color: #000000;">fkds</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{}</span> <span style="color: #000080;font-style:italic;">-- fixed key dictionaries ;-)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">freelist</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span> |
|||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">fkd_destroy</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">std</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #7060A8;">destroy_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">std</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #7060A8;">destroy_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">freelist</span> |
|||
<span style="color: #000000;">freelist</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">id</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span> |
|||
<span style="color: #008080;">function</span> <span style="color: #000000;">fkd_new</span><span style="color: #0000FF;">(</span><span style="color: #004080;">sequence</span> <span style="color: #000000;">key_pairs</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">std</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">new_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key_pairs</span><span style="color: #0000FF;">),</span> |
|||
<span style="color: #000000;">cur</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">new_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">std</span><span style="color: #0000FF;">),</span> |
|||
<span style="color: #000000;">id</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">freelist</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> |
|||
<span style="color: #000000;">fkds</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">append</span><span style="color: #0000FF;">(</span><span style="color: #000000;">fkds</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">std</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">})</span> |
|||
<span style="color: #000000;">id</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">fkds</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">else</span> |
|||
<span style="color: #000000;">freelist</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">std</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">}</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #008080;">return</span> <span style="color: #000000;">id</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span> |
|||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">fkd_clear</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">std</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #7060A8;">destroy_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">][</span><span style="color: #000000;">CUR</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">new_dict</span><span style="color: #0000FF;">(</span><span style="color: #000000;">std</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span> |
|||
<span style="color: #008080;">function</span> <span style="color: #000000;">fkd_get</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">object</span> <span style="color: #000000;">key</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">return</span> <span style="color: #7060A8;">getd</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key</span><span style="color: #0000FF;">,</span><span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">][</span><span style="color: #000000;">CUR</span><span style="color: #0000FF;">])</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span> |
|||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">object</span> <span style="color: #000000;">key</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">data</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">node</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">getd_index</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key</span><span style="color: #0000FF;">,</span><span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">][</span><span style="color: #000000;">CUR</span><span style="color: #0000FF;">])</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #000000;">node</span><span style="color: #0000FF;">=</span><span style="color: #004600;">NULL</span> <span style="color: #008080;">then</span> <span style="color: #008080;">throw</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"invalid/new key"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #7060A8;">setd</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key</span><span style="color: #0000FF;">,</span><span style="color: #000000;">data</span><span style="color: #0000FF;">,</span><span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">][</span><span style="color: #000000;">CUR</span><span style="color: #0000FF;">])</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span> |
|||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">fkd_remove</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">object</span> <span style="color: #000000;">key</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">std</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">],</span> |
|||
<span style="color: #000000;">node</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">getd_index</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key</span><span style="color: #0000FF;">,</span><span style="color: #000000;">std</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">if</span> <span style="color: #000000;">node</span><span style="color: #0000FF;">=</span><span style="color: #004600;">NULL</span> <span style="color: #008080;">then</span> <span style="color: #008080;">throw</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"invalid key"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span> |
|||
<span style="color: #7060A8;">setd</span><span style="color: #0000FF;">(</span><span style="color: #000000;">key</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">getd_by_index</span><span style="color: #0000FF;">(</span><span style="color: #000000;">node</span><span style="color: #0000FF;">,</span><span style="color: #000000;">std</span><span style="color: #0000FF;">),</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span> |
|||
<span style="color: #008080;">function</span> <span style="color: #000000;">fkd_sprint</span><span style="color: #0000FF;">(</span><span style="color: #004080;">integer</span> <span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">cur</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkds</span><span style="color: #0000FF;">[</span><span style="color: #000000;">id</span><span style="color: #0000FF;">][</span><span style="color: #000000;">CUR</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #004080;">sequence</span> <span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">getd_all_keys</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">res</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span> |
|||
<span style="color: #004080;">object</span> <span style="color: #000000;">ri</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> |
|||
<span style="color: #000000;">res</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">ri</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">getd</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ri</span><span style="color: #0000FF;">,</span><span style="color: #000000;">cur</span><span style="color: #0000FF;">)}</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span> |
|||
<span style="color: #008080;">return</span> <span style="color: #000000;">res</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span> |
|||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">main</span><span style="color: #0000FF;">()</span> |
|||
<span style="color: #004080;">integer</span> <span style="color: #000000;">id</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">fkd_new</span><span style="color: #0000FF;">({{</span><span style="color: #008000;">"a"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">1</span><span style="color: #0000FF;">},{</span><span style="color: #008000;">"b"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">2</span><span style="color: #0000FF;">}})</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">fkd_sprint</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- {{"a",1},{"b",2}}</span> |
|||
<span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"a"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">55</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"b"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">66</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">fkd_sprint</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- {{"a",55},{"b",66}}</span> |
|||
<span style="color: #000000;">fkd_clear</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">fkd_sprint</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- {{"a",1},{"b",2}}</span> |
|||
<span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"a"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">55</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"b"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">66</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">fkd_get</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"a"</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- 55</span> |
|||
<span style="color: #000000;">fkd_remove</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"a"</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">try</span> |
|||
<span style="color: #000000;">fkd_set</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"NoNewKey"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">77</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">catch</span> <span style="color: #000000;">e</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">e</span><span style="color: #0000FF;">[</span><span style="color: #004600;">E_USER</span><span style="color: #0000FF;">]</span> <span style="color: #000080;font-style:italic;">-- "invalid/new key"</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">try</span> |
|||
<span style="color: #0000FF;">?</span><span style="color: #000000;">fkd_sprint</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- {{"a",1},{"b",66}}</span> |
|||
<span style="color: #000000;">fkd_destroy</span><span style="color: #0000FF;">(</span><span style="color: #000000;">id</span><span style="color: #0000FF;">)</span> |
|||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span> |
|||
<span style="color: #000000;">main</span><span style="color: #0000FF;">()</span> |
|||
<!--</syntaxhighlight>--> |
|||
=={{header|Python}}== |
=={{header|Python}}== |
||
< |
<syntaxhighlight lang="python"> |
||
from collections import UserDict |
from collections import UserDict |
||
import copy |
import copy |
||
Line 348: | Line 924: | ||
raise KeyError |
raise KeyError |
||
else: |
else: |
||
return super().setdefault(key, default)</ |
return super().setdefault(key, default)</syntaxhighlight> |
||
=={{header| |
=={{header|Racket}}== |
||
{{trans|D}} |
|||
{{in progress|lang=Ruby|day=15|month=February|year=2011}} |
|||
This task is implemented as a new fenced-hash time with an interface similar to the native hash. Also it can be used a native dict. |
|||
Implementation of functions that handle fenced-hash: |
|||
TODO: Write comments for FencedHash::new, FencedHash#delete and related methods. Add more methods (merge, merge!, reject, reject!, select, select!, update). Explain why FencedHash#replace and FencedHash#shift will not exist. |
|||
<syntaxhighlight lang="racket"> |
|||
;(struct fenced-hash (actual original) ...) |
|||
(define (fenced-hash-ref dict |
|||
<lang ruby># fencedhash.rb |
|||
key |
|||
require 'forwardable' |
|||
[default (lambda () (error "key not found" key))]) |
|||
(hash-ref (fenced-hash-actual dict) key default)) |
|||
(define (fenced-hash-set! dict key val) |
|||
(unless (hash-has-key? (fenced-hash-actual dict) key) |
|||
(error "unable to add key" key)) |
|||
(hash-set! (fenced-hash-actual dict) key val)) |
|||
(define (fenced-hash-remove! dict key) ;reset the value! |
|||
(unless (hash-has-key? (fenced-hash-actual dict) key) |
|||
(error "key not found" key)) |
|||
(hash-set! (fenced-hash-actual dict) |
|||
key |
|||
(hash-ref (fenced-hash-original dict) key))) |
|||
(define (fenced-hash-clear! dict) ;reset all values! |
|||
(hash-for-each (fenced-hash-original dict) |
|||
(lambda (key val) (hash-set! (fenced-hash-actual dict) key val)))) |
|||
(define (fenced-hash-has-key? dict key) |
|||
# A FencedHash acts like a Hash, but with a fence around its keys. |
|||
(hash-has-key? (fenced-hash-actual dict) key)) |
|||
# After the creation of a FencedHash, one cannot add nor remove keys. |
|||
(define (fenced-hash-count dict) |
|||
# Any attempt to insert a new key will raise KeyError. Any attempt to |
|||
(hash-count (fenced-hash-actual dict))) |
|||
# delete a key-value pair will keep the key but will reset the value to |
|||
# the default value. |
|||
class FencedHash < Object |
|||
extend Forwardable |
|||
include Enumerable |
|||
(define (fenced-hash-iterate-first dict) |
|||
#-- |
|||
(hash-iterate-first (fenced-hash-actual dict))) |
|||
# @hash: our Hash inside the fence |
|||
(define (fenced-hash-iterate-next dict pos) |
|||
# @default_proc: passes self, not @hash |
|||
(hash-iterate-next (fenced-hash-actual dict) pos)) |
|||
#++ |
|||
(define (fenced-hash-iterate-key dict pos) |
|||
def_delegators(:@hash, :[], :assoc, |
|||
(hash-iterate-key (fenced-hash-actual dict) pos)) |
|||
:compare_by_identity, :compare_by_identity?, |
|||
(define (fenced-hash-iterate-value dict pos) |
|||
:default, :empty?, :fetch, :flatten, |
|||
(hash-iterate-value (fenced-hash-actual dict) pos)) |
|||
:has_key?, :has_value?, :hash, :include?, |
|||
:key, :key?, :keys, :length, :member?, |
|||
:rassoc, :size, :to_a, |
|||
:values, :values_at, :value?) |
|||
attr_reader :default_proc |
|||
(define (*fenced-hash-print dict port mode) |
|||
# Acts like Hash::[] but creates a FencedHash. |
|||
;private custom-write ;mode is ignored |
|||
def self.[](*args) |
|||
(write-string "#fenced-hash" port) |
|||
allocate.instance_eval do |
|||
(write (hash->list (fenced-hash-actual dict)) port))</syntaxhighlight> |
|||
@hash = Hash[*args] |
|||
self |
|||
end |
|||
end |
|||
Definition of the actual structure and a “public” creator: |
|||
# call-seq: |
|||
<syntaxhighlight lang="racket">(struct fenced-hash (actual original) |
|||
# FencedHash.new(obj=nil [,keys]) -> fh |
|||
#:extra-constructor-name *fenced-hash ;private constructor |
|||
# FencedHash.new([keys]) { |fh, key| block } -> fh |
|||
#:omit-define-syntaxes ;not sure this is a good idea |
|||
# |
|||
#:methods gen:custom-write |
|||
# Creates a FencedHash..... |
|||
[(define write-proc *fenced-hash-print)] |
|||
def initialize(*args, &block) |
|||
n = args.length |
|||
#:methods gen:dict |
|||
if block_given? |
|||
[(define dict-ref fenced-hash-ref) |
|||
raise ArgumentError, "wrong number of arguments" if n > 1 |
|||
(define dict-set! fenced-hash-set!) |
|||
(define dict-remove! fenced-hash-remove!) |
|||
(define dict-has-key? fenced-hash-has-key?) ;unused in 5.6.3 |
|||
(define dict-count fenced-hash-count) |
|||
(define dict-iterate-first fenced-hash-iterate-first) |
|||
(define dict-iterate-next fenced-hash-iterate-next) |
|||
(define dict-iterate-key fenced-hash-iterate-key) |
|||
(define dict-iterate-value fenced-hash-iterate-value)]) |
|||
@default_proc = block |
|||
@hash = Hash.new { |hash, key| block[self, key] } |
|||
if n > 0 |
|||
args[0].each { |key| @hash[key] = nil } |
|||
clear |
|||
end |
|||
else |
|||
raise ArgumentError, "wrong number of arguments" if n > 2 |
|||
(define (fenced-hash . args) ; public constructor |
|||
default = if n > 0 then n[0] else nil end |
|||
(define original (apply hash args)) |
|||
(*fenced-hash (hash-copy original) original))</syntaxhighlight> |
|||
if n > 1 |
|||
args[1].each { |key| @hash[key] = default } |
|||
end |
|||
end |
|||
end |
|||
'''Example:''' Use the fenced-hash functions: |
|||
def initialize_copy(orig) |
|||
<syntaxhighlight lang="racket">(define d (fenced-hash "a" 1 "b" 2)) |
|||
super |
|||
@hash = @hash.dup |
|||
end |
|||
(displayln d) |
|||
# Clears all values. For each key-value pair, this retains the key |
|||
(fenced-hash-set! d "a" 55) |
|||
# but resets the value to default. |
|||
(fenced-hash-set! d "b" 66) |
|||
#-- |
|||
(displayln d) |
|||
# The line "@hash = @hash" checks that _self_ is not frozen, because |
|||
(fenced-hash-clear! d) |
|||
# Object#freeze only freezes _self_ and not @hash. |
|||
(displayln d) |
|||
#++ |
|||
(fenced-hash-set! d "a" 55) |
|||
def clear |
|||
(fenced-hash-set! d "b" 66) |
|||
@hash = @hash |
|||
(displayln d) |
|||
@hash.each_key { |key| delete key } |
|||
(fenced-hash-remove! d "a") |
|||
self |
|||
(displayln d)</syntaxhighlight> |
|||
end |
|||
{{out}} |
|||
<pre>#fenced-hash(("b" . 2) ("a" . 1)) |
|||
#fenced-hash(("b" . 66) ("a" . 55)) |
|||
#fenced-hash(("b" . 2) ("a" . 1)) |
|||
#fenced-hash(("b" . 66) ("a" . 55)) |
|||
#fenced-hash(("b" . 66) ("a" . 1))</pre> |
|||
'''Example (continued):''' Use the same object as a dict. The dict-clear! method is not defined, so we must call fenced-hash-clear! instead. |
|||
# ..... |
|||
<syntaxhighlight lang="racket">(fenced-hash-clear! d) |
|||
def default=(obj) |
|||
(displayln d) |
|||
@default_proc = nil |
|||
(dict-set! d "a" 55) |
|||
@hash.default = obj |
|||
(dict-set! d "b" 66) |
|||
end |
|||
(displayln d) |
|||
(fenced-hash-clear! d) ;dict-clear is not defined |
|||
(displayln d) |
|||
(dict-set! d "a" 55) |
|||
(dict-set! d "b" 66) |
|||
(displayln d) |
|||
(dict-remove! d "a") |
|||
(displayln d)</syntaxhighlight> |
|||
{{out}} |
|||
<pre>#fenced-hash(("b" . 2) ("a" . 1)) |
|||
#fenced-hash(("b" . 66) ("a" . 55)) |
|||
#fenced-hash(("b" . 2) ("a" . 1)) |
|||
#fenced-hash(("b" . 66) ("a" . 55)) |
|||
#fenced-hash(("b" . 66) ("a" . 1))</pre> |
|||
=={{header|Raku}}== |
|||
# ..... |
|||
(formerly Perl 6) |
|||
def default_proc=(proc_obj) |
|||
{{Works with|rakudo|2016.08}} |
|||
# Convert _proc_obj_ to a block parameter. |
|||
Here we use delegation to handle all the normal hash methods that we don't need to override to define our new class. |
|||
proc_obj = proc &proc_obj |
|||
<syntaxhighlight lang="raku" line>class FixedHash { |
|||
has $.hash handles *; |
|||
method new(*@args) { self.bless: hash => Hash.new: @args } |
|||
method AT-KEY(FixedHash:D: $key is copy) is rw { |
|||
$!hash.EXISTS-KEY($key) ?? $!hash.AT-KEY($key) !! Failure.new(q{can't store value for unknown key}); |
|||
} |
|||
method DELETE-KEY($key) { $!hash.{$key} = Nil } |
|||
} |
|||
# Testing |
|||
@hash.default_proc = proc { |hash, key| proc_obj[self, key] } |
|||
my $fh = FixedHash.new: "a" => 1, "b" => 2; |
|||
@default_proc = proc_obj |
|||
say $fh<a b>; # 1 2 |
|||
end |
|||
$fh<b>:delete; |
|||
say $fh<a b>; # 1 Nil |
|||
$fh<b> = 42; |
|||
say $fh<a b>; # 1 42 |
|||
say $fh<c>; # Nil |
|||
$fh<c> = 43; # error |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre>(1 2) |
|||
(1 (Any)) |
|||
(1 42) |
|||
can't store value for unknown key |
|||
in block <unit> at native-demonstration.p6:17 |
|||
Actually thrown at: |
|||
# Deletes the value of the key-value pair for _key_. |
|||
in block <unit> at native-demonstration.p6:17</pre> |
|||
# |
|||
# If _key_ is in the fence..... |
|||
def delete(key) |
|||
@hash = @hash |
|||
By defining [http://design.raku.org/S12.html#FALLBACK_methods FALLBACK] any class can handle undefined method calls. Since any class inherits plenty of methods from <tt>Any</tt> our magic object will be more of a novice conjurer then a master wizard proper. |
|||
begin |
|||
original_value = @hash.fetch(key) |
|||
rescue IndexError |
|||
# _key_ is not in the fence. |
|||
if block_given? |
|||
yield key |
|||
else |
|||
nil |
|||
end |
|||
else |
|||
# _key_ is in the fence. |
|||
if @default_proc |
|||
@default_proc[self, key] |
|||
else |
|||
@hash[key] = @hash.default |
|||
end |
|||
original_value |
|||
end |
|||
end |
|||
<syntaxhighlight lang="raku" line>class Magic { |
|||
# ..... |
|||
has %.hash; |
|||
def delete_if |
|||
multi method FALLBACK($name, |c) is rw { # this will eat any extra parameters |
|||
return enum_for(:delete_if) unless block_given? |
|||
%.hash{$name} |
|||
} |
|||
multi method FALLBACK($name) is rw { |
|||
@hash = @hash |
|||
%.hash{$name} |
|||
@hash.each { |key, value| delete key if yield key, value } |
|||
} |
|||
} |
|||
end |
|||
my $magic = Magic.new; |
|||
# Yields each key-value pair to the block, or returns an enumerator. |
|||
$magic.foo = 10; |
|||
# Acts like Hash#each. |
|||
say $magic.foo; |
|||
def each &block # :yields: key, value |
|||
$magic.defined = False; # error</syntaxhighlight> |
|||
return enum_for(:each) unless block |
|||
@hash.each &block |
|||
end |
|||
alias each_pair each |
|||
{{output}} |
|||
# Yields each key to the block, or returns an enumerator. |
|||
<pre>10 |
|||
# Acts like Hash#each_key. |
|||
Cannot modify an immutable Bool |
|||
def each_key &block # :yields: key |
|||
in block <unit> at native-demonstration.p6:15</pre> |
|||
return enum_for(:each_key) unless block |
|||
@hash.each_key &block |
|||
end |
|||
=={{header|Ring}}== |
|||
# Yields each value to the block, or returns an enumerator. |
|||
<syntaxhighlight lang="ring"> |
|||
# Acts like Hash#each_value. |
|||
# Project : Create an object/Native demonstration |
|||
def each_value &block # :yields: value |
|||
return enum_for(:each_value) unless block |
|||
@hash.each_value &block |
|||
end |
|||
map = [] |
|||
# Returns true if _other_ is a FencedHash and has the same key-value |
|||
map["A"] = 65 |
|||
# pairs as _self_. Acts like Hash#eql?. |
|||
map["B"] = 66 |
|||
#-- |
|||
map["C"] = 67 |
|||
# Consistent with FencedHash#hash because it delegates to @hash.hash. |
|||
see map + nl |
|||
</syntaxhighlight> |
|||
def eql?(other) |
|||
Output: |
|||
FencedHash === other and |
|||
<pre> |
|||
@hash.eql?(other.instance_eval { @hash }) |
|||
A |
|||
end |
|||
65 |
|||
B |
|||
66 |
|||
C |
|||
67 |
|||
</pre> |
|||
=={{header|Ruby}}== |
|||
# Returns true if _other_ is a FencedHash and if the key-value pairs |
|||
{{works with|Ruby|1.9}} |
|||
# of _self_ equal those of _other_. Acts like Hash#==. |
|||
<syntaxhighlight lang="ruby"># A FencedHash acts like a Hash, but with a fence around its keys. |
|||
def ==(other) |
|||
# One may change its values, but not its keys. Any attempt to insert |
|||
FencedHash === other and |
|||
# a new key raises KeyError. One may delete a key, but this only |
|||
@hash == (other.instance_eval { @hash }) |
|||
# restores its original value. |
|||
end |
|||
# |
|||
# FencedHash reimplements these Hash methods: #[] #[]= #clear #delete |
|||
# #delete_if #default #default= #each_key #each_pair #each_value |
|||
# #fetch #has_key? #keep_if #keys #length #values #values_at |
|||
class FencedHash |
|||
# |
# call-seq: |
||
# FencedHash.new(hash, obj=nil) -> fh |
|||
def keep_if |
|||
# |
|||
return enum_for(:keep_if) unless block_given? |
|||
# Creates a FencedHash that takes its keys and original values from |
|||
# a source _hash_. The source _hash_ can be any object that |
|||
@hash = @hash |
|||
# responds to each_pair. Sets the default value for missing keys to |
|||
@hash.each { |key, value| delete key unless yield key, value } |
|||
# _obj_, so FencedHash#[] returns _obj_ when a key is not in fence. |
|||
self |
|||
def initialize(hash, obj=nil) |
|||
end |
|||
@default = obj |
|||
@hash = {} |
|||
# Stores a _value_ for a _key_. This only works if _key_ is in the |
|||
hash.each_pair do |key, value| |
|||
# fence; FencedHash prevents the insertion of new keys. If _key_ is |
|||
# @hash[key][0] = current value |
|||
# not in the fence, then this method raises KeyError. |
|||
# @hash[key][1] = original value |
|||
@hash = |
@hash[key] = [value, value] |
||
if @hash.has_key? key |
|||
@hash.store(key, value) |
|||
else |
|||
c = if defined? KeyError then KeyError else IndexError end |
|||
raise c, "fence prevents new key: #{key}" |
|||
end |
end |
||
end |
end |
||
alias []= store |
|||
def initialize_clone(orig) |
|||
# Converts _self_ to a regular Hash. Returns a new Hash that has the |
|||
# Object#clone calls here in Ruby 2.0. If _orig_ was frozen, then |
|||
# same key-value pairs as _self_. |
|||
# each array of _values_ is frozen, so make frozen clones. |
|||
def to_hash |
|||
super |
|||
copy = {} |
|||
@hash.each_pair {|key, values| copy[key] = values.clone } |
|||
@hash = copy |
|||
end |
end |
||
def initialize_dup(orig) |
|||
# Converts _self_ to a String. |
|||
# Object#dup calls here in Ruby 2.0. If _orig_ was frozen, then |
|||
def to_s |
|||
# make duplicates that are not frozen. |
|||
"#<#{self.class}: #{@hash.inspect}>" |
|||
super |
|||
copy = {} |
|||
@hash.each_pair {|key, values| copy[key] = values.dup } |
|||
@hash = copy |
|||
end |
end |
||
alias inspect to_s |
|||
end</lang> |
|||
# Retrieves current value for _key_, like Hash#[]. If _key_ is not |
|||
<lang ruby># fh-test.rb |
|||
# in fence, returns default object. |
|||
require 'fencedhash' |
|||
def [](key) |
|||
require 'test/unit' |
|||
values = @hash[key] |
|||
if values |
|||
class TestFencedHash < Test::Unit::TestCase |
|||
values[0] |
|||
if RUBY_VERSION >= "1.9" |
|||
else |
|||
KeyEx = KeyError |
|||
@default |
|||
end |
|||
KeyEx = IndexError |
|||
FrozenEx = TypeError |
|||
end |
end |
||
# call-seq: |
|||
def setup |
|||
# fh[key] = value -> value |
|||
# fh.store(key, value) -> value |
|||
# |
|||
# Sets _value_ for a _key_. Returns _value. If _key_ is not in |
|||
# fence, raises KeyError. |
|||
def []=(key, value) |
|||
values = @hash[key] |
|||
if values |
|||
values[0] = value |
|||
else |
|||
raise KeyError, "fence prevents adding new key: #{key.inspect}" |
|||
end |
|||
end |
end |
||
alias store []= |
|||
# Resets all keys to their original values. Returns self. |
|||
def test_bracket_operator |
|||
def clear |
|||
assert_equal 11, @fh[:q] |
|||
@hash.each_value {|values| values[0] = values[1]} |
|||
assert_equal 22, @fh[:w] |
|||
self |
|||
assert_equal 33, @fh[:e] |
|||
assert_equal 44, @fh[:r] |
|||
assert_equal 55, @fh[:t] |
|||
assert_equal 66, @fh[:y] |
|||
assert_nil @fh[:u] |
|||
end |
end |
||
# Resets _key_ to its original value. Returns old value before |
|||
def test_delete |
|||
# reset. If _key_ is not in fence, returns +nil+. |
|||
assert_equal 44, (@fh.delete :r) |
|||
def delete(key) |
|||
assert_nil @fh.fetch(:r) |
|||
values = @hash[key] |
|||
if values |
|||
assert_nil @fh.delete(:u) |
|||
old = values[0] |
|||
values[0] = values[1] |
|||
assert_equal "replacement", (@fh.delete :r) |
|||
old # return old |
|||
end # else return nil |
|||
end |
end |
||
# call-seq: |
|||
def test_delete_if |
|||
# fh.delete_if {|key, value| block } -> fh |
|||
# fh.delete_if -> enumerator |
|||
assert_same @fh, a |
|||
# |
|||
assert_equal 2, @fh.values.grep(nil).length |
|||
# Yields each _key_ with current _value_ to _block_. Resets _key_ |
|||
@fh[:y] = "why?" |
|||
# to its original value when block evaluates to true. |
|||
@fh[:t] = "tea!" |
|||
def delete_if |
|||
assert_equal 0, @fh.values.grep(nil).length |
|||
if block_given? |
|||
@hash.each_pair do |key, values| |
|||
yield(key, values[0]) and values[0] = values[1] |
|||
end |
|||
self |
|||
else |
|||
enum_for(:delete_if) { @hash.size } |
|||
end |
|||
end |
end |
||
# The default value for keys not in fence. |
|||
def test_default |
|||
attr_accessor :default |
|||
fruit = FencedHash.new(0, [:apple, :banana, :cranberry]) |
|||
assert_equal [0, 0, 0], fruit.values |
|||
fruit[:apple] += 1 |
|||
fruit[:banana] += 5 |
|||
fruit[:cranberry] *= 5 |
|||
assert_equal 1, fruit[:apple] |
|||
assert_equal 5, fruit[:banana] |
|||
assert_equal 0, fruit[:cranberry] |
|||
assert_equal 0, fruit.default |
|||
end |
|||
# call-seq: |
|||
def test_default_assign |
|||
# fh.each_key {|key| block} -> fh |
|||
assert_nil @fh.default |
|||
# fh.each_key -> enumerator |
|||
@fh.delete :w |
|||
# |
|||
# Yields each key in fence to the block. |
|||
@fh.default = -1 |
|||
def each_key(&block) |
|||
assert_equal -1, @fh.default |
|||
if block |
|||
@hash.each_key(&block) |
|||
self |
|||
else |
|||
assert_equal -1, @fh[:e] |
|||
enum_for(:each_key) { @hash.size } |
|||
end |
|||
end |
end |
||
# call-seq: |
|||
def test_default_proc |
|||
# fh.each_pair {|key, value| block} -> fh |
|||
count = 0 |
|||
# fh.each_pair -> enumerator |
|||
fruit = FencedHash.new([:apple, :banana, :cranberry]) do |h, k| |
|||
# |
|||
if h.key? k then h[k] = [] else count += 1 end |
|||
# Yields each key-value pair to the block, like Hash#each_pair. |
|||
# This yields each [key, value] as an array of 2 elements. |
|||
def each_pair |
|||
if block_given? |
|||
@hash.each_pair {|key, values| yield [key, values[0]] } |
|||
self |
|||
else |
|||
enum_for(:each_pair) { @hash.size } |
|||
end |
end |
||
fruit[:apple].push :red |
|||
fruit[:banana].concat [:green, :yellow] |
|||
fruit[:cranberry].push :red |
|||
assert_equal 1, fruit[:orange] |
|||
assert_equal [:red], fruit[:apple] |
|||
assert_equal [:green, :yellow], fruit[:banana] |
|||
assert_equal [:red], fruit.delete(:cranberry) |
|||
assert_equal 2, fruit[:orange] |
|||
assert_equal [], fruit[:cranberry] |
|||
assert_nil fruit.delete(:orange) |
|||
assert_equal 3, fruit[:orange] |
|||
assert_equal [], fruit.default_proc[FencedHash[1 => 2], 1] |
|||
end |
end |
||
# call-seq |
|||
def test_each |
|||
# fh.each_value {|value| block} -> fh |
|||
count = 0 |
|||
# fh.each_value -> enumerator |
|||
@fh.each do |key, value| |
|||
# |
|||
assert_kind_of Symbol, key |
|||
# Yields current value of each key-value pair to the block. |
|||
assert_kind_of Integer, value |
|||
def each_value |
|||
assert_equal true, (@fh.has_key? key) |
|||
if block_given? |
|||
assert_equal true, (@fh.has_value? value) |
|||
@hash.each_value {|values| yield values[0] } |
|||
count += 1 |
|||
else |
|||
enum_for(:each_value) { @hash.size } |
|||
end |
end |
||
assert_equal 6, count |
|||
end |
end |
||
# call-seq: |
|||
def test_eql? |
|||
# fenhsh.fetch(key [,default]) |
|||
other = FencedHash[:r, 44, :t, 55, :y, 66, |
|||
# fenhsh.fetch(key) {|key| block } |
|||
:q, 11, :w, 22, :e, 33] |
|||
# |
|||
float = FencedHash[:y, 66.0, :t, 55.0, :r, 44.0, |
|||
# Fetches value for _key_. Takes same arguments as Hash#fetch. |
|||
:e, 33.0, :w, 22.0, :q, 11.0] |
|||
def fetch(*argv) |
|||
tt = [true, true] |
|||
argc = argv.length |
|||
unless argc.between?(1, 2) |
|||
raise(ArgumentError, |
|||
if RUBY_VERSION >= "1.9" |
|||
"wrong number of arguments (#{argc} for 1..2)") |
|||
assert_equal tt, [(@fh.eql? other), (other.eql? @fh)] |
|||
end |
|||
assert_equal ff, [(@fh.eql? float), (float.eql? @fh)] |
|||
if argc == 2 and block_given? |
|||
assert_equal ff, [(other.eql? float), (float.eql? other)] |
|||
warn("#{caller[0]}: warning: " + |
|||
"block supersedes default value argument") |
|||
end |
end |
||
key, default = argv |
|||
assert_equal tt, [@fh == other, other == @fh] |
|||
values = @hash[key] |
|||
assert_equal tt, [@fh == float, float == @fh] |
|||
if values |
|||
assert_equal tt, [other == float, float == other] |
|||
values[0] |
|||
elsif block_given? |
|||
yield key |
|||
elsif argc == 2 |
|||
assert_equal ff, [(@fh.eql? h), (h.eql? @fh)] |
|||
default |
|||
else |
|||
raise KeyError, "key not found: #{key.inspect}" |
|||
end |
end |
||
assert_equal ff, [@fh == h, h == @fh] |
|||
end |
end |
||
# Freezes this FencedHash. |
|||
def test_fetch |
|||
def freeze |
|||
assert_equal 11, @fh.fetch(:q) |
|||
@hash.each_value {|values| values.freeze } |
|||
assert_equal 22, @fh.fetch(:w) |
|||
super |
|||
assert_equal 33, @fh.fetch(:e) |
|||
assert_equal 44, @fh.fetch(:r) |
|||
assert_equal 55, @fh.fetch(:t) |
|||
assert_equal 66, @fh.fetch(:y) |
|||
assert_raises(KeyEx) { @fh.fetch :u } |
|||
end |
end |
||
# Returns true if _key_ is in fence. |
|||
def test_freeze |
|||
def has_key?(key) |
|||
assert_equal false, @fh.frozen? |
|||
@ |
@hash.has_key?(key) |
||
2.times do |
|||
assert_equal true, @fh.frozen? |
|||
assert_raises(FrozenEx) { @fh.clear } |
|||
assert_raises(FrozenEx) { @fh.delete :q } |
|||
assert_raises(FrozenEx) { @fh.delete_if { true } } |
|||
assert_raises(FrozenEx) { @fh.keep_if { false } } |
|||
assert_raises(FrozenEx) { @fh.store :w, "different" } |
|||
assert_raises(FrozenEx) { @fh[:w] = "different" } |
|||
# Repeat the tests with a clone. The clone must be frozen. |
|||
@fh = @fh.clone |
|||
end |
|||
# A duplicate is not frozen. |
|||
@fh = @fh.dup |
|||
assert_equal false, @fh.frozen? |
|||
@fh[:w] = "different" |
|||
assert_equal "different", @fh[:w] |
|||
end |
end |
||
alias include? has_key? |
|||
alias member? has_key? |
|||
# call-seq: |
|||
def test_has_key |
|||
# fh.keep_if {|key, value| block } -> fh |
|||
2.times do |t| |
|||
# fh.keep_if -> enumerator |
|||
assert_equal true, (@fh.has_key? :y) |
|||
# |
|||
assert_equal true, (@fh.include? :y) |
|||
# Yields each _key_ with current _value_ to _block_. Resets _key_ |
|||
assert_equal true, (@fh.key? :y) |
|||
# to its original value when block evaluates to false. |
|||
assert_equal true, (@fh.member? :y) |
|||
def keep_if |
|||
if block_given? |
|||
assert_equal false, (@fh.has_key? :u) |
|||
@hash.each_pair do |key, values| |
|||
assert_equal false, (@fh.include? :u) |
|||
yield(key, values[0]) or values[0] = values[1] |
|||
assert_equal false, (@fh.key? :u) |
|||
end |
|||
assert_equal false, (@fh.member? :u) |
|||
self |
|||
else |
|||
# Repeat the tests. |
|||
enum_for(:keep_if) { @hash.size } |
|||
# The fence must prevent any changes to the keys. |
|||
@fh.delete :y |
|||
(@fh[:u] = "value") rescue "ok" |
|||
end |
end |
||
end |
end |
||
# Returns array of keys in fence. |
|||
def test_has_value |
|||
def keys |
|||
assert_equal true, (@fh.has_value? 22) |
|||
@hash.keys |
|||
assert_equal true, (@fh.value? 22) |
|||
assert_equal false, (@fh.has_value? 4444) |
|||
assert_equal false, (@fh.value? 4444) |
|||
end |
end |
||
# Returns number of key-value pairs. |
|||
def test_inject |
|||
def length |
|||
# To get an :inject method, FencedHash should mix in Enumerable. |
|||
@hash.length |
|||
assert_kind_of Enumerable, @fh |
|||
assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] } |
|||
end |
end |
||
alias size length |
|||
# Converts self to a regular Hash. |
|||
def test_keep_if |
|||
def to_h |
|||
a = @fh.keep_if { |key, value| key == :t || value == 66 } |
|||
result = Hash.new(@default) |
|||
assert_same @fh, a |
|||
@hash.each_pair {|key, values| result[key] = values[0]} |
|||
result |
|||
@fh.delete :t |
|||
assert_equal 6, @fh.values.grep(nil).length |
|||
end |
end |
||
# Converts self to a String. |
|||
def test_keys |
|||
def to_s |
|||
assert_equal([:e, :q, :r, :t, :w, :y], |
|||
"#<#{self.class}: #{to_h}>" |
|||
@fh.keys.sort_by { |o| o.to_s }) |
|||
end |
end |
||
alias inspect to_s |
|||
# Returns array of current values. |
|||
def test_length |
|||
def values |
|||
assert_equal 6, @fh.length |
|||
@hash.each_value.map {|values| values[0]} |
|||
assert_equal 6, @fh.size |
|||
end |
end |
||
# Returns array of current values for keys, like Hash#values_at. |
|||
def test_store |
|||
def values_at(*keys) |
|||
assert_raises(KeyEx) { @fh[:a] = 111 } |
|||
keys.map {|key| self[key]} |
|||
assert_equal 222, (@fh[:e] = 222) |
|||
assert_equal 222, (@fh.fetch :e) |
|||
assert_equal 333, @fh.store(:e, 333) |
|||
assert_equal 333, @fh[:e] |
|||
end |
end |
||
end</syntaxhighlight> |
|||
=={{header|Scala}}== |
|||
def test_values |
|||
{{Out}}Best seen running in your browser either by [https://scalafiddle.io/sf/OuVZ3bT/0 ScalaFiddle (ES aka JavaScript, non JVM)] or [https://scastie.scala-lang.org/qW5qzmdKSZSyAbZEqDROoA Scastie (remote JVM)]. |
|||
assert_equal [11, 22, 33, 44, 55, 66], @fh.values.sort! |
|||
<syntaxhighlight lang="scala">object CreateMapObject extends App { |
|||
end |
|||
val map = Map('A' -> 65, 'B' -> 66, 'C' -> 67) |
|||
println(map) |
|||
if RUBY_VERSION >= "1.8.7" |
|||
}</syntaxhighlight> |
|||
def test_delete_if_enum |
|||
a = @fh.delete_if.with_index { |kv, i| i >= 2 } |
|||
assert_same @fh, a |
|||
assert_equal 4, @fh.values.grep(nil).length |
|||
end |
|||
=={{header|Tcl}}== |
|||
def test_keep_if_enum |
|||
This solution uses a dict(ionary), so requires Tcl 8.5 or better. Variable traces are used to detect write or unset access to such a protected variable, restore it to the backup value at protection time, and throw an exception |
|||
a = @fh.keep_if.with_index { |kv, i| i >= 2 } |
|||
assert_same @fh, a |
|||
assert_equal 2, @fh.values.grep(nil).length |
|||
end |
|||
end |
|||
<syntaxhighlight lang="tcl">proc protect _var { |
|||
if RUBY_VERSION >= "1.9" |
|||
upvar 1 $_var var |
|||
def test_class_bracket_operator |
|||
trace add variable var {write unset} [list protect0 $var] |
|||
from_pairs = FencedHash[10, "ten", 20, "twenty", 30, "thirty"] |
|||
} |
|||
from_alist = FencedHash[ [ [10, "ten"], [20, "twenty"], [30, "thirty"] ] ] |
|||
proc protect0 {backup name1 name2 op} { |
|||
from_hash = FencedHash[10 => "ten", 20 => "twenty", 30 => "thirty"] |
|||
upvar 1 $name1 var |
|||
from_fhash = FencedHash[from_pairs] |
|||
trace remove variable var {write unset} [list protect 0 $backup] |
|||
set var $backup |
|||
trace add variable var {write unset} [list protect0 $backup] |
|||
return -code error "$name1 is protected" |
|||
} |
|||
proc trying cmd { #-- convenience function for demo |
|||
puts "trying: $cmd" |
|||
if [catch {uplevel 1 $cmd} msg] {puts $msg} |
|||
}</syntaxhighlight> |
|||
Testing: |
|||
dict set dic 1 one |
|||
dict set dic 2 two |
|||
puts dic:$dic |
|||
protect dic |
|||
trying "dict set dic 3 three" |
|||
puts dic:$dic |
|||
trying "dict unset dic 1" |
|||
trying "unset dic" |
|||
puts dic:$dic |
|||
displays on stdout: |
|||
[from_pairs, from_alist, from_hash, from_fhash, from_pairs |
|||
dic:1 one 2 two |
|||
].each_cons(2) do |a, b| |
|||
trying: dict set dic 3 three |
|||
assert_equal a, b |
|||
can't set "dic": dic is protected |
|||
assert_not_same a, b |
|||
dic:1 one 2 two |
|||
end |
|||
trying: dict unset dic 1 |
|||
end |
|||
can't set "dic": dic is protected |
|||
trying: unset dic |
|||
dic:1 one 2 two |
|||
=={{header|Wren}}== |
|||
def test_default_proc_assign |
|||
<syntaxhighlight lang="wren">class FixedSizeMap { |
|||
assert_nil @fh.default_proc |
|||
construct new(map) { |
|||
p = @fh.default_proc = proc { |h, k| h[k] = :deleted } |
|||
// copy the map so it cannot be mutated from the original reference |
|||
assert_same p, @fh.default_proc |
|||
_map = {} |
|||
for (me in map.toList) _map[me.key] = me.value |
|||
} |
|||
containsKey(key) { _map[key] != null } |
|||
assert_equal 11, @fh.delete(:q) |
|||
assert_equal :deleted, @fh[:q] |
|||
assert_raises(KeyEx) { @fh[:u] } |
|||
count { _map.count } |
|||
assert_nil @fh.default_proc |
|||
@fh.default_proc = p |
|||
assert_nil @fh.default |
|||
end |
|||
keys { _map.keys } |
|||
def test_each_rewind |
|||
class << @fh |
|||
values { _map.values } |
|||
attr_reader :test_rewind |
|||
def rewind |
|||
[key] { _map[key] } |
|||
@test_rewind = "correct" |
|||
[key] =(value) { |
|||
// do nothing if key doesn't already exist |
|||
end |
|||
if (_map[key] != null) _map[key] = value |
|||
assert_nil @fh.test_rewind |
|||
} |
|||
reset(key) { |
|||
var t = _map[key].type |
|||
// leave unaltered if no suitable default value |
|||
_map[key] = (t == Num) ? 0 : |
|||
(t == String) ? "": |
|||
(t == Bool) ? false : |
|||
(t == List) ? [] : |
|||
(t == Map) ? {} : _map[key] |
|||
} |
|||
iterate(iterator) { _map.iterate(iterator) } |
|||
iteratorValue(iterator) { _map.iteratorValue(iterator) } |
|||
toString { _map.toString } |
|||
} |
|||
var map = { "a": 1, "b": 2 } |
|||
var fsm = FixedSizeMap.new(map) |
|||
System.print(fsm) |
|||
System.print(fsm.count) |
|||
fsm["a"] = 3 |
|||
fsm["b"] = 4 |
|||
System.print(fsm) |
|||
System.print(fsm.containsKey("c")) |
|||
fsm["c"] = 5 // attempt to add a new key/value pair |
|||
System.print(fsm) // ignored |
|||
fsm.reset("a") |
|||
System.print(fsm) |
|||
System.print(fsm.keys.toList) |
|||
System.print(fsm.values.toList) |
|||
for (me in fsm) System.print([me.key, me.value]) |
|||
</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
{b: 2, a: 1} |
|||
2 |
|||
{b: 4, a: 3} |
|||
false |
|||
{b: 4, a: 3} |
|||
{b: 4, a: 0} |
|||
[b, a] |
|||
[4, 0] |
|||
[b, 4] |
|||
[a, 0] |
|||
</pre> |
|||
=={{header|zkl}}== |
|||
zkl has two dictionary objects: SD, a small dictionary that is created immutable and the "regular" dictionary has has a makeReadOnly method. They both behave the same when locked down. |
|||
<syntaxhighlight lang="zkl">d:=SD("one",1,"two",2); |
|||
d.keys; //-->L("one","two") |
|||
d["one"]; //-->1 |
|||
d.add("three",3); // error thrown |
|||
d.pop("one") // error thrown</syntaxhighlight> |
|||
# @fh.each.rewind must call @fh.rewind. If @fh forwards :each |
|||
# to another object then this test fails. |
|||
@fh.each.rewind |
|||
assert_equal "correct", @fh.test_rewind |
|||
end |
|||
def test_insertion_order |
|||
assert_equal [:q, :w, :e, :r, :t, :y], @fh.keys |
|||
assert_equal [11, 22, 33, 44, 55, 66], @fh.values |
|||
end |
|||
def test_key |
|||
assert_equal :q, @fh.key(11) |
|||
assert_equal :w, @fh.key(22) |
|||
assert_equal :e, @fh.key(33) |
|||
assert_equal :r, @fh.key(44) |
|||
assert_equal :t, @fh.key(55) |
|||
assert_equal :y, @fh.key(66) |
|||
assert_nil @fh.key(77) |
|||
end |
|||
end |
|||
end</lang> |
|||
{{omit from|AWK|Cannot tie the []= operator nor the delete command to custom functions.}} |
{{omit from|AWK|Cannot tie the []= operator nor the delete command to custom functions.}} |
||
Line 861: | Line 1,489: | ||
{{omit from|C|No associative arrays.}} |
{{omit from|C|No associative arrays.}} |
||
{{omit from|dc|No associative arrays.}} |
{{omit from|dc|No associative arrays.}} |
||
{{omit from|Go|No operator overriding.}} |
|||
{{omit from|GUISS}} |
{{omit from|GUISS}} |
||
{{omit from|PureBasic}} |
{{omit from|PureBasic}} |
||
{{omit from|sed|No associative arrays.}} |
{{omit from|sed|No associative arrays.}} |
||
{{omit from|Tcl|The value system and object system do not interact in a way that is necessary to support the prerequisites of this task. In particular, native dicts and arrays are not objects, and objects formally occupy part of the space of functions and not values or variables.}} |
|||
{{omit from|UNIX Shell|Bourne shell does not have these, but bashisms may be available on some shells}} |
{{omit from|UNIX Shell|Bourne shell does not have these, but bashisms may be available on some shells}} |
||
{{omit from|PicoLisp|Can't change the behavior of the property system without changing the language around it.}} |
{{omit from|PicoLisp|Can't change the behavior of the property system without changing the language around it.}} |
Latest revision as of 14:15, 21 November 2023
Create a Hash/Associative Array/Dictionary-like object that can be initialized with key/value pairs. The object should behave like a native Hash/Associative Array/Dictionary of the language, but with the following differences:
- No new keys can be added;
- Keys cannot be removed;
- Attempting to delete a key should set that keys value back to that used during initialisation.
(The value assigned to keys may be changed by normal assignment however).
If the language supports Magic Methods, then show how these work.
BASIC
BASIC256
map mapa
mapa["A"] = 65
mapa["B"] = 66
mapa["C"] = 67
foreach valor in mapa
print valor
print mapa[valor]
next valor
- Output:
A 65 B 66 C 67
FreeBASIC
FB doesn't have Dict natively, but we can implement them via Type
Type dict
m1 As String*1
m2 As Integer
End Type
Dim mapOf(1 To 3) As dict => {("A", 65), ("B", 66), ("C", 67)}
For i As Integer = 1 To Ubound(mapOf)
Print mapOf(i).m1
Print mapOf(i).m2
Next i
- Output:
A 65 B 66 C 67
C++
#include <iostream>
#include <map>
#include <utility>
using namespace std;
template<typename T>
class FixedMap : private T
{
// Two standard maps are used to implement FixedMap. One as a private
// base class which will allow the values (but not the keys) to be modified.
// Members of a private base class are not exposed to the derived class which will
// prevent keys from being added or deleted. Another map will hold copies of
// the initial values.
T m_defaultValues;
public:
FixedMap(T map)
: T(map), m_defaultValues(move(map)){}
// Expose members of the base class that do not modify the map.
using T::cbegin;
using T::cend;
using T::empty;
using T::find;
using T::size;
// Also expose members that can modify values but not add or remove keys.
using T::at;
using T::begin;
using T::end;
// The [] operator will normally add a new key if the key is not already in the
// map. Instead, throw an error if the key is missing.
auto& operator[](typename T::key_type&& key)
{
// Make it behave like at()
return this->at(forward<typename T::key_type>(key));
}
// Instead of removing a key, change the sematics of erase() to restore
// the original value of the key.
void erase(typename T::key_type&& key)
{
T::operator[](key) = m_defaultValues.at(key);
}
// Also change the sematics of clear() to restore all keys
void clear()
{
// Reset the base class using the defaults
T::operator=(m_defaultValues);
}
};
// Print the contents of a map
auto PrintMap = [](const auto &map)
{
for(auto &[key, value] : map)
{
cout << "{" << key << " : " << value << "} ";
}
cout << "\n\n";
};
int main(void)
{
// Create a fixed map based on the standard map
cout << "Map intialized with values\n";
FixedMap<map<string, int>> fixedMap ({
{"a", 1},
{"b", 2}});
PrintMap(fixedMap);
cout << "Change the values of the keys\n";
fixedMap["a"] = 55;
fixedMap["b"] = 56;
PrintMap(fixedMap);
cout << "Reset the 'a' key\n";
fixedMap.erase("a");
PrintMap(fixedMap);
cout << "Change the values the again\n";
fixedMap["a"] = 88;
fixedMap["b"] = 99;
PrintMap(fixedMap);
cout << "Reset all keys\n";
fixedMap.clear();
PrintMap(fixedMap);
try
{
// Adding or retrieving a missing key is a run time error
cout << "Try to add a new key\n";
fixedMap["newKey"] = 99;
}
catch (exception &ex)
{
cout << "error: " << ex.what();
}
}
- Output:
Map intialized with values {a : 1} {b : 2} Change the values of the keys {a : 55} {b : 56} Reset the 'a' key {a : 1} {b : 56} Change the values the again {a : 88} {b : 99} Reset all keys {a : 1} {b : 2} Try to add a new key error: map::at
D
struct DefaultAA(TK, TV) {
TV[TK] standard, current;
this(TV[TK] default_) pure /*nothrow*/ @safe {
this.standard = default_;
this.current = default_.dup;
}
alias current this;
void remove(in TK key) pure nothrow {
current[key] = standard[key];
}
void clear() pure /*nothrow*/ @safe {
current = standard.dup;
}
}
void main() {
import std.stdio;
auto d = ["a": 1, "b": 2].DefaultAA!(string, int);
d.writeln; // ["a":1, "b":2]
d["a"] = 55; d["b"] = 66;
d.writeln; // ["a":55, "b":66]
d.clear;
d.writeln; // ["a":1, "b":2]
d["a"] = 55; d["b"] = 66;
d["a"].writeln; // 55
d.remove("a");
d.writeln; // ["a":1, "b":66]
}
- Output:
["a":1, "b":2] ["a":55, "b":66] ["a":1, "b":2] 55 ["a":1, "b":66]
Go
Go's built-in map type is mutable and so, to complete this task, we need to create a read-only wrapper for it which doesn't permit further items to be added or existing items to be deleted though does allow them to be reset to their default value.
First create a sub-directory, romap, of the project directory and place the following package in it:
package romap
type Romap struct{ imap map[byte]int }
// Create new read-only wrapper for the given map.
func New(m map[byte]int) *Romap {
if m == nil {
return nil
}
return &Romap{m}
}
// Retrieve value for a given key, if it exists.
func (rom *Romap) Get(key byte) (int, bool) {
i, ok := rom.imap[key]
return i, ok
}
// Reset value for a given key, if it exists.
func (rom *Romap) Reset(key byte) {
_, ok := rom.imap[key]
if ok {
rom.imap[key] = 0 // default value of int
}
}
This package can now be imported and used within the main package as follows:
package main
import (
"./romap"
"fmt"
)
func main() {
// create a normal map
m := map[byte]int{'A': 65, 'B': 66, 'C': 67}
// place it in a read-only wrapper so no new item can be added or item deleted.
rom := romap.New(m)
// retrieve value represented by 'C' say
i, _ := rom.Get('C')
fmt.Println("'C' maps to", i)
// reset this to default value (doesn't actually delete the key)
rom.Reset('C')
i, _ = rom.Get('C')
fmt.Println("'C' now maps to", i)
}
- Output:
'C' maps to 67 'C' now maps to 0
J
Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be:
lookup=: values {~ keys&i.
For example:
lookup=: 10 20 30 40 50 {~ (;:'this is a test')&i.
lookup ;:'a test'
30 40
Notes:
1) While the result can not be modified or deleted, the name used to refer to it can be made to refer to something else, and once all references are lost it will be garbage collected.
2) In the above example, we have 5 values and 4 keys. The extra value is used when no key is found. If no extra value was provided, the "key not found" case would be an error case.
3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else.
Java
Java supports unmodifiable maps, sets, lists, and other more specialized unmodifiable collections. In this example, we have a unmodifiable map. We first create an ordinary map, modify as needed, then call the Collections.unmodifiableMap
. We can subsequently read the map, but modification is not permitted. The returned map will subsequently throw a UnsupportedOperationException
exception if a mutation operator is called. Several are demonstrated below.
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
// Title: Create an object/Native demonstration
public class ImmutableMap {
public static void main(String[] args) {
Map<String,Integer> hashMap = getImmutableMap();
try {
hashMap.put("Test", 23);
}
catch (UnsupportedOperationException e) {
System.out.println("ERROR: Unable to put new value.");
}
try {
hashMap.clear();
}
catch (UnsupportedOperationException e) {
System.out.println("ERROR: Unable to clear map.");
}
try {
hashMap.putIfAbsent("Test", 23);
}
catch (UnsupportedOperationException e) {
System.out.println("ERROR: Unable to put if absent.");
}
for ( String key : hashMap.keySet() ) {
System.out.printf("key = %s, value = %s%n", key, hashMap.get(key));
}
}
private static Map<String,Integer> getImmutableMap() {
Map<String,Integer> hashMap = new HashMap<>();
hashMap.put("Key 1", 34);
hashMap.put("Key 2", 105);
hashMap.put("Key 3", 144);
return Collections.unmodifiableMap(hashMap);
}
}
{out}}
ERROR: Unable to put new value. ERROR: Unable to clear map. ERROR: Unable to put if absent. key = Key 1, value = 34 key = Key 2, value = 105 key = Key 3, value = 144
JavaScript
This is a first demonstration of the task, but only implemented the functionality, not any native behavior, eg indexing. JavaScript experts may want to replace this one.
var keyError = new Error("Invalid Key Error (FixedKeyDict)") ;
function FixedKeyDict(obj)
{
var myDefault = new Object() ;
var myData = new Object() ;
for(k in obj) {
myDefault[k] = obj[k] ;
myData[k] = obj[k] ;
}
var gotKey = function(k) {
for(kk in myDefault) {
if(kk == k) return true ;
}
return false ;
} ;
this.hasKey = gotKey ;
var checkKey = function(k) {
if(!gotKey(k))
throw keyError ;
} ;
this.getItem = function(k) {
checkKey(k) ;
return myData[k];
} ;
this.setItem = function(k, v) {
checkKey(k) ;
myData[k] = v ;
} ;
this.resetItem = function(k) {
checkKey(k) ;
myData[k] = myDefault[k] ;
} ;
this.delItem = this.resetItem ;
this.clear = function() {
for(k in myDefault)
myData[k] = myDefault[k] ;
} ;
this.iterator = function() {
for(k in myDefault)
yield (k);
} ;
this.clone = function() {
return new FixedKeyDict(myDefault) ;
}
this.toStr = function() {
var s = "" ;
for(key in myData)
s = s + key + " => " + myData[key] + ", " ;
return "FixedKeyDict{" + s + "}" ;
} ;
}
Test run:
const BR = "<BR>\n"
var pl = function(s) {
document.write(s + BR) ;
} ;
pl("<pre>") ;
var o = { foo:101, bar:102 } ;
var h = new FixedKeyDict(o) ;
pl("Fixed Key Dict Created") ;
pl("toString : " + h.toStr()) ;
pl("get an item: " + h.getItem("foo")) ;
pl("check a key: " + h.hasKey("boo")) ;
pl("ditto : " + h.hasKey("bar")) ;
h.setItem("bar", 999) ;
pl("set an item: " + h.toStr()) ;
pl("Test iterator (or whatever)") ;
for(k in h.iterator())
pl(" " + k + " => " + h.getItem(k)) ;
var g = h.clone() ;
pl("Clone a dict") ;
pl(" clone : " + g.toStr()) ;
pl(" original : " + h.toStr()) ;
h.clear() ;
pl("clear or reset the dict") ;
pl(" : " + h.toStr()) ;
try {
h.setItem("NoNewKey", 666 ) ;
} catch(e) {
pl("error test : " + e.message) ;
}
output :
Fixed Key Dict Created toString : FixedKeyDict{foo => 101, bar => 102, } get an item: 101 check a key: false ditto : true set an item: FixedKeyDict{foo => 101, bar => 999, } Test iterator (or whatever) foo => 101 bar => 999 Clone a dict clone : FixedKeyDict{foo => 101, bar => 102, } original : FixedKeyDict{foo => 101, bar => 999, } clear or reset the dict : FixedKeyDict{foo => 101, bar => 102, } error test : Invalid Key Error (FixedKeyDict)
jq
jq objects are JSON objects and can be created using JSON syntax, e.g.
{"language": "jq"}
Objects can also be created programmatically, e.g.
{"one": 1} + {"two": 2}
jq objects, however, are really just values: they are immutable, and cannot be "deleted" any more than the number 1 can be deleted.
Julia
using BackedUpImmutable
function testBackedUpImmutableDict()
fibr = BackedUpImmutableDict{String,Int64}(["a" => 0, "b" => 1, "c" => 1, "d" => 2,
"e" => 3, "f" => 5, "g" => 8, "h" => 13, "i" => 21, "j" => 34, "extra" => -1])
x = fibr["extra"]
@test x == -1
fibr["extra"] = 0
y = fibr["extra"]
@test y == 0
restore!(fibr, "extra")
z = fibr["extra"]
@test z == -1
@test_throws String begin fibr["k"] = 55 end
fibr["a"] = 9
fibr["b"] = 7
# test restore all to default
restoreall!(fibr)
@test fibr["a"] == 0
end
All tests pass.
Kotlin
// version 1.1.2
fun main(args: Array<String>) {
// This line creates a read-only map which cannot be changed in any way nor cleared
val map = mapOf('A' to 65, 'B' to 66, 'C' to 67)
println(map)
}
- Output:
{A=65, B=66, C=67}
M2000 Interpreter
Module CheckIt {
Class LockedHash {
Private:
inventory Vars ' no same keys
unlock
module nosuchvariable {
Error "No such value:"+letter$
}
module NoNewItem {
Error "No new item, use unlock method before"
}
module NoRemoveItem {
Error "Can't remove item, use unlock method before"
}
Public:
module Unlock {
.unlock<=True
}
module Writeln {
m=each(.Vars)
while m {
Print Quote$(Eval$(m, m^));",";Eval(m),
}
Print
}
Value (st$){
st$=Ucase$(st$)
if exist(.Vars, st$) then =Eval(.Vars) : Exit
.nosuchvariable st$
}
Set (st$){
st$=Ucase$(st$)
Read val
if exist(.Vars, st$) then Return .Vars, st$:=val : Exit
If .unlock then { Append .Vars, st$:=val} Else .NoNewItem
}
module Remove (st$) {
if not .unlock then .NoRemoveItem
st$=Ucase$(st$)
Try {
delete .Vars, st$
}
}
module Clear {
Clear .Vars
}
Class: ' this part exist only at construction
module LockedHash {
While match("SN") {
read st$, val
st$=ucase$(st$)
\\ if we append key which exist we get error
Append .Vars, st$:=val
}
}
}
d=LockedHash("a", 1, "b", 2)
d.writeln
d("a")=55 : d("b")=66
d.writeln
d.clear
d.writeln
d.unlock
d("a")=55 : d("b")=66
Print d("a")=55, d("a")/d("b")<1
d.remove "a"
d.writeln
}
Checkit
Mathematica / Wolfram Language
a[1] = "Do not modify after creation";
a[2] = "Native demonstration";
Protect[a];
Example usage:
a[3] = 2 ->Set::write: Tag a in a[1] is Protected. >>
Nim
We leverage native stdlib table as our own object by implementing limited actual native table functionalities.
import tables, options
type
MyTable = object
table: TableRef[string, int]
# return empty if the key is not available
proc `[]`(m: MyTable, key: string): Option[int] =
if key in m.table: result = some m.table[key]
else: result = none int
# update an item, doing nothing if the key is available during first initialization
proc `[]=`(m: var MyTable, key: string, val: int) =
if key notin m.table: return
m.table[key] = val
proc reset(m: var MyTable) =
for _, v in m.table.mpairs: v = 0
# sugar for defining MyTable object
proc toTable(vals: openarray[(string, int)]): MyTable =
result.table = newTable vals
proc main =
# MyTable construction
var myobj = {"key1": 1, "key2": 2, "key3": 3}.toTable
# test getting existing key
let val1 = myobj["key1"]
if val1.isSome: echo "val1: ", val1.get
# test adding new key
myobj["key4"] = 4
let val4 = myobj["key4"]
if val4.isSome: echo val4.get
else: echo "val4 is empty"
# test reset and test whether its value is zero-ed
reset myobj
doAssert myobj["key3"].get == 0
main()
- Output:
val1: 1 val4 is empty
Perl
use strict;
package LockedHash;
use parent 'Tie::Hash';
use Carp;
sub TIEHASH {
my $cls = shift;
my %h = @_;
bless \%h, ref $cls || $cls;
}
sub STORE {
my ($self, $k, $v) = @_;
croak "Can't add key $k" unless exists $self->{$k};
$self->{$k} = $v;
}
sub FETCH {
my ($self, $k) = @_;
croak "No key $k" unless exists $self->{$k};
$self->{$k};
}
sub DELETE {
my ($self, $k) = @_;
croak "No key $k" unless exists $self->{$k};
$self->{$k} = 0;
}
sub CLEAR { } # ignored
sub EXISTS { exists shift->{+shift} }
sub FIRSTKEY {
my $self = shift;
keys %$self;
each %$self;
}
sub NEXTKEY {
my $self = shift;
each %$self;
}
sub lock_hash :prototype(\%) {
my $ref = shift;
tie(%$ref, __PACKAGE__, %$ref);
}
1;
my %h = (a => 3, b => 4, c => 5);
# lock down %h
LockedHash::lock_hash(%h);
# show hash content and iteration
for (sort keys %h) { print "$_ => $h{$_}\n"; }
# try delete b
delete $h{b};
print "\nafter deleting b: b => $h{b}\n";
# change value of a
$h{a} = 100;
print "\na => $h{a}\n";
# add a new key x: will die
eval { $h{x} = 1 };
if ($@) { print "Operation error: $@" }
output:
a => 3
b => 4
c => 5
after deleting b: b => 0
a => 100
operation error: Can't add key x at test.pl line 14
LockedHash::STORE('LockedHash=HASH(0x8cebe14)', 'x', 1) called at test.pl line 66
eval {...} called at test.pl line 66
Phix
There is no native "read-only" setting on phix dictionaries, so the following wraps a pair of them to provide the requested functionality.
with javascript_semantics enum STD, CUR sequence fkds = {} -- fixed key dictionaries ;-) integer freelist = 0 procedure fkd_destroy(integer id) integer {std,cur} = fkds[id] destroy_dict(std) destroy_dict(cur) fkds[id] = freelist freelist = id end procedure function fkd_new(sequence key_pairs) integer std = new_dict(key_pairs), cur = new_dict(std), id = freelist if id=0 then fkds = append(fkds,{std,cur}) id = length(fkds) else freelist = fkds[id] fkds[id] = {std,cur} end if return id end function procedure fkd_clear(integer id) integer {std,cur} = fkds[id] destroy_dict(cur) fkds[id][CUR] = new_dict(std) end procedure function fkd_get(integer id, object key) return getd(key,fkds[id][CUR]) end function procedure fkd_set(integer id, object key, data) integer node = getd_index(key,fkds[id][CUR]) if node=NULL then throw("invalid/new key") end if setd(key,data,fkds[id][CUR]) end procedure procedure fkd_remove(integer id, object key) integer {std,cur} = fkds[id], node = getd_index(key,std) if node=NULL then throw("invalid key") end if setd(key,getd_by_index(node,std),cur) end procedure function fkd_sprint(integer id) integer cur = fkds[id][CUR] sequence res = getd_all_keys(cur) for i=1 to length(res) do object ri = res[i] res[i] = {ri,getd(ri,cur)} end for return res end function procedure main() integer id = fkd_new({{"a",1},{"b",2}}) ?fkd_sprint(id) -- {{"a",1},{"b",2}} fkd_set(id,"a",55) fkd_set(id,"b",66) ?fkd_sprint(id) -- {{"a",55},{"b",66}} fkd_clear(id) ?fkd_sprint(id) -- {{"a",1},{"b",2}} fkd_set(id,"a",55) fkd_set(id,"b",66) ?fkd_get(id,"a") -- 55 fkd_remove(id,"a") try fkd_set(id,"NoNewKey",77) catch e ?e[E_USER] -- "invalid/new key" end try ?fkd_sprint(id) -- {{"a",1},{"b",66}} fkd_destroy(id) end procedure main()
Python
from collections import UserDict
import copy
class Dict(UserDict):
'''
>>> d = Dict(a=1, b=2)
>>> d
Dict({'a': 1, 'b': 2})
>>> d['a'] = 55; d['b'] = 66
>>> d
Dict({'a': 55, 'b': 66})
>>> d.clear()
>>> d
Dict({'a': 1, 'b': 2})
>>> d['a'] = 55; d['b'] = 66
>>> d['a']
55
>>> del d['a']
>>> d
Dict({'a': 1, 'b': 66})
'''
def __init__(self, dict=None, **kwargs):
self.__init = True
super().__init__(dict, **kwargs)
self.default = copy.deepcopy(self.data)
self.__init = False
def __delitem__(self, key):
if key in self.default:
self.data[key] = self.default[key]
else:
raise NotImplementedError
def __setitem__(self, key, item):
if self.__init:
super().__setitem__(key, item)
elif key in self.data:
self.data[key] = item
else:
raise KeyError
def __repr__(self):
return "%s(%s)" % (type(self).__name__, super().__repr__())
def fromkeys(cls, iterable, value=None):
if self.__init:
super().fromkeys(cls, iterable, value)
else:
for key in iterable:
if key in self.data:
self.data[key] = value
else:
raise KeyError
def clear(self):
self.data.update(copy.deepcopy(self.default))
def pop(self, key, default=None):
raise NotImplementedError
def popitem(self):
raise NotImplementedError
def update(self, E, **F):
if self.__init:
super().update(E, **F)
else:
haskeys = False
try:
keys = E.keys()
haskeys = Ture
except AttributeError:
pass
if haskeys:
for key in keys:
self[key] = E[key]
else:
for key, val in E:
self[key] = val
for key in F:
self[key] = F[key]
def setdefault(self, key, default=None):
if key not in self.data:
raise KeyError
else:
return super().setdefault(key, default)
Racket
This task is implemented as a new fenced-hash time with an interface similar to the native hash. Also it can be used a native dict.
Implementation of functions that handle fenced-hash:
;(struct fenced-hash (actual original) ...)
(define (fenced-hash-ref dict
key
[default (lambda () (error "key not found" key))])
(hash-ref (fenced-hash-actual dict) key default))
(define (fenced-hash-set! dict key val)
(unless (hash-has-key? (fenced-hash-actual dict) key)
(error "unable to add key" key))
(hash-set! (fenced-hash-actual dict) key val))
(define (fenced-hash-remove! dict key) ;reset the value!
(unless (hash-has-key? (fenced-hash-actual dict) key)
(error "key not found" key))
(hash-set! (fenced-hash-actual dict)
key
(hash-ref (fenced-hash-original dict) key)))
(define (fenced-hash-clear! dict) ;reset all values!
(hash-for-each (fenced-hash-original dict)
(lambda (key val) (hash-set! (fenced-hash-actual dict) key val))))
(define (fenced-hash-has-key? dict key)
(hash-has-key? (fenced-hash-actual dict) key))
(define (fenced-hash-count dict)
(hash-count (fenced-hash-actual dict)))
(define (fenced-hash-iterate-first dict)
(hash-iterate-first (fenced-hash-actual dict)))
(define (fenced-hash-iterate-next dict pos)
(hash-iterate-next (fenced-hash-actual dict) pos))
(define (fenced-hash-iterate-key dict pos)
(hash-iterate-key (fenced-hash-actual dict) pos))
(define (fenced-hash-iterate-value dict pos)
(hash-iterate-value (fenced-hash-actual dict) pos))
(define (*fenced-hash-print dict port mode)
;private custom-write ;mode is ignored
(write-string "#fenced-hash" port)
(write (hash->list (fenced-hash-actual dict)) port))
Definition of the actual structure and a “public” creator:
(struct fenced-hash (actual original)
#:extra-constructor-name *fenced-hash ;private constructor
#:omit-define-syntaxes ;not sure this is a good idea
#:methods gen:custom-write
[(define write-proc *fenced-hash-print)]
#:methods gen:dict
[(define dict-ref fenced-hash-ref)
(define dict-set! fenced-hash-set!)
(define dict-remove! fenced-hash-remove!)
(define dict-has-key? fenced-hash-has-key?) ;unused in 5.6.3
(define dict-count fenced-hash-count)
(define dict-iterate-first fenced-hash-iterate-first)
(define dict-iterate-next fenced-hash-iterate-next)
(define dict-iterate-key fenced-hash-iterate-key)
(define dict-iterate-value fenced-hash-iterate-value)])
(define (fenced-hash . args) ; public constructor
(define original (apply hash args))
(*fenced-hash (hash-copy original) original))
Example: Use the fenced-hash functions:
(define d (fenced-hash "a" 1 "b" 2))
(displayln d)
(fenced-hash-set! d "a" 55)
(fenced-hash-set! d "b" 66)
(displayln d)
(fenced-hash-clear! d)
(displayln d)
(fenced-hash-set! d "a" 55)
(fenced-hash-set! d "b" 66)
(displayln d)
(fenced-hash-remove! d "a")
(displayln d)
- Output:
#fenced-hash(("b" . 2) ("a" . 1)) #fenced-hash(("b" . 66) ("a" . 55)) #fenced-hash(("b" . 2) ("a" . 1)) #fenced-hash(("b" . 66) ("a" . 55)) #fenced-hash(("b" . 66) ("a" . 1))
Example (continued): Use the same object as a dict. The dict-clear! method is not defined, so we must call fenced-hash-clear! instead.
(fenced-hash-clear! d)
(displayln d)
(dict-set! d "a" 55)
(dict-set! d "b" 66)
(displayln d)
(fenced-hash-clear! d) ;dict-clear is not defined
(displayln d)
(dict-set! d "a" 55)
(dict-set! d "b" 66)
(displayln d)
(dict-remove! d "a")
(displayln d)
- Output:
#fenced-hash(("b" . 2) ("a" . 1)) #fenced-hash(("b" . 66) ("a" . 55)) #fenced-hash(("b" . 2) ("a" . 1)) #fenced-hash(("b" . 66) ("a" . 55)) #fenced-hash(("b" . 66) ("a" . 1))
Raku
(formerly Perl 6)
Here we use delegation to handle all the normal hash methods that we don't need to override to define our new class.
class FixedHash {
has $.hash handles *;
method new(*@args) { self.bless: hash => Hash.new: @args }
method AT-KEY(FixedHash:D: $key is copy) is rw {
$!hash.EXISTS-KEY($key) ?? $!hash.AT-KEY($key) !! Failure.new(q{can't store value for unknown key});
}
method DELETE-KEY($key) { $!hash.{$key} = Nil }
}
# Testing
my $fh = FixedHash.new: "a" => 1, "b" => 2;
say $fh<a b>; # 1 2
$fh<b>:delete;
say $fh<a b>; # 1 Nil
$fh<b> = 42;
say $fh<a b>; # 1 42
say $fh<c>; # Nil
$fh<c> = 43; # error
- Output:
(1 2) (1 (Any)) (1 42) can't store value for unknown key in block <unit> at native-demonstration.p6:17 Actually thrown at: in block <unit> at native-demonstration.p6:17
By defining FALLBACK any class can handle undefined method calls. Since any class inherits plenty of methods from Any our magic object will be more of a novice conjurer then a master wizard proper.
class Magic {
has %.hash;
multi method FALLBACK($name, |c) is rw { # this will eat any extra parameters
%.hash{$name}
}
multi method FALLBACK($name) is rw {
%.hash{$name}
}
}
my $magic = Magic.new;
$magic.foo = 10;
say $magic.foo;
$magic.defined = False; # error
- Output:
10 Cannot modify an immutable Bool in block <unit> at native-demonstration.p6:15
Ring
# Project : Create an object/Native demonstration
map = []
map["A"] = 65
map["B"] = 66
map["C"] = 67
see map + nl
Output:
A 65 B 66 C 67
Ruby
# A FencedHash acts like a Hash, but with a fence around its keys.
# One may change its values, but not its keys. Any attempt to insert
# a new key raises KeyError. One may delete a key, but this only
# restores its original value.
#
# FencedHash reimplements these Hash methods: #[] #[]= #clear #delete
# #delete_if #default #default= #each_key #each_pair #each_value
# #fetch #has_key? #keep_if #keys #length #values #values_at
class FencedHash
# call-seq:
# FencedHash.new(hash, obj=nil) -> fh
#
# Creates a FencedHash that takes its keys and original values from
# a source _hash_. The source _hash_ can be any object that
# responds to each_pair. Sets the default value for missing keys to
# _obj_, so FencedHash#[] returns _obj_ when a key is not in fence.
def initialize(hash, obj=nil)
@default = obj
@hash = {}
hash.each_pair do |key, value|
# @hash[key][0] = current value
# @hash[key][1] = original value
@hash[key] = [value, value]
end
end
def initialize_clone(orig)
# Object#clone calls here in Ruby 2.0. If _orig_ was frozen, then
# each array of _values_ is frozen, so make frozen clones.
super
copy = {}
@hash.each_pair {|key, values| copy[key] = values.clone }
@hash = copy
end
def initialize_dup(orig)
# Object#dup calls here in Ruby 2.0. If _orig_ was frozen, then
# make duplicates that are not frozen.
super
copy = {}
@hash.each_pair {|key, values| copy[key] = values.dup }
@hash = copy
end
# Retrieves current value for _key_, like Hash#[]. If _key_ is not
# in fence, returns default object.
def [](key)
values = @hash[key]
if values
values[0]
else
@default
end
end
# call-seq:
# fh[key] = value -> value
# fh.store(key, value) -> value
#
# Sets _value_ for a _key_. Returns _value. If _key_ is not in
# fence, raises KeyError.
def []=(key, value)
values = @hash[key]
if values
values[0] = value
else
raise KeyError, "fence prevents adding new key: #{key.inspect}"
end
end
alias store []=
# Resets all keys to their original values. Returns self.
def clear
@hash.each_value {|values| values[0] = values[1]}
self
end
# Resets _key_ to its original value. Returns old value before
# reset. If _key_ is not in fence, returns +nil+.
def delete(key)
values = @hash[key]
if values
old = values[0]
values[0] = values[1]
old # return old
end # else return nil
end
# call-seq:
# fh.delete_if {|key, value| block } -> fh
# fh.delete_if -> enumerator
#
# Yields each _key_ with current _value_ to _block_. Resets _key_
# to its original value when block evaluates to true.
def delete_if
if block_given?
@hash.each_pair do |key, values|
yield(key, values[0]) and values[0] = values[1]
end
self
else
enum_for(:delete_if) { @hash.size }
end
end
# The default value for keys not in fence.
attr_accessor :default
# call-seq:
# fh.each_key {|key| block} -> fh
# fh.each_key -> enumerator
#
# Yields each key in fence to the block.
def each_key(&block)
if block
@hash.each_key(&block)
self
else
enum_for(:each_key) { @hash.size }
end
end
# call-seq:
# fh.each_pair {|key, value| block} -> fh
# fh.each_pair -> enumerator
#
# Yields each key-value pair to the block, like Hash#each_pair.
# This yields each [key, value] as an array of 2 elements.
def each_pair
if block_given?
@hash.each_pair {|key, values| yield [key, values[0]] }
self
else
enum_for(:each_pair) { @hash.size }
end
end
# call-seq
# fh.each_value {|value| block} -> fh
# fh.each_value -> enumerator
#
# Yields current value of each key-value pair to the block.
def each_value
if block_given?
@hash.each_value {|values| yield values[0] }
else
enum_for(:each_value) { @hash.size }
end
end
# call-seq:
# fenhsh.fetch(key [,default])
# fenhsh.fetch(key) {|key| block }
#
# Fetches value for _key_. Takes same arguments as Hash#fetch.
def fetch(*argv)
argc = argv.length
unless argc.between?(1, 2)
raise(ArgumentError,
"wrong number of arguments (#{argc} for 1..2)")
end
if argc == 2 and block_given?
warn("#{caller[0]}: warning: " +
"block supersedes default value argument")
end
key, default = argv
values = @hash[key]
if values
values[0]
elsif block_given?
yield key
elsif argc == 2
default
else
raise KeyError, "key not found: #{key.inspect}"
end
end
# Freezes this FencedHash.
def freeze
@hash.each_value {|values| values.freeze }
super
end
# Returns true if _key_ is in fence.
def has_key?(key)
@hash.has_key?(key)
end
alias include? has_key?
alias member? has_key?
# call-seq:
# fh.keep_if {|key, value| block } -> fh
# fh.keep_if -> enumerator
#
# Yields each _key_ with current _value_ to _block_. Resets _key_
# to its original value when block evaluates to false.
def keep_if
if block_given?
@hash.each_pair do |key, values|
yield(key, values[0]) or values[0] = values[1]
end
self
else
enum_for(:keep_if) { @hash.size }
end
end
# Returns array of keys in fence.
def keys
@hash.keys
end
# Returns number of key-value pairs.
def length
@hash.length
end
alias size length
# Converts self to a regular Hash.
def to_h
result = Hash.new(@default)
@hash.each_pair {|key, values| result[key] = values[0]}
result
end
# Converts self to a String.
def to_s
"#<#{self.class}: #{to_h}>"
end
alias inspect to_s
# Returns array of current values.
def values
@hash.each_value.map {|values| values[0]}
end
# Returns array of current values for keys, like Hash#values_at.
def values_at(*keys)
keys.map {|key| self[key]}
end
end
Scala
- Output:
Best seen running in your browser either by ScalaFiddle (ES aka JavaScript, non JVM) or Scastie (remote JVM).
object CreateMapObject extends App {
val map = Map('A' -> 65, 'B' -> 66, 'C' -> 67)
println(map)
}
Tcl
This solution uses a dict(ionary), so requires Tcl 8.5 or better. Variable traces are used to detect write or unset access to such a protected variable, restore it to the backup value at protection time, and throw an exception
proc protect _var {
upvar 1 $_var var
trace add variable var {write unset} [list protect0 $var]
}
proc protect0 {backup name1 name2 op} {
upvar 1 $name1 var
trace remove variable var {write unset} [list protect 0 $backup]
set var $backup
trace add variable var {write unset} [list protect0 $backup]
return -code error "$name1 is protected"
}
proc trying cmd { #-- convenience function for demo
puts "trying: $cmd"
if [catch {uplevel 1 $cmd} msg] {puts $msg}
}
Testing:
dict set dic 1 one dict set dic 2 two puts dic:$dic protect dic trying "dict set dic 3 three" puts dic:$dic trying "dict unset dic 1" trying "unset dic" puts dic:$dic
displays on stdout:
dic:1 one 2 two trying: dict set dic 3 three can't set "dic": dic is protected dic:1 one 2 two trying: dict unset dic 1 can't set "dic": dic is protected trying: unset dic dic:1 one 2 two
Wren
class FixedSizeMap {
construct new(map) {
// copy the map so it cannot be mutated from the original reference
_map = {}
for (me in map.toList) _map[me.key] = me.value
}
containsKey(key) { _map[key] != null }
count { _map.count }
keys { _map.keys }
values { _map.values }
[key] { _map[key] }
[key] =(value) {
// do nothing if key doesn't already exist
if (_map[key] != null) _map[key] = value
}
reset(key) {
var t = _map[key].type
// leave unaltered if no suitable default value
_map[key] = (t == Num) ? 0 :
(t == String) ? "":
(t == Bool) ? false :
(t == List) ? [] :
(t == Map) ? {} : _map[key]
}
iterate(iterator) { _map.iterate(iterator) }
iteratorValue(iterator) { _map.iteratorValue(iterator) }
toString { _map.toString }
}
var map = { "a": 1, "b": 2 }
var fsm = FixedSizeMap.new(map)
System.print(fsm)
System.print(fsm.count)
fsm["a"] = 3
fsm["b"] = 4
System.print(fsm)
System.print(fsm.containsKey("c"))
fsm["c"] = 5 // attempt to add a new key/value pair
System.print(fsm) // ignored
fsm.reset("a")
System.print(fsm)
System.print(fsm.keys.toList)
System.print(fsm.values.toList)
for (me in fsm) System.print([me.key, me.value])
- Output:
{b: 2, a: 1} 2 {b: 4, a: 3} false {b: 4, a: 3} {b: 4, a: 0} [b, a] [4, 0] [b, 4] [a, 0]
zkl
zkl has two dictionary objects: SD, a small dictionary that is created immutable and the "regular" dictionary has has a makeReadOnly method. They both behave the same when locked down.
d:=SD("one",1,"two",2);
d.keys; //-->L("one","two")
d["one"]; //-->1
d.add("three",3); // error thrown
d.pop("one") // error thrown